JAVA-详解内部类

By timebusker on June 23, 2015

自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。 自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。

装箱/拆箱

Java为每种基本数据类型都提供了对应的包装器类型,但在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

# JDK 1.5之前
Integer i = new Integer(10);

# 从Java SE5开始就提供了自动装箱的特性
Integer i = 10;

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

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

下表是基本数据类型对应的包装器类型: | 基础数据类型 | 包装类 |
| :——| :—— |
| int(4字节) | Integer |
| byte(1字节) | Byte |
| short(2字节) | Short |
| long(8字节) | Long |
| float(4字节) | Float |
| double(8字节) | Double |
| char(2字节) | Character |
| boolean(4字节/1字节) | Boolean |

Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。 在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值, 在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组, 每个元素boolean元素占8位”。这样我们可以得出boolean类型占了单独使用4个字节,在数组中又是1个字节

装箱/拆箱实现

# 测试代码
public class Main {
    public static void main(String[] args) {
        Integer i = 10;
        int n = i;
    }
}

反编译结果: image

从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。 而在拆箱的时候自动调用的是Integer的intValue方法。如Double、Character也类似。

装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。

自动装箱的弊端

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。

Integer sum = 0;
 for(int i=1000; i<5000; i++){
   sum+=i;
}

# 上面代码内部实际等价于
sum = sum.intValue() + i;
Integer sum = new Integer(result);

要注意的事项

  • 对象相等比较 ”==“可以用于原始值进行比较,也可以用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象代表的值, 而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals()方法。

  • 容易混乱的对象和原始数据值 当我们在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue, 会抛出NullPointerException。

  • 缓存的对象 在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围, 并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。

  • 生成无用对象增加GC压力 自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。

Java包装类

虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备 “对象”的特性——不携带属性、没有方法可调用。 沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。

这种借助于非面向对象技术的做法有时也会带来不便,比如引用类型数据均继承了 Object 类的特性,要转换为 String 类型 (经常有这种需要)时只要简单调用 Object 类中定义的toString()即可,而基本数据类型转换为 String 类型则要麻烦得多。 为解决此类问题 ,Java为每种基本数据类型分别设计了对应的类,称之为包装类(Wrapper Classes),也有教材称为外覆类或数据类型类。

关于自动装箱和拆箱常见面试

  • Integer
# 代码输出结果?
public class Main {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

# 正确答案:
# true
# false
# 输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。

# 如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}
  • Double
# 代码输出结果?
public class Main {
    public static void main(String[] args) {
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

# 正确答案:
# false
# false

# 查看Double类的valueOf的实现
public static Double valueOf(double d) {
    return new Double(d);
}

注意:

Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

Double、Float的valueOf方法的实现是类似的。

  • Boolean
# 代码输出结果?
public class Main {
    public static void main(String[] args) {
 
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
 
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

# 正确答案:
# true
# true

# 查看Boolean类的valueOf的实现
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}
  • Integer i = new Integer(1)和Integer i =1;的区别

1)第一种方式不会触发自动装箱的过程;而第二种方式会触发; 2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。