`
491823151
  • 浏览: 4458 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

泛型(Generic)

    博客分类:
  • Java
阅读更多

前几天在看撒加老师早期的一些关于泛型的回复,对于泛型深有体会,略作小结。

对于泛型来说,这个JDK1.5加入的新朋友,大家应该都熟悉的再熟悉不过了,在此就不作介绍了,直接进入正题。

(调用撒迦的话来说) ^ ^
Java泛型有这么一种规律:
位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。


下面我来看一下使用侧泛型:

 

import java.util.ArrayList;  
import java.util.List;  
  
public class TestClass {  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<String>(); 
        list.add("dog");
        String s = list.get(0);
    }  
}  

 

 通过jad查看编译后的class文件:

 

public static void main(String args[]){
        ArrayList arraylist = new ArrayList();
        arraylist.add("dog");
        String s = (String)arraylist.get(0);
}

 

我们可以看到编译过后,原先我们定义的泛型信息全部消失了(擦除),并且经过javac编译后自动帮我们添加上了类型转化代码,接下来我们来看下main方法的bytecode:

 

   //    0    0:new             #2   <Class ArrayList>
    //    1    3:dup             
    //    2    4:invokespecial   #3   <Method void ArrayList()>
    //    3    7:astore_1        
    //    4    8:aload_1         
    //    5    9:ldc1            #4   <String "dog">
    //    6   11:invokeinterface #5   <Method boolean List.add(Object)>
    //    7   16:pop             
    //    8   17:aload_1         
    //    9   18:iconst_0        
    //   10   19:invokeinterface #6   <Method Object List.get(int)>
    //   11   24:checkcast       #7   <Class String>
    //   12   27:astore_2        
    //   13   28:return          

 

在第6行的add方法中我们是以一个Object类型添加进容器,在第11行get出来的是Object类型并且通过checkcast (String)来确保我们类型的正确性,并且在这个class文件中没有  Signature:xxxx 的存在(由于1.5以后泛型的加入,class文件格式有所调整,需要将泛型信息记录在class文件中,Signature就是用来记录)。由此可见在使用测的泛型信息全部被擦除掉,并且添加进去的类型是以List<T>决定,这两问题稍后再谈。由此可见在JDK1.4之前我们要手动进行类型检查,在JDK1.5后有了泛型,虽然我们在java源代码中不用手动去进行类型转化,但不代表类型转换就不需要,在编译后的class文件我们看到checkcast的添加,所以可见使用和不使用泛型并不能增加我们什么性能,类型检查都是需要的,我们来看下没有使用泛型的bytecode:

 

  //    0    0:new             #2   <Class ArrayList>
    //    1    3:dup             
    //    2    4:invokespecial   #3   <Method void ArrayList()>
    //    3    7:astore_1        
    //    4    8:aload_1         
    //    5    9:ldc1            #4   <String "dog">
    //    6   11:invokeinterface #5   <Method boolean List.add(Object)>
    //    7   16:pop             
    //    8   17:aload_1         
    //    9   18:iconst_0        
    //   10   19:invokeinterface #6   <Method Object List.get(int)>
    //   11   24:checkcast       #7   <Class String>
    //   12   27:astore_2        
    //   13   28:return          

 

比较下我们可以看到两者没有区别~~

 

接下来我们来看下声明测泛型的使用:

 

什么是声明测泛型?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。(还是撒迦的话) ^   ^

 

接下来我们来看:

import java.util.List;  
import java.util.Map;  
  
public class GenericClass<T> {                
    private List<T> list;                    
   
    private T t;            
      
    public <U> U genericMethod(Map<T, U> m) { 
        return null;  private java.util.List list;  
    }  
}  

 

以上面的两个成员变量list和t为例,通过javap我们查看编译后的class文件,可以看到

 

private java.util.List list;  
Signature: length = 0x2  
   00 07

private java.lang.Object t;  
  Signature: length = 0x2  
   00 0A

 

初看list中不是没有记录我们所定义的泛型信息么?但下面有我们上面提到过的Signature : 00 07(表示的是常量池索引)

到常量池中找出对应的项:

 

const #7 = Asciz       Ljava/util/List<TT;>;;

 

 可以看到泛型信息被我们记录在此,对于成员变量t我们查看其类型信息~

具体泛型在class中的描述形式可以参:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf

但为什么我们t类型被表示为Object类型呢?

 

事实上,JVM并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。 
    处理方法就是我们所提到的擦除(erased) 
    无论我们如何定义一个泛型类型,相应的都会有一个原始类型被自动提供。原始类型的名字就是擦除类型参数的泛型类型的名字。
         如果泛型类型的类型变量没有限定(<T>) ,那么我们就用Object作为原始类型;
         如果有限定(<T extends XClass>),我们就XClass作为原始类型;
         如果有多个限定(<T extends XClass1&XClass2>),我们就用第一个边界的类型变量XClass1类作为原始类型; 

    比如上面的GenericClass<T>例子,编译器会把它当成被Object原始类型替代的普通类来替代。

所以现在可以知道为什么我们在使用list.add("dog")的时候会以Object类型装入容器了,因为List在被编译后原始类型被定义为Object了~

 

理解了泛型的声明侧和使用侧,也就知道该什么之后可以获取具体的泛型信息~

 

写的浑身都是汗。。重庆的天气~~哎

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics