自学内容网 自学内容网

Java基础概览和常用知识(七)

什么是自动装箱和自动拆箱,原理是什么?

自动装箱和自动拆箱是Java编程语言中的两个重要概念,它们涉及到基本数据类型与其对应包装类之间的自动转换。

一、定义

自动装箱:是指Java编译器在需要将基本数据类型转换为对应的包装类时,会自动进行转换的过程。例如,将int类型转换为Integer类型,将double类型转换为Double类型等。

自动拆箱:则是自动装箱的逆过程,即Java编译器在需要将包装类对象转换为对应的基本数据类型时,会自动进行转换。例如,将Integer类型转换为int类型,将Double类型转换为double类型等。

二、原理

自动装箱:当基本数据类型被赋值给一个包装类对象时,编译器会自动生成一个调用对应包装类构造函数的代码。这种转换是通过包装类的构造函数或静态工厂方法(例如Integer.valueOf(int i))实现的。

自动拆箱:当包装类对象被赋值给基本数据类型时,编译器会自动生成一个调用包装类的xxxValue()方法的代码,以获取基本数据类型的值。这种转换是通过包装类提供的xxxValue()方法实现的,其中xxx表示基本数据类型(如intValue、doubleValue等)。

三、例子

  1. 自动装箱
int num = 5;  
Integer numObj = num; // 自动装箱,编译器会将int类型的num自动转换为Integer类型的numObj
在这个例子中,编译器将int类型的num自动转换为Integer类型的numObj。
  1. 自动拆箱
Integer numObj = 10;  
int num = numObj; // 拆箱,编译器会将Integer类型的numObj自动转换为int类型的num

在这个例子中,编译器将Integer类型的numObj自动转换为int类型的num。

另外,自动装箱和拆箱在Java集合框架(如ArrayList)中尤为重要,因为这些框架只能存储对象而不能存储基本数据类型。例如:

ArrayList<Integer> list = new ArrayList<>();  
list.add(10); // int 10 被自动装箱为 Integer 对象  
int value = list.get(0); // Integer 对象被自动拆箱为 int 基本类型

在这个例子中,list.add(10)将int类型的10自动装箱为Integer对象,然后list.get(0)将Integer对象自动拆箱为int基本数据类型。

举例:

Integer i = 10;  //装箱
int n = i;   //拆箱

上面这两行代码对应的字节码为:

   L1

    LINENUMBER 8 L1

    ALOAD 0

    BIPUSH 10

    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

    PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;

   L2

    LINENUMBER 9 L2

    ALOAD 0

    ALOAD 0

    GETFIELD AutoBoxTest.i : Ljava/lang/Integer;

    INVOKEVIRTUAL java/lang/Integer.intValue ()I

    PUTFIELD AutoBoxTest.n : I

    RETURN

从字节码中,我们发现装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。

因此,

  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。

private static long sum() {
    // 应该使用 long 而不是 Long
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

总的来说,自动装箱和自动拆箱机制使得Java语言的基本数据类型与对象之间的转换变得更加简单和高效。然而,在使用时也需要注意其潜在的问题,如性能开销和可能的空指针异常等。因此,在编写代码时,应尽量避免在循环或大量计算中自动装箱,如果可能,手动进行装箱操作以减少不必要的开销。

什么浮点数运算的时候会有精度丢失的风险?

浮点数运算时会有精度丢失的风险,这主要源于浮点数在计算机中的表示方式及其运算特性。以下是对这一现象的详细解释:

一、浮点数的表示方式

二进制系统:计算机内部使用二进制系统来存储和处理数据。然而,许多十进制小数在转换为二进制时无法得到一个有限长度的循环小数或终止小数,例如十进制的0.1在二进制中就是无限循环小数。

IEEE 754标准:浮点数在计算机中通常采用IEEE 754标准进行表示,该标准定义了浮点数的编码格式,包括符号位、指数部分和尾数部分。其中,尾数的位数是有限的,这意味着浮点数只能精确地表示一定数量的小数位数。

二、精度丢失的原因

有限位数:由于浮点数的尾数位数有限,因此无法精确表示所有的十进制小数。当小数位数超过尾数的表示范围时,就会发生精度丢失。

舍入误差:在浮点数运算过程中,由于数值的内部表示不精确,运算结果也需要进行舍入处理以适应浮点数的存储格式。这种舍入误差会随着多次运算的累积而逐渐增大,最终导致精度丢失。

二进制与十进制的不匹配:由于二进制和十进制之间的不匹配,某些十进制小数在二进制中无法精确表示,这也会导致精度丢失。

三、精度丢失的影响

计算误差:精度丢失会导致计算结果与期望结果之间存在误差。这种误差在多次运算后会逐渐累积,最终影响结果的准确性。

比较问题:由于浮点数的精度问题,直接比较两个浮点数是否相等通常是不可靠的。即使两个浮点数的值在视觉上非常接近,它们在底层的二进制表示中也可能存在微小差异。

四、应对措施

使用BigDecimal类:在需要高精度计算的场景中,可以使用Java中的BigDecimal类来代替浮点数进行运算。BigDecimal类可以精确表示小数,并且允许开发者控制舍入模式,从而避免浮点数的精度问题。

引入误差容忍度:在比较两个浮点数时,可以引入误差容忍度(也称为epsilon),即允许两个浮点数的差值在某个很小的范围内视为相等。这样可以避免直接比较浮点数时产生的误差。

选择合适的数值类型:根据实际需求选择合适的数值类型,如float、double或BigDecimal。在可能的情况下,优先选择精度更高的数值类型以减少精度丢失的风险。

综上所述,浮点数运算时会有精度丢失的风险,这主要源于浮点数的表示方式和运算特性。为了降低这种风险,可以采取相应的应对措施来确保计算的准确性。

如何解决浮点数运算的精度丢失问题?

浮点数运算的精度丢失问题在计算机科学中是一个普遍存在的挑战,源于二进制无法精确表示某些十进制小数以及运算过程中的舍入误差。以下是一些解决浮点数运算精度丢失问题的方法:

一、使用整数进行计算

  1. 原理
    • 将浮点数转换为整数进行计算,可以避免浮点数表示不精确的问题。
    • 通过放大或缩小数值的倍数(如乘以10的幂),将浮点数转换为整数进行计算,然后再将结果转换回浮点数。
function multiply(a, b) {  
    const multiplier = 10000; // 选择一个合适的倍数,如10000  
    const intA = a * multiplier;  
    const intB = b * multiplier;  
    return (intA * intB) / (multiplier * multiplier);  
}

二、使用toFixed()方法

  1. 原理
    • toFixed()方法可以将浮点数四舍五入到指定的小数位数。
    • 需要注意的是,toFixed()返回的是一个字符串,需要使用parseFloat()将其转换回数字。
const result = parseFloat((0.1 + 0.2).toFixed(10));

三、使用第三方库

  1. 原理
    • 有一些第三方库专门用于处理浮点数计算精度问题,如decimal.jsbignumber.js等。
    • 这些库提供了一系列方法来进行高精度的浮点数计算。
  2. 示例
    • 使用decimal.js库:
import Decimal from 'decimal.js';  
const a = new Decimal(0.1);  
const b = new Decimal(0.2);  
const result = a.plus(b).toNumber();

四、使用BigDecimal类(Java)

  1. 原理
    • 在Java中,BigDecimal类可以精确表示小数,并且允许开发者控制舍入模式。
    • BigDecimal提供了丰富的数学运算方法,可以避免浮点数的精度问题。
import java.math.BigDecimal;  
public class BigDecimalExample {  
    public static void main(String[] args) {  
        BigDecimal a = new BigDecimal("0.1");  
        BigDecimal b = new BigDecimal("0.2");  
        BigDecimal sum = a.add(b);  
        System.out.println(sum); // 输出: 0.3  
    }  
}

五、引入误差容忍度(epsilon)

  1. 原理
    • 在比较两个浮点数时,由于它们在底层的二进制表示中可能存在微小差异,直接比较可能会导致错误的结果。
    • 引入误差容忍度(epsilon),即允许两个浮点数的差值在某个很小的范围内视为相等。
double a = 0.1 + 0.2;  
double b = 0.3;  
double epsilon = 1e-10; // 容忍度  
System.out.println(Math.abs(a - b) < epsilon); // 输出: true

六、使用整数表示货币值

  1. 原理
    • 在处理货币运算时,可以使用整数来表示货币值(如以分为单位表示货币值而不是以元),从而避免浮点数精度问题。
int priceInCents = 100; // 1.00元表示为100分  
int quantity = 3;  
int totalCostInCents = priceInCents * quantity;  
System.out.println("总价:" + (totalCostInCents / 100.0) + " 元"); // 输出: 3.00 元

综上所述,解决浮点数运算精度丢失问题的方法有多种,可以根据具体场景和需求选择合适的方法。在实际应用中,应尽量避免在需要高精度计算的场景中使用浮点数,而是采用整数或其他高精度数据类型进行计算。

超过Long整型的数据应该如何表示?

基本数值类型都有一个表达范围,如果超过这个范围就会有数值溢出的风险。

在 Java 中,64 位 long 整型是最大的整数类型。

long l = Long.MAX_VALUE;
System.out.println(l + 1); // -9223372036854775808
System.out.println(l + 1 == Long.MIN_VALUE); // true

BigInteger 内部使用 int[] 数组来存储任意大小的整形数据。

相对于常规整数类型的运算来说,BigInteger 运算的效率会相对较低。


原文地址:https://blog.csdn.net/LiuYuHao_/article/details/142992876

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!