JavaSE5️⃣核心类 - 包装类型 & 任意精度类型

1、包装类型

Java 数据类型

  1. 基本类型
    1. byte,short,int,long
    2. float,double
    3. char,boolean
  2. 引用类型:八种基本类型之外的所有数据类型。
    1. 包含所有 class 和 interface 类型。
    2. 可以赋值为 null,表示空(不存在)。

1.1、包装类

包装类(Wrapper Class)

  1. 目的使基本类型拥有对象(引用类型)的性质

    • 很多地方(如集合)需要使用引用类型,而不是基本类型。
    • 相比基本类型,对象可以定义更复杂的操作。
  2. 类库java.lang。定义了基本类型对应的包装类。

    基本类型 包装类型
    byte Byte
    short Short
    int Integer
    long Long
    float Float
    double Double
    char Character
    boolean Boolean

1.1.1、不可变类

String 相同,包装类的声明、变量值均由 final 修饰。

对象实例化后,无法改变。

以 Integer 为例

public final class Integer {
    private final int value;
}

1.1.2、比较变量值

  1. 基本类型:使用 == 直接比较。

  2. 包装类型(属于引用类型):

    1. ==:比较地址。

    2. equals():先判断 obj 是否为当前包装类型,再比较数值

      // e.g. Integer的equals()
      public boolean equals(Object obj) {
          if (obj instanceof Integer) {
              return value == ((Integer)obj).intValue();
          }
          return false;
      }
      

1.1.3、获取变量值

数值类型的包装类均继承自 Number 抽象类。

定义了一系列获取数值的方法——xxxValue()

public abstract class Number implements java.io.Serializable {

    public abstract int intValue();

    public abstract long longValue();

    public abstract float floatValue();

    public abstract double doubleValue();

    public byte byteValue() {
        return (byte)intValue();
    }

    public short shortValue() {
        return (short)intValue();
    }

    private static final long serialVersionUID = -8742448824652078965L;
}

示例

Double num = 8.88;

System.out.println(num.byteValue());	// 8
System.out.println(num.intValue());		// 8
System.out.println(num.longValue());	// 8
System.out.println(num.floatValue());	// 8.88
System.out.println(num.doubleValue());	// 8.88

1.2、拆装箱

1.2.1、含义

  1. 装箱:基本类型 → 包装类型,使用静态方法 包装类.valueOf(参数)

  2. 拆箱:包装类型 → 基本类型。使用实例方法 包装类实例.xxxValue()

    int n = 100;
    
    Integer num = Integer.valueOf(int);	// 装箱
    int num1 = num.intValue();	// 拆箱
    

1.2.2、自动拆装箱

Java 1.5

编译期优化(语法糖),用于简化代码。

本质:编译器自动调用 valueOf(基本类型)xxxValue() 方法。

  1. 自动装箱(Auto Boxing):

    int num = 7;
    
    Integer num1 = Integer.valueOf(num);
    Integer num2 = num;	// 自动装箱
    
  2. 自动拆箱(Auto Unboxing):

    Integer num = 100;	// 自动装箱
    
    int num1 = num.intValue();
    int num2 = num;	// 自动拆箱
    

缺点

  1. 耗费资源,影响性能
    • 对于使用者而言,基本类型和包装类型可以直接赋值,无需考虑其区别。
    • 对于 JVM 底层,字节码文件严格区分基本类型和引用类型
    • 因此,相比对应类型的直接赋值,频繁的拆装箱操作会消耗性能。
  2. 自动拆箱可能报错 NPE,因为待拆箱变量可能为 null。
  3. 包装对象实例无法使用 == 比较(Integer 缓存区间内的对象除外)。

思考:如何验证自动拆装箱的触发时机?

:反编译,查看字节码文件。

1.3、无符号整型转换

涉及知识:👉 计算机中的数值表示(无符号、有符号)

  • Java 没有无符号整型(Unsigned)的基本数据类型。
  • 可通过包装类型的静态方法,实现无符号整型的转换。

方法签名

  1. Byte

    public static int toUnsignedInt(byte x) {}
    public static long toUnsignedLong(byte x) {}
    
  2. Short

    public static int toUnsignedInt(short x) {}
    public static long toUnsignedLong(short x) {}
    
  3. Integer

    public static long toUnsignedLong(int x) {}
    
    // 转换为无符号整数的字符串表示
    public static String toUnsignedString(int i) {}
    public static String toUnsignedString(int i, int radix) {}
    
  4. Long

    // 转换为无符号整数的字符串表示
    public static String toUnsignedString(long i) {}
    public static String toUnsignedString(long i, int radix) {}
    

示例:将 byte 转换为无符号整数 int。

Hint:byte 占 2 个字节

byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
  1. 负数

    • 对于有符号整数,-1 的原码是 1000 0001,推导出补码是 1111 1111

    • 对于无符号整数,1111 1111 对应的十进制数是 255。

      1 000 0001	// 原码
      1 111 1110	// 反码 = 原码取反(符号位除外)
      1 111 1111	// 补码 = 反码 + 1
      
  2. 正数

    • 对于有符号整数,127 的三码是 0111 1111

    • 对于无符号整数,0111 1111 对应的十进制数是 127。

      0111 1111				// byte
      0000 0000 0111 1111		// int
      

1.4、String 转换

包装类型和 String 可以相互转换

见 👉 核心类 - String 相关类

2、Integer 缓存优化

Java 1.5

  1. 含义:提前创建缓存区间内的 Integer 实例,实现对象实例的复用。
  2. 默认区间[-128, 127]
  3. 作用:节省内存,提高性能。

2.1、创建实例

创建 Integer 的两种方式

  • new:始终在堆内存中创建新的对象。
  • valueOf(参数):提前创建对象,缓存优化(👍)。

2.1.1、new

始终创建新的实例(堆内存)。

  1. 方法签名

    public Integer(int value) {
        this.value = value;
    }
    
    // 重载:解析字符串
    public Integer(String s) throws NumberFormatException {
        this.value = parseInt(s, 10);
    }
    
  2. 示例:不同的实例,数值相同但地址不同。

    Integer num1 = new Integer(100);
    Integer num2 = new Integer(100);
    
    System.out.println(num1.equals(num2));	// true
    System.out.println(num1 == num2);		// false
    

2.1.2、valueOf(参数)

相当于静态工厂方法,利用了缓存优化。

👉 工厂模式

  1. 方法源码

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            // 缓存区间:返回已创建的对象引用
            return IntegerCache.cache[i + (-IntegerCache.low)];
        // 非缓存区间:新建对象并返回
        return new Integer(i);
    }
    
    // 重载方法:字符串+指定进制
    public static Integer valueOf(String s, int radix)
        throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }
    
    // 重载方法:字符串+10进制
    public static Integer valueOf(String s)
        throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }
    
  2. 示例缓存区间内的 Integer 对象,值相同则 == 比较为 true

    Integer num1 = Integer.valueOf(100);
    Integer num2 = Integer.valueOf("100", 10);
    Integer num3 = Integer.valueOf("100");
    
    System.out.println(num1 == num2);	// true
    System.out.println(num1 == num3);	// true
    

2.2、源码

Integer 类中,有关缓存优化的源码。

① 缓存区间

默认 [-128, 127]

static final int low = -128;	// 缓存下限
static final int high;			// 缓存上限
static final Integer cache[];	// 缓存数组

② 缓存上限

通过静态代码块 static{} 进行初始化。

  1. 默认上限h = 127

  2. 系统变量

    1. 相关 JVM 参数-XX:AutoBoxCacheMax
      • 在 VM 初始化时会被设置并保存到 sun.misc.VM 类中。
      • 可通过 java.lang.Integer.IntegerCache.high 获取到。
    2. 最大上限:Java 规定缓存区间不超过 Integer.MAX_VALUE
  3. 确定上限:将最终的 h 赋值给 high,作为缓存区间上限。

    // 默认上限
    int h = 127;
    
    // 系统变量
    String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    
    if (integerCacheHighPropValue != null) {
        try {
            int i = parseInt(integerCacheHighPropValue);
            // 取最大值
            i = Math.max(i, 127);
            // 取最小值:保证不超过最大上限
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        } catch( NumberFormatException nfe) {
        }
    }
    
    // 赋值上限
    high = h;
    

上限的可能取值

  • 未设置系统变量:127
  • 设置了系统变量
    • 值 <= 127:127
    • 127 < 值 < 最大上限:变量值
    • > 最大上限:最大上限

③ 初始化缓存数组

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);

assert IntegerCache.high >= 127;

3、任意精度类型

Java 中,CPU 原生提供的数值范围有限

可通过 CPU 指令直接运算,速度快

  • 最大整数long 64 位。
  • 最大浮点数double 64 位。

软件模拟,可表示任意精度的数值,

缺点是运算速度慢

3.1、API - BigXxx

java.math 包提供了软件模拟的任意精度数值。

  1. 类型
    1. BigInteger:任意精度整数(较少使用)。
    2. BigDecimal:任意精度的有符号十进制数较多使用)。
  2. 特点
    1. Number 子类:实现了 xxxValue(),可转换为任意数值类型。
    2. 不可变:运算不会修改原值,而是创建并返回新的对象(类似 String)。

底层实现

  1. BigInteger:使用 int[] 数组表示任意精度整数

    final int[] mag;
    
  2. BigDecimal:使用 BigInteger 表示整数部分,使用 int 类型的 scale 表示小数位数

    private final BigInteger intVal;
    private final int scale;
    

3.2、通用操作

3.2.1、创建实例

  1. new

    BigInteger bi = new BigInteger(参数);
    BigDecimal bd = new BigDecimal(参数);
    
  2. valueOf(参数):推荐使用。

    BigInteger bi = BigInteger.valueOf(参数);
    BigDecimal bd = BigDecimal.valueOf(参数);
    

3.2.2、常用运算

不可变:运算不会修改原值,而是创建并返回新的对象

  1. 比较大小:compareTo()
  2. 基础运算
    1. :add()
    2. :substract()
    3. :multiply()
    4. :divide()
      1. BigInteger:仅保留整数部分。
      2. BigDecimal:可指定末位小数的舍入模式。
    5. 取余:remainder()
  3. 其它
    1. 最大公约数:gcd()
    2. 绝对值:abs()
    3. 取反:negate()
    4. 幂运算:pow()

3.2.3、类型转换

实现了 xxxValue(),可转换为任意数值类型。

两种形式:当 BigInteger 值超出目标数值类型的表示范围时,采取不同的溢出措施。

xxxValue() xxxValueExact()
来历 实现 Number 类的方法 自身定义的方法
溢出时操作 丢失高位信息 ArithmeticException 异常
特点 不报错,但结果未必精确 要么报错,要么精确

示例:将一个超出 long 范围的 BigInteger,转换成 long。

BigInteger bi1 = BigInteger.valueOf(Long.MAX_VALUE);
BigInteger bi2 = BigInteger.valueOf(1);
BigInteger bi = bi1.add(bi2);   // Long.MAX_VALUE + 1

System.out.println(Long.MAX_VALUE);	//  9223372036854775807
System.out.println(bi);				//  9223372036854775808
System.out.println(bi.longValue());	// -9223372036854775808

System.out.println(bi.longValueExact());	// java.lang.ArithmeticException: BigInteger out of long range

分析:各个值所对应的二进制数。

最高位 高位部分 中间部分 低位部分 说明
Long.MAX_VALUE 0111 1111 都是 1 1111 1111 Long 最大值
bi 0 1000 0000 都是 0 0000 0000 Long 最大值 + 1
bi.longValue() 丢弃 1000 0000 都是 0 0000 0000 溢出(丢弃高位)
bi.longValueExact() - - - - 溢出(抛出异常)

3.3、BigDecimal 说明

3.3.1、使用场景

要求浮点数精确运算的场景。

  1. 基本数据类型中的浮点数(float 和 double)无法精确表示,其运算结果也是如此。
  2. 因此,基本类型的浮点数只适用于科学计算或工程计算(对精度要求不高)。
  3. 在商业运算中,使用 BigDecimal 可保证精确

3.3.2、舍入模式

舍入模式(Rounding Mode):

  1. 说明:当 BigDecimal 存在多位小数时,可以指定策略来进行舍入。
  2. 使用时机:进行以下操作时,可指定要保留的位数和舍入模式。
    1. 手动舍入setScale()
    2. 除法舍入divide()

八大模式

假设保留 n 位小数,根据第 n + 1 位执行舍入策略。

模式 含义 舍入策略(根据第 n+1 位) 说明
0 UP 进位 非零则进位
1 DOWN 截断 直接截断 不受第 n + 1 位影响
2 CEILING 向上取值 正数 UP,负数 DOWN
3 FLOOR 向下取值 与 CEILING 相反 不会变大
4 HALF_UP 四舍五入 >= 5 进位
5 HALF_DOWN 五舍六入 >5 进位
6 HALF_EVEN - 第 n 位是奇数则 HALF_UP 处理第 n + 1 位,偶数则 HALF_DOWN 不是根据第 n + 1 位,而是根据第 n 位
7 UNNECESSARY 无需舍入 断言当前操作具有精确结果,无需舍入 若指定此模式且产生不精确结果,抛 ArithmeticException 异常

3.3.3、scale

scale:表示小数位数。

  1. scale():获取小数位数。

    BigDecimal d1 = new BigDecimal("123");
    BigDecimal d2 = new BigDecimal("123.4");
    BigDecimal d3 = new BigDecimal("123.45");
    
    System.out.println(d1.scale()); // 0
    System.out.println(d2.scale()); // 1
    System.out.println(d3.scale()); // 2
    
  2. setScale():设置小数位数,指定位数和舍入模式。

    BigDecimal bd = new BigDecimal("123.45");
    
    int newScale = 1;
    
    // 省略System.out.println();
    bd.setScale(newScale, RoundingMode.UP);         // 123.5(进位)
    bd.setScale(newScale, RoundingMode.DOWN);       // 123.4(截断)
    bd.setScale(newScale, RoundingMode.CEILING);    // 123.5(UP)
    bd.setScale(newScale, RoundingMode.FLOOR);      // 123.4(DOWN)
    bd.setScale(newScale, RoundingMode.HALF_UP);    // 123.5(五入)
    bd.setScale(newScale, RoundingMode.HALF_DOWN);  // 123.4(五舍)
    bd.setScale(newScale, RoundingMode.HALF_EVEN);  // 123.4(4是偶数,HALF_DOWN)
    bd.setScale(newScale, RoundingMode.UNNECESSARY);// 不精确,抛异常
    

3.3.4、除法

divide() divideAndRemainder()
含义 精确除法 整除并取余
运算结果 精确的浮点数 数组,元素依次为商、余数
说明 必须指定舍入模式,否则除不尽时会抛 ArithmeticException 异常 商一定是整数,余数是整数或浮点数

示例

  1. divide

    BigDecimal d1 = new BigDecimal(10);
    BigDecimal d2 = new BigDecimal(3);
    
    System.out.println(d1.divide(d2, 2, RoundingMode.HALF_UP));	// 3.33
    System.out.println(d1.divide(d2));	// java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
    
  2. divideAndRemainder

    // 余数是整数
    BigDecimal d1 = new BigDecimal("10");
    BigDecimal d2 = new BigDecimal("3");
    BigDecimal[] result = d1.divideAndRemainder(d2);
    
    System.out.println(Arrays.toString(result));	// [3, 1]
    
    // 余数是浮点数
    d1 = new BigDecimal("10.1");
    d2 = new BigDecimal("3");
    result = d1.divideAndRemainder(d2);
    
    System.out.println(Arrays.toString(result));	// [3.0, 1.1]
    

3.3.5、比较

若要比较数值相同,必须使用 compareTo()

== equals() compareTo()
比较内容 实例地址 值、scale
返回值 地址相同返回 true 值和 scale 相同返回 true 两个数的差值
实用性 几乎不用 较少使用 常用(👍)

示例

BigDecimal d1 = new BigDecimal("10");
BigDecimal d2 = new BigDecimal("10.0");

System.out.println(d1 == d2);	        // false:不同实例
System.out.println(d1.equals(d2));	    // false:小数位不同
System.out.println(d1.compareTo(d2));	// 0:数值相同
posted @ 2023-02-14 22:44  Jaywee  阅读(21)  评论(0编辑  收藏  举报

👇