装箱(Autoboxing)和拆箱(Unboxing)

装箱(Autoboxing)和拆箱(Unboxing)

wiki

  • Autoboxing and Unboxing
  • 深入剖析Java中的装箱和拆箱
  • Why do we use autoboxing and unboxing in Java?

学习目标

  • Why:为什么需要装箱和拆箱;
    • 代码更加简洁
    • 基本数据类型可以使用泛型
  • What:什么是装箱和拆箱;
  • How:装箱和拆箱是怎么实现的?
    • 装箱:包装类的valueOf()方法
    • 拆箱:包装类的***Value()方法
  • When:什么时候使用装箱和拆箱
  • Where:在什么地方会使用到装箱和拆箱
  • Other:装箱和拆箱的优点和缺点
    • 优点:代码简洁,可以使用泛型
    • 缺点:装箱需要创建新的对象,时间会比较长

装箱和拆箱的定义

  • 装箱(Autoboxing):基本数据类型(int,double等)转换为对应的包装类叫做装箱;
    • 基本数据类型作为参数传递给一个用对应包装类作为参数的方法时使用装箱;
    • 将基本数据类型赋值给对应的包装类;
  • 拆箱(Unboxing):包装类转换为对应的基本数据类型叫做拆箱;
    • 将包装类作为参数传递给一个用对应基本数据类型作为参数的方法时使用装箱;
    • 将包装类赋值给对应的基本数据类型;
Interger i = 10; //装箱
int num = i; //拆箱

基本数据类型和对应的包装类

Primitive Type Wrapper Class
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

为什么需要拆箱和装箱

  • 在泛型中,编译器会进行类型擦除,将泛型中的类型编译为Object;对应基础数据类型,无法使用泛型,比如创建列表,即不能出现List, List;通过装箱和拆箱,可以将int类型加入List中,其他类型类似

    public static void main(String[] args)
    {
        int i = 1;
        List list = new ArrayList();
        list.add(i);
    }
    

    编译后代码

    public static void main(String[] args) {
        int i = 1;
        ArrayList list = new ArrayList();
        list.add(Integer.valueOf((int)i));
    }
    

装箱和拆箱是怎么实现的?

public static void main(String[] args)
{
    Integer i = 10;

    int n = i;
}
  • 使用命令java -jar .\cfr_0_132.jar .\BoxingTest.class --sugarboxing false > BoxingTest.txt反编译上述代码

    public static void main(String[] args) {
            Integer i = Integer.valueOf((int)10);
            int n = i.intValue();
        }
    
  • 装箱是利用包装类的valueOf()方法;

  • 拆箱是利用包装类的***Value()方法;

包装类的valueOf()方法

public static void main(String[] args) {

    Integer i1 = 100;
    Integer i2 = 100;
    Integer i3 = 200;
    Integer i4 = 200;

    System.out.println(i1==i2); // true
    System.out.println(i3==i4); // false
}
  • 查看Integer.valueOf()方法

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
    • valueOf方法可以看出,如果数字在[-127,128]之间,不会新建Integer类,会直接返回缓存中已经创建的对象的引用;
public static void main(String[] args)
{
    Double d1 = 10.0;
    Double d2 = 10.0;

    System.out.println(d1 == d2); // false
}
  • 查看Double.valueOf()方法

    public Double(double value) {
        this.value = value;
    }
    
    • 由于double类型的值不是离散的,无法使用缓存保存;
  • Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的

    类型 包装类缓存范围
    Integer,Byte,Short,Long [-128,127]
    Character [0,127]
    Double,Float
  • Double、Float的valueOf方法的实现是类似的

when and where: 使用装箱和拆箱

public static void main(String[] args)
{
    Integer i1 = 1;
    Integer i2 = 2;
    Integer i3 = 3;
    System.out.println(i3 == (i1 + i2)); // true

    Long l1 = 3l;
    Long l2 = 2l;
    System.out.println(l1 == (i1 + i2)); // true
    System.out.println(l1.equals(i1 + i2)); // false
    System.out.println(l1.equals(i1 + l2)); // true
}

cfr编译后的代码

public static void main(String[] args) {
    Integer i1 = Integer.valueOf((int)1);
    Integer i2 = Integer.valueOf((int)2);
    Integer i3 = Integer.valueOf((int)3);
    System.out.println((boolean)(i3.intValue() == i1.intValue() + i2.intValue()));
    
    Long l1 = Long.valueOf((long)3L);
    Long l2 = Long.valueOf((long)2L);
    System.out.println((boolean)(l1.longValue() == (long)(i1.intValue() + i2.intValue())));
    System.out.println((boolean)l1.equals((Object)Integer.valueOf((int)(i1.intValue() + i2.intValue()))));
    System.out.println((boolean)l1.equals((Object)Long.valueOf((long)((long)i1.intValue() + l2.longValue()))));
}
  • 当 "=="运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象;而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程);
  • 包装器类型中equals()方法中并不会进行类型转换。

三目运算符中的装箱和拆箱

  • java1.7中的三目运算符
  • java1.8中的三目运算符
public static void main(String[] args)
{
    Map map =  new HashMap();
    Boolean b = (map!=null ? map.get("test") : false);
}
  • jdk1.7编译结果

    public static void main(String[] args) {
        HashMap map = new HashMap();
        Boolean b = Boolean.valueOf((boolean)(map != null ?((Boolean)map.get((Object)"test")).booleanValue() : false));
    }
    
    • 由于jdk1.7中,第二和第三操作数为基本类型和对象时,会将对象拆箱,所有上述代码执行时会报空指针的错误;
  • jdk1.8编译结果

    public static void main(String[] args) {
        HashMap map = new HashMap();
        Boolean b = map != null ? (Boolean)map.get((Object)"test") : Boolean.valueOf((boolean)false);
    }
    
    • 理解要点

      • 三目运算符第二个和第三个操作数的类型不同时返回的类型到底是什么?
      • 第二个和第三个操作数会进行什么操作?
    • 在java1.8中,操作数共有18中,基本数据类型和对应的包装类共16个,加上null和Object共18个,所有表达式返回的数据类型组合共有324种情况。

    • lub()的理解

      least upper bound - basically the closest superclass that they have in common; since null (type "the special null type") can be implicitly converted (widened) to any type, you can consider the special null type to be a "superclass" of any type (class) for the purposes of lub().

      public static void main(String[] args)
      {
          Map map =  new HashMap();
          Boolean flag = true;
          Integer i = 1;
          Comparable res = map!=null ? flag : i;
          System.out.println(res);
      }
      

      如果第二个操作数是Boolean,第三个操作数是Integer,则三目运算符的返回类型是lub(Boolean,Integer)

      // 编译后的结果
      public static void main(String[] args) {
          HashMap map = new HashMap();
          Boolean flag = Boolean.valueOf((boolean)true);
          Integer i = Integer.valueOf((int)1);
          Comparable res = (Comparable)(map != null ? (Comparable)flag : (Comparable)i);
          System.out.println((Object)res);
      }
      

      lub的作用是需找两个对象的最小共同父类或者父接口,通过查看BooleanInteger代码可以得到lub是Comparable接口

      public final class Boolean implements java.io.Serializable,
                                            Comparable{}
      
      public final class Integer extends Number implements Comparable {}
      
    • bnp(..): bnp(..) means to apply binary numeric promotion.

      When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

      1. If any operand is of a reference type, it is subjected to unboxing conversion (§5.1.8).
      2. Widening primitive conversion (§5.1.2) is applied to convert either or both operands as specified by the following rules:
        • If either operand is of type double, the other is converted to double.
        • Otherwise, if either operand is of type float, the other is converted to float.
        • Otherwise, if either operand is of type long, the other is converted to long.
        • Otherwise, both operands are converted to type int.

      Binary numeric promotion is performed on the operands of certain operators:

    • T | bnp(..):The form "T | bnp(..)" is used where one operand is a constant expression of type intand may be representable in type T, where binary numeric promotion is used if the operand is not representable in type T.

      如果一个操作数是int的常量(即1,2,...,不是int变量),并且int常量的范围在类型T的范围内,则返回类型T,否则返回bnp(int,T)

      以第二个操作数为short,第三个操作数为int为例,返回类型为short| bnp(short,int)

      public static void main(String[] args)
      {
          // 以操作数二为short类型,操作三为int类型为例
          Map map =  new HashMap();
          short b1 = 1;
          int i1 = 2;
          short test = (map!=null ? b1 : 32767); // 编译不报错, short类型的范围是[-32768,32767],所有32767可以转化为short类型
          short test1 = (map!=null ? b1 : 32768); //编译报错,32768不可以转化为short类型
          short test2 = (map!=null ? b1 : i1); //编译报错,i1是符号引用,在编译器无法确定具体的值,无法确定是否可以转化为short类型。
      }
      

你可能感兴趣的:(装箱(Autoboxing)和拆箱(Unboxing))