天天看点

面试 | 自动装箱实现原理?类型转换实现原理?

1 什么是自动装箱?什么是自动拆箱?

自动装箱和自动拆箱的示例:

public class Test {
	public static void main(String[] args) {
		ArrayList<Integer> intList = new ArrayList<Integer>();
		intList.add(100);
		intList.add(150);
		intList.add(200);
		
		int a = intList.get(0);
		int b = intList.get(1);
		int c = intList.get(2);
		
		System.out.println(a + ", " + b + ", " + c);
	}
}
           

intList列表中只能放Integer类型的对象,而代码中把int基本数据类型放到intList列表中,那么实际上编译器会把int基本数据类型转换城Integer类型(包装类)的对象,那么编译器的这个操作过程就是装箱的过程;而从intList列表中拿取的也是Integer类型的对象,而代码中把Integer类型的对象赋值给int基本数据类型,那么编译器会把Integer类型的对象转换成int基本数据类型,那么编译器的这个操作过程就是拆箱的过程;另外,由于装箱和拆箱都是编译器自动完成,不需要在代码中显式的说明,故称为自动装箱和自动拆箱。基本数据类型byte,short,char,int,long,float,double和boolean对应的包装类为Byte,Short,Character,Integer,Long,Float,Double和Boolean。

2 如何实现自动装箱和自动拆箱?

自动装箱和自动拆箱由编译器自动完成。当编译器检测到需要把基本类型转换成包装类型时,编译器自动调用包装类型的valueOf()方法实现自动装箱;当编译器检测到需要把包装类型转换成基本类型时,编译器自动调用包装类型的xxValue()方法实现自动拆箱。

八大基本数据类型自动装箱的原理代码:

// Byte
public static Byte valueOf(byte b) {
        final int offset = 128;
        return ByteCache.cache[(int)b + offset];
    }

// Short
public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }

// Character
public static Character valueOf(char c) {
        if (c <= 127) { // must cache
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }

// Ingeger
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    } 

// Long
public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }

// Double
public static Double valueOf(double d) {
        return new Double(d);
    }

// Float
public static Float valueOf(float f) {
        return new Float(f);
    }

// Boolean
public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
           

从装箱的源代码可知,当基本类型为byte,short,char,int和long类型时,首先根据赋值的数值大小判断是否在缓存区,否则直接new一个对应的包装类型对象;注意只有double和float会直接new一个包装类。(因为基本数据类型byte,char和int用的较多,所以Java在包装类初始化的时候在内存中缓存一定范围的值,避免生成大量的对象。缓存范围为[-128,127],正好是short基本类型的范围)。

八大基本数据类型自动拆箱的原理代码:

public xxx xxxValue() {
        return value;
    }
           
3 自动装箱和自动拆箱有什么作用?有什么弊端?

JDK1.5之前,编译器还没有实现自动装箱和自动拆箱,需要显式的编写装箱和拆箱的过程;如下面的代码:

intList.add(Integer.valueOf(100));
intList.add(Integer.valueOf(150));
intList.add(Integer.valueOf(200));
		
int _a = intList.get(0).intValue();
int _b = intList.get(1).intValue();
int _c = intList.get(2).intValue();
           

可以看出代码很繁杂,JDK1.5之后的valueOf()和intValue()由编译器自动完成,可以简化代码。

自动装箱和自动拆箱给我们带来便利的同时,也带来了弊端,主要体现以下几点:

  • 包装类之间的比较;
public static void method2() {
		Integer a = 100;
		int _a = 100;
		Integer _a1 = new Integer(100);
		System.out.println("a == _a : " + (a == _a));     // true
		System.out.println("a == _a1 : " + (a == _a1));   // false
		System.out.println("_a == _a1 : " + (_a == _a1)); //true
		
		Integer b = 200;
		int _b = 200;
		Integer _b1 = new Integer(200);
		System.out.println("b == _b : " + (b == _b));     // false
		System.out.println("b == _b1 : " + (b == _b1));   // false
		System.out.println("_b == _b1 : " + (_b == _b1)); //false
	}
           

==可以用于对象与对象之间的比较,比较的是对象的内存地址是否相等,即是否是同一个对象,比较的过程没有自动拆箱;也可以用于基本数据类型之间的比较,比较的是字面值;所以,对于对象之间的比较应该用equals()方法。

  • 稍不留神,容易创建额外对象
Integer result = 0;
for (int i = 127; i < 1127; i++) {
    result += i;
}
           

上面的result += i 实际会先进行自动拆箱操作,然后进行自动装箱操作。即

result = result.intValue() + i;
result = result.valueOf(result);
           

自动装箱实际会创建1000各无用的Integer对象,会给内存造成极大的浪费;所以编写代码中避免使用不必要的自动装箱。