Java 包装类型


1 了解包装类型

1.1 介绍

在 Java 中, 数据类型总共可以分为2大类 : 基础数据类型引用数据类型
基础数据类型并不支持面向对象编程, 因为基础数据类型不具备 “对象” 的特性 – 携带属性和方法。
所以 Java 为 8 种基础数据类型提供了对应的类, 他们就是包装类, 侧面的将基础数据类型变为类, 符合面向对象编程。
之所以没有一开始就是提供包装类, 而是使用基础数据类型, 个人认为只是为了迎合人类根深蒂固的习惯, 并的确能简单、有效地进行常规数据处理。
总的来说:包装类就是基础数据类型在面向对象中的体现。

1.2 包装类型的种类

基本数据类型 对应的包装类
byte (1 字节) java.lang.Byte
short (2 字节) java.lang.Short
int (4 字节) java.lang.Integer
long (8 字节) java.lang.Long
char (2 字节) java.lang.Character
float (4 字节) java.lang.Float
double (8 字节) java.lang.Double
boolean (未定) java.lang.Boolean

在 Java boolean 的大小, 有三种说法 1/8字节, 1字节, 4个字节

1.3 Java 代码中的继承关系

Alt '包装类的继承关系'

可以看出他们都实现了 Comparable 接口, 支持进行比较。
同时数字类型的包装类继承了 Number 抽象类, 支持转为其他基础数据类型。

2 装箱/拆箱

在 Java 中, 我们可以无缝的在包装类型和基础数据类型之间切换, 之间是有一个装换过程的, 这个过程就是 装箱/拆箱

// jdk5 之前, 我们如果需要将一个基础数据和包装类型切换, 需要手动处理

int m = 500;
// 手动装箱
Integer obj = new Integer(m);  
// 手动拆箱
int n = obj.intValue();

// jdk5 后支持自动装箱和拆箱

// 自动装箱, 实质是调用 对应包装类.valueOf() 方法实现的
Integer obj = m;
// 自动拆箱, 实质是调用 对应包装类.xxxValue()方法实现的, 其中的xxx 就是对应的数据基础数据类型
int n = obj;

3 包装类型的缓存问题

从装箱和拆箱, 我们可以知道, 把一个基础数据类型转为对应的包装类型, 有 2 种方式

// 主动装箱
Integer num = new Integer(1);
// 自动装箱
Integer num = 1;

这2种方式, 虽然都能达到将一个基础数据类型转为包装类型, 但是他们内部的实现, 还是有很大的区别, 这个区别很多时候, 能被用到很多面试题上。

手动装箱

public class Integer {

  // 内部还是通过一个基础数据类型存储我们的数据值的
  private final int value;

  public Integer(int value) {
    this.value = value;
  }
}

从 Integer 的源码我们可以知道, 我们每次手动装箱, 都会创建一个新的 Integer 对象。

自动装箱

public class Integer {

  /** 自动装箱 会调用到这个方法 */
  public static Integer valueOf(int i) {
      // 从这3行的 逻辑和 变量名 我们可以推测出
      // 如果我们 装箱的值  Integer缓存的下限 <= i <= Integer缓存的上限, 那么他会从 Interger的缓存里面取出对应的包装类, // 否则就重新创建一个新的
      // 那么 Integer的上下限分别是多少呢, 这个就需要看到 IntegerCache的代码了
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
  }

  /** Integer 内部的缓存 */
  private static class IntegerCache {

      static final int low = -128;
      static final int high;
      static final Integer cache[];

      private IntegerCache() {}

      static {
        int h = 127;
        // 读取启动命令行 里面的 java.lang.Integer.IntegerCache.high 配置的值
        // 可以通过控制应用启动时, Integer 内部缓存池的大小
        String integerCacheHighPropValue =  sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

        if (integerCacheHighPropValue != null) {
          try {
              int i = parseInt(integerCacheHighPropValue);
              // 从配置的值和127中取大的那一个
              i = Math.max(i, 127);

              // 控制 h 的值 小于等于 Integer.MAX_VALUE - 129
              // 这里说一下, h 和 high 都是 int 声明的, 所以最大值为 Integer.MAX_VALUE
              // 同时为了避免下面的(high - low) + 1 = high + 128 + 1 经过运算后超过了 int 的最大值, 变为负数, 所以此处才需要控制 h 的最大上限为  Integer.MAX_VALUE - 129
              h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
          } catch( NumberFormatException nfe) {
          }
        }

        high = h;
        cache = new Integer[(high - low) + 1];
        int j = low;
        // 创建 一个 缓存数组 存放 -128 到 h
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
        assert IntegerCache.high >= 127;
      }
  }
}
  1. 从 Integer 的源码 可以知道, Integer 内部维护了一个Integer的缓存, 这个缓存默认为 -128 到 127 之间, 所以我们通过自动装箱创建的 Integer 并且值在这个区间内, 都是从缓存里面获取到的
  2. 同时我们可以在我们程序启动时, 指定参数 java.lang.Integer.IntegerCache.high = 你想要设置的缓存上限, 修改我们的 Integer 缓存池的上限
  3. 几个数值包装类型, 除了有精度的 Float 和 Double 外都有对应的缓存, 都是 [-128, 127] 之间, 但是只要 Integer 支持配置

4 面试题

// 第一, 比较的是否为同一个对象
Integer num01 = new Integer(1);
Integer num02 = 1;
int num03 = 1;
// false  2 个对象 == 比较的是 2 个对象的内存地址是否一样
System.out.println(num01 == num02);  
// ture  基础数据类型 和 包装类型 比较时, 之间比较数值大小
System.out.println(num02 == num03);
 // true
System.out.println(num01 == num03);


// 第二, 比较的是 缓存, 自动装箱 数值默认在 [-128, 127] 之间都是从缓存取对象, Integer num04 = 2;
Integer num05 = 2;
// true
System.out.println(num04 == num05);

Integer num06 = 128;
Integer num07 = 128;
// false
System.out.println(num06 == num07);


// 第三, 包装类型进行运算后, 会被转为 基础数据类型
Integer  num08 = 1;
Integer  num09 = 2;
Long num10 = 3L;
// true, num08 + num09 变成了数值 3, 此处比较的是数值大小
System.out.println(num10 == (num08 + num09));
// false, equals 比较的是 2 个对象是否一样, 基础数据类型是无法比较的, 所以此处自动装箱了
System.out.println(num10.equals(num08 + num09));

// 第四:低精度和高精度运算, 低精度换转为高精度, 再进行运算
Integer  num11 = 1;
Long  num12 = 2L;
Long num13 = 3L;
// 这里运算后 结果为 3L, 自动装箱为 3, 同时有缓存, 所以 2 个一样
System.out.println(num13.equals(num11 + num12));

// 第五: Double 和 Float 的自动装箱没有缓存
Double num14 = 1D;
Double num15 = 1D;
System.out.println(num14 == num15);

5 总结

包装类型的知识就这些吧, 在判断 2 个对象是否一样时, 基本可以从下面入手

  1. 比较的是数值, 还是对象, 存在自动装箱和自动拆箱
  2. 比较的是包装类型, 是否自动装箱了, 值超过了缓存
  3. 包装类型进行了运算, 默认运算后的结果是基础数据类型
  4. 低精度和高精度运算, 低精度换转为高精度
  5. Double 和 Float 的自动装箱没有缓存

  目录