Java中的泛型和装箱和拆箱

对于泛型和基本数据类型的装箱和拆箱大家都很了解。

我就简单说一下。

    1、泛型是JDK1.5的一项新增特性,它的本质是参数化类型的应用,也就是说所操作的数据类型被指定为一个参数。这种参数可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

    Java中的泛型只存在于程序源码中,在编译的字节码文件中,就已经替换为原来的原生类型,并且在相应的地方插入了强制转换。因此,对于运行期的Java语言,ArrayList<Integer>和ArrayList<String>就是同一个类。

    我们看一段简单的Java泛型的例子。

public static void main(String[] args) {
    Map<String,String> map = new HashMap<String,String>();

    map.put("hello","111");

    map.put("how are you?","222");

    System.out.println(map.get("hello"));

    System.out.println(map.get("how are you?"));
}

    把这段代码编译成Class文件,然后经过字节码反编译工具进行反编译后,将会发现泛型都不见了

public static void main(String[] args) {
    Map map = new HashMap();

    map.put("hello","111");

    map.put("how are you?","222");

    System.out.println(map.get("hello"));

    System.out.println(map.get("how are you?"));
}

    这就是泛型在编译期的擦除。泛型擦除只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。

   相信你看上面的内容后,就知道下面代码的输出结果。

public static void main(String[] args) {
    Class c1 = new ArrayList<Integer>().getClass();
    Class c2 = new ArrayList<String>().getClass();
    System.out.println(c1==c2);
}

    没错,就是true。因为在编译期生成的只有ArrayList类,而它的的Class文件必然只有一个,所以返回true。

扫描二维码关注公众号,回复: 157355 查看本文章

    当然,泛型擦除也有不足的地方,就比如当泛型遇上重载

public void f(List<String> list){
    System.out.println(list);
}

public void f(List<Integer> list){
    System.out.println(list);
}
    这样的代码通过不了编译,因为编译期将泛型擦除后,它们的参数都是ArrayList,导致两个方法的参数签名一模一样,无法解决歧义。

    2、自动装箱、拆箱

        什么是装箱和拆箱?

            大家都熟悉基本类型数据和包装类型数据吧。

            在JavaSE 1.5提供了自动装箱和拆箱,比如我们要创建一个数值为10的Integer对象,可以这样

            Integer i = 10;

            这便是自动装箱,在这个过程中会自动根据数值创建Integer对象。

            而int j = i, 便是自动拆箱,就是将包装类型转换为基本数据类型。

        装箱和拆箱的实现

            看一段代码

            

public static void main(String[] args) {
    Integer i = 10;
    int j = i;
    System.out.println(j);
}
                反编译之后    


        可以看到,给对象i赋值时,使用了Integer.valueOf方法,而在给变量j赋值时使用了Integer.intValue方法。

    在装箱的过程中是有几个坑要注意的,比如在下面的代码中可以看到

public static void main(String[] args) {
    Integer i = 100;
    Integer j = 100;
    Integer m = 200;
    Integer n = 200;
    System.out.println(i==j);
    System.out.println(m==n);
}
      输出为true和false,为什么呢,我们可以进入源码看

        

    它是有一个缓冲池,在-128到127之间,这之间的数并没有直接创建新的Integer对象,而是直接从缓存池中拿的。所以他们是相同的引用,用==比较时都是返回true,但是这些数之外的就是新创建的Integer对象,不可能是相同的。

    但是Double就不一样的,看代码

public static void main(String[] args) {
    Double i = 100.0;
    Double j = 100.0;
    Double m = 200.0;
    Double n = 200.0;
    System.out.println(i==j);
    System.out.println(m==n);
}

    它的输出却为两个false。

    还有你能说出Integer i = new Integer(x)和Integer i = x的区别吗?

        第一种不会触发自动装箱,而第二种会触发自动装箱

        第二种在一般情况下的执行效率和空间占用效率比第一种优

    最后再出一个题考考吧 

public static void main(String[] args) {
    Integer a =1;
    Integer b =2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;
    System.out.println(c==d);
    System.out.println(e==f);
    System.out.println(c==(a+b));
    System.out.println(c.equals(a+b));
    System.out.println(g==(a+b));
    System.out.println(g.equals(a+b));
}

        第一个和第二个没什么说的,第三个用到了关系操作符+,所以触发了自动拆箱,因此比较的是值的大小,返回true;

        第四个用到了操作符+和equals,所以先拆箱后装箱,比较的也是值的大小,所以返回true;第五个用到了操作符+,进行

        自动拆箱后,比较的是值本身,返回true。最后一个用到了操作符+和equals方法,先拆箱后装箱,不过拆箱的时候用的是intValue方法,返回为int,装箱之后也是Integer,而Long的equals方法只能和Long进行比较,其余都返回false。

        

猜你喜欢

转载自blog.csdn.net/yanghan1222/article/details/80174144