java泛型深入理解_深入理解java泛型详解 - CSDN
精华内容
参与话题
  • 深入理解java泛型

    2017-08-14 17:43:20
    泛型(Generic type 或者 generics)是对 简单的理解,就是对类型的参数化,比如我们定义一个类属性或者实例属性时,往往要指定具体的类型,如Integer、Person等等, 但是如果使用了泛型,我们把这些具体的类型参数...


    一、 什么是泛型?

    泛型(Generic type 或者 generics)是对 简单的理解,就是对类型的参数化,比如我们定义一个类属性或者实例属性时,往往要指定具体的类型,如Integer、Person等等, 但是如果使用了泛型,我们把这些具体的类型参数化,用一个广泛的可以表示所有类型的“类型”T来定义,那这个T就是泛型的表示。

    可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。

    因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:

    [java] view plain copy print?
    1. Map m = new HashMap();  
    2. m.put("key", "value");  
    3. String s = (String) m.get("key");  
    要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。如果map中保存了的不是 String 的数据,则上面的代码将会抛出 ClassCastException。


    二、 泛型的好处

    Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:
    1、 类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。
    2、 消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

    3、 潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。


    三、 泛型用法的例子

     3.1  我们再程序中定义一个类,并制定泛型参数

     

    [java] view plain copy print?
    1. class Point<T>{       // 此处可以随便写标识符号,T是type的简称  
    2.     private T var ; // var的类型由T指定,即:由外部指定  
    3.     public T getVar(){  // 返回值的类型由外部决定  
    4.         return var ;  
    5.     }  
    6.     public void setVar(T var){  // 设置的类型也由外部决定  
    7.         this.var = var ;  
    8.     }  
    9. };  
    10. public class GenericsDemo{  
    11.     public static void main(String args[]){  
    12.         Point<String> p = new Point<String>() ; // 里面的var类型为String类型  
    13.         p.setVar("MLDN") ;      // 设置字符串  
    14.         System.out.println(p.getVar().length()) ;   // 取得字符串的长度  
    15.     }  
    16. };  

    说明: 

    1. 命名类型参数
    推荐的命名约定是使用大写的单个字母名称作为类型参数。这与 C++ 约定有所不同(参阅 附录 A:与 C++ 模板的比较),并反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:
    K —— 键,比如映射的键。 
    V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。 
    E —— 异常类。 
    T —— 泛型。

    2. 以上,是将var变量设置为了String类型,当然也可以设置为其他的数据类型,比如Integer等,如果你设置的内容与你制定的泛型类型不一致,则在编译时将出现错误。比如:

     

    [java] view plain copy print?
    1. class Point<T>{       // 此处可以随便写标识符号,T是type的简称  
    2.     private T var ; // var的类型由T指定,即:由外部指定  
    3.     public T getVar(){  // 返回值的类型由外部决定  
    4.         return var ;  
    5.     }  
    6.     public void setVar(T var){  // 设置的类型也由外部决定  
    7.         this.var = var ;  
    8.     }  
    9. };  
    10. public class GenericsDemo{  
    11.     public static void main(String args[]){  
    12.         Point<Integer> p = new Point<Integer>() ;   // 里面的var类型为String类型  
    13.         p.setVar("MLDN") ;      // 设置字符串  
    14.     }  
    15. };  
    程序在编译期就出报错:

     

    [plain] view plain copy print?
    1. GenericsDemo.java:13: 错误: 无法将类 Point<T>中的方法 setVar应用到给定类型;  
    2.         p.setVar("MLDN") ;      // 设置字符串  
    3.          ^  
    4.   需要: Integer  
    5.   找到: String  
    6.   原因: 无法通过方法调用转换将实际参数String转换为Integer  
    7.   其中, T是类型变量:  
    8.     T扩展已在类 Point中声明的Object  
    9. 1 个错误  


    看log就可以发现,我我们已经规定了泛型的类型为Integer,则说明T类型就是Integer,所以在传入参数时,当然不能传入String类型的参数了。


     3.2  构造方法中使用泛型

    构造方法可以为类中的属性进行初始化,如果类中的属性用过泛型指定,而又需要通过构造器设置属性的内容时,那么构造方法的定义与之前并无不同,不需要像声明类那样指定泛型。

    [java] view plain copy print?
    1. class Point<T>{       // 此处可以随便写标识符号,T是type的简称  
    2.     private T var ; // var的类型由T指定,即:由外部指定  
    3.     public Point(T var){        // 通过构造方法设置内容  
    4.         this.var = var ;  
    5.     }  
    6.     public T getVar(){  // 返回值的类型由外部决定  
    7.         return var ;  
    8.     }  
    9.     public void setVar(T var){  // 设置的类型也由外部决定  
    10.         this.var = var ;  
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Point<String> p = new Point<String>("MLDN") ;   // 里面的var类型为String类型  
    16.         System.out.println("内容:" + p.getVar()) ;  
    17.     }  
    18. };  

    这里我们讲一个泛型的警告问题: 当你为某个类只定了泛型,但是,你实例化该类的对象的时候,并没有指定泛型的类型,则程序在编译时会出现警告,警告并不会影响程序的运行。 

     

    [java] view plain copy print?
    1. class Info<T>{  
    2.     private T var ;  
    3.     public T getVar(){  
    4.         return this.var ;  
    5.     }  
    6.     public void setVar(T var){  
    7.         this.var = var ;  
    8.     }  
    9.     public String toString(){       // 覆写Object类中的toString()方法  
    10.         return this.var.toString() ;  
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Info i = new Info() ;       // 警告,没有指定泛型类型  
    16.         i.setVar("MLDN") ;          // 设置字符串  
    17.         System.out.println("内容:" + i.getVar()) ;  
    18.     }  
    19. };  
    编译程序会出现警告:
    注: GenericsDemo10.java使用了未经检查或不安全的操作。
    注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

    说明: 由于没有指定泛型类型,则类可以接受任何数据类型,也就是此时的var的类型就是Object,所有的泛型信息都会被擦除。


    三、 泛型通配符

    3.1 引入泛型通配符

    我们先来看一个例子:

     

    [java] view plain copy print?
    1. class Info<T>{  
    2.     private T var ;     // 定义泛型变量  
    3.     public void setVar(T var){  
    4.         this.var = var ;  
    5.     }  
    6.     public T getVar(){  
    7.         return this.var ;  
    8.     }  
    9.     public String toString(){   // 直接打印  
    10.         return this.var.toString() ;  
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Info<String> i = new Info<String>() ;       // 使用String为泛型类型  
    16.         i.setVar("MLDN") ;                          // 设置内容  
    17.         fun(i) ;  
    18.     }  
    19.     public static void fun(Info<Object> temp){        // 接收Object泛型类型的Info对象  
    20.         System.out.println("内容:" + temp) ;  
    21.     }  
    22. };  
    在方法调用过程中,我们将Info<String>传递给Info<Object>,此时会发现,程序在编译时会报错:

     

    [sql] view plain copy print?
    1. GenericsDemo.java:17: 错误: 无法将类 GenericsDemo中的方法 fun应用到给定类型;  
    2.         fun(i) ;  
    3.         ^  
    4.   需要: Info<Object>  
    5.   找到: Info<String>  
    6.   原因: 无法通过方法调用转换将实际参数Info<String>转换为Info<Object>  
    7. 1 个错误</span>  


    上述错误说明,泛型对象进行引用传递的时候,类型必须一致,Info<Object>并不是Info<String>的父类。如果现在非要传递,则可以讲fun方法中的info参数的泛型取消掉:

     

    [java] view plain copy print?
    1. public static void main(String args[]){  
    2.     Info<String> i = new Info<String>() ;       // 使用String为泛型类型  
    3.     i.setVar("MLDN") ;                          // 设置内容  
    4.     fun(i) ;  
    5. }  
    6. public static void fun(Info temp){      // 接收Object泛型类型的Info对象  
    7.     System.out.println("内容:" + temp) ;  
    8. }  

    当然,这样看来程序已经可以正常运行了,但是,我们之前已经指定了泛型,此时却在方法传递过程中把它取消了,总是不妥的,所以,java提供了?通配符来匹配任何的泛型类型。

     

    [java] view plain copy print?
    1. public class GenericsDemo{  
    2.     public static void main(String args[]){  
    3.         Info<String> i = new Info<String>() ;       // 使用String为泛型类型  
    4.         i.setVar("MLDN") ;                          // 设置内容  
    5.         fun(i) ;  
    6.     }  
    7.     public static void fun(Info<?> temp){     // 可以接收任意的泛型对象  
    8.         System.out.println("内容:" + temp) ;  
    9.     }  
    10. };  
    我们应当注意,在fun方法中,我们是直接输出了temp对象,并为其做任何修改,实质上,使用?可以接收任意的内容,但是此内容却无法直接使用<?>进行修改,比如:我们这样去创建一个对象:

     

    [java] view plain copy print?
    1. public class GenericsDemo{  
    2.     public static void main(String args[]){  
    3.         Info<?> i = new Info<String>() ;        // 使用String为泛型类型  
    4.         i.setVar("MLDN") ;                          // 设置内容  
    5.     }  
    6. };  
    编译后,程序会报错:

     

    [plain] view plain copy print?
    1. GenericsDemo.java:16: 错误: 无法将类 Info<T>中的方法 setVar应用到给定类型;  
    2.         i.setVar("MLDN") ;                          // 设置内容  
    3.          ^  
    4.   需要: CAP#1  
    5.   找到: String  
    6.   原因: 无法通过方法调用转换将实际参数String转换为CAP#1  
    7.   其中, T是类型变量:  
    8.     T扩展已在类 Info中声明的Object  
    9.   其中, CAP#1是新类型变量:  
    10.     CAP#1从?的捕获扩展Object  
    11. 1 个错误  


    四、 受限泛型

    4.1 泛型上限: 表示参数化的类型可能是所指定类型,或者是其子类。

     

    [java] view plain copy print?
    1. class Info<T>{  
    2.     private T var ;     // 定义泛型变量  
    3.     public void setVar(T var){  
    4.         this.var = var ;  
    5.     }  
    6.     public T getVar(){  
    7.         return this.var ;  
    8.     }  
    9.     public String toString(){   // 直接打印  
    10.         return this.var.toString() ;  
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Info<Integer> i1 = new Info<Integer>() ; // 声明Integer的泛型对象  
    16.         Info<Float> i2 = new Info<Float>() ;  // 声明Float的泛型对象  
    17.         i1.setVar(30) ;  // 设置整数,自动装箱  
    18.         i2.setVar(30.1f) ;  // 设置小数,自动装箱  
    19.         fun(i1) ;  
    20.         fun(i2) ;  
    21.     }  
    22.     public static void fun(Info<? extends Number> temp){  // 只能接收Number及其Number的子类  
    23.         System.out.print(temp + "、") ;  
    24.     }  
    25. };  


    如果你接收的不是Number类及其子类,则程序会报错:

     

    [java] view plain copy print?
    1. public class GenericsDemo{  
    2.     public static void main(String args[]){  
    3.         Info<String> i1 = new Info<String>() ;      // 声明String的泛型对象  
    4.         i1.setVar("hello") ;  
    5.         fun(i1) ;  
    6.     }  
    7.     public static void fun(Info<? extends Number> temp){  // 只能接收Number及其Number的子类  
    8.         System.out.print(temp + "、") ;  
    9.     }  
    10. };  
    错误: 无法将类 GenericsDemo中的方法 fun应用到给定类型;
    fun(i1) ;
    ^
      需要: Info<? extends Number>
      找到: Info<String>
      原因: 无法通过方法调用转换将实际参数Info<String>转换为Info<? extends Number>
    1 个错误


    4.2 泛型下限:使用的泛型只能是本类及其父类类型上应用的时候,就必须使用泛型的下限。
    [java] view plain copy print?
    1. class Info<T>{  
    2.     private T var ;     // 定义泛型变量  
    3.     public void setVar(T var){  
    4.         this.var = var ;  
    5.     }  
    6.     public T getVar(){  
    7.         return this.var ;  
    8.     }  
    9.     public String toString(){   // 直接打印  
    10.         return this.var.toString() ;  
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Info<String> i1 = new Info<String>() ;      // 声明String的泛型对象  
    16.         Info<Object> i2 = new Info<Object>() ;      // 声明Object的泛型对象  
    17.         i1.setVar("hello") ;  
    18.         i2.setVar(new Object()) ;  
    19.         fun(i1) ;  
    20.         fun(i2) ;  
    21.     }  
    22.     public static void fun(Info<? super String> temp){    // 只能接收String或Object类型的泛型  
    23.         System.out.print(temp + "、") ;  
    24.     }  
    25. };  


    五、 泛型与子类继承

    一个类的子类可以通过对象多态性,为其父类实例化,但是在泛型操作中,子类的泛型类型是无法使用父类的泛型类型接受的,例如,Info<String>不能使用 Info<Object>接收。


    六、 泛型接口

    6.1 定义泛型接口

     

    [java] view plain copy print?
    1. interface Info<T>{   // 在接口上定义泛型  
    2.    public T getVar() ;  // 定义抽象方法,抽象方法的返回值就是泛型类型  
    3. }  


    6.2 泛型接口的两种实现方式

     6.2.1 定义子类,在子类的上也使用泛型声明

     

    [java] view plain copy print?
    1. interface Info<T>{        // 在接口上定义泛型  
    2.     public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
    3. }  
    4. class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
    5.     private T var ;             // 定义属性  
    6.     public InfoImpl(T var){     // 通过构造方法设置属性内容  
    7.         this.setVar(var) ;    
    8.     }  
    9.     public void setVar(T var){  
    10.         this.var = var ;  
    11.     }  
    12.     public T getVar(){  
    13.         return this.var ;  
    14.     }  
    15. };  
    16. public class GenericsDemo{  
    17.     public static void main(String arsg[]){  
    18.         Info<String> i = null;        // 声明接口对象  
    19.         i = new InfoImpl<String>("李兴华") ; // 通过子类实例化对象  
    20.         System.out.println("内容:" + i.getVar()) ;  
    21.     }  
    22. };  

    6.2.2 定义子类,直接指定泛型的具体操作类型

     

    [java] view plain copy print?
    1. interface Info<T>{        // 在接口上定义泛型  
    2.     public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
    3. }  
    4. class InfoImpl implements Info<String>{   // 定义泛型接口的子类  
    5.     private String var ;                // 定义属性  
    6.     public InfoImpl(String var){        // 通过构造方法设置属性内容  
    7.         this.setVar(var) ;    
    8.     }  
    9.     public void setVar(String var){  
    10.         this.var = var ;  
    11.     }  
    12.     public String getVar(){  
    13.         return this.var ;  
    14.     }  
    15. };  
    16. public class GenericsDemo{  
    17.     public static void main(String arsg[]){  
    18.         Info i = null;      // 声明接口对象  
    19.         i = new InfoImpl("李兴华") ;   // 通过子类实例化对象  
    20.         System.out.println("内容:" + i.getVar()) ;  
    21.     }  
    22. };  


    七、 泛型方法


    7.1 泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

    [java] view plain copy print?
    1. class Demo{  
    2.     public <T> T fun(T t){            // 可以接收任意类型的数据  
    3.         return t ;          // 直接把参数返回  
    4.     }  
    5. };  
    6. public class GenericsDemo{  
    7.     public static void main(String args[]){  
    8.         Demo d = new Demo() ;   // 实例化Demo对象  
    9.         String str = d.fun("李兴华") ;  // 传递字符串  
    10.         int i = d.fun(30) ;     // 传递数字,自动装箱  
    11.         System.out.println(str) ;   // 输出内容  
    12.         System.out.println(i) ;     // 输出内容  
    13.     }  
    14. };  

    7.2 通过泛型方法,返回泛型类的实例
    [java] view plain copy print?
    1. class Info<T extends Number>{ // 指定上限,只能是数字类型  
    2.     private T var ;     // 此类型由外部决定  
    3.     public T getVar(){  
    4.         return this.var ;     
    5.     }  
    6.     public void setVar(T var){  
    7.         this.var = var ;  
    8.     }  
    9.     public String toString(){       // 覆写Object类中的toString()方法  
    10.         return this.var.toString() ;      
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Info<Integer> i = fun(30) ;  
    16.         System.out.println(i.getVar()) ;  
    17.     }  
    18.     public static <T extends Number> Info<T> fun(T param){  
    19.         Info<T> temp = new Info<T>() ;      // 根据传入的数据类型实例化Info  
    20.         temp.setVar(param) ;        // 将传递的内容设置到Info对象的var属性之中  
    21.         return temp ;   // 返回实例化对象  
    22.     }  
    23. };  

    7.2 使用泛型,统一传递参数的类型

     

    [java] view plain copy print?
    1. class Info<T>{    // 指定上限,只能是数字类型  
    2.     private T var ;     // 此类型由外部决定  
    3.     public T getVar(){  
    4.         return this.var ;     
    5.     }  
    6.     public void setVar(T var){  
    7.         this.var = var ;  
    8.     }  
    9.     public String toString(){       // 覆写Object类中的toString()方法  
    10.         return this.var.toString() ;      
    11.     }  
    12. };  
    13. public class GenericsDemo{  
    14.     public static void main(String args[]){  
    15.         Info<String> i1 = new Info<String>() ;  
    16.         Info<String> i2 = new Info<String>() ;  
    17.         i1.setVar("HELLO") ;        // 设置内容  
    18.         i2.setVar("李兴华") ;      // 设置内容  
    19.         add(i1,i2) ;  
    20.     }  
    21.     public static <T> void add(Info<T> i1,Info<T> i2){  
    22.         System.out.println(i1.getVar() + " " + i2.getVar()) ;  
    23.     }  
    24. };  


    对于上述程序,我们再add方法里指定的T类型必须一致,比如上面指定了两个String类型,如果你传递的不一致,则会出现错误。

     

    [java] view plain copy print?
    1. public class GenericsDemo{  
    2.     public static void main(String args[]){  
    3.         Info<Integer> i1 = new Info<Integer>() ;  
    4.         Info<String> i2 = new Info<String>() ;  
    5.         i1.setVar(30) ;     // 设置内容  
    6.         i2.setVar("李兴华") ;      // 设置内容  
    7.         add(i1,i2) ;  
    8.     }  
    9.     public static <T> void add(Info<T> i1,Info<T> i2){  
    10.         System.out.println(i1.getVar() + " " + i2.getVar()) ;  
    11.     }  
    12. };  

    编译时报错:

     

    [java] view plain copy print?
    1. GenericsDemo.java:19: 错误: 无法将类 GenericsDemo中的方法 add应用到给定类型;  
    2.         add(i1,i2) ;  
    3.         ^  
    4.   需要: Info<T>,Info<T>  
    5.   找到: Info<Integer>,Info<String>  
    6.   原因: 不存在类型变量T的实例, 以使参数类型Info<String>与形式参数类型Info<T>一致  
    7.   其中, T是类型变量:  
    8.     T扩展已在方法 <T>add(Info<T>,Info<T>)中声明的Object  
    9. 1 个错误  

    六、 泛型数组

    不能创建一个确切泛型类型的数组。如下面代码会出错。
    List<String>[] lsa = new ArrayList<String>[10]; 
    因为如果可以这样,那么考虑如下代码,会导致运行时错误。 

    [java] view plain copy print?
    1. List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组    
    2. Object o = lsa;    
    3. Object[] oa = (Object[]) o;    
    4. List<Integer>li = new ArrayList<Integer>();    
    5. li.add(new Integer(3));    
    6. oa[1] = li;// unsound, but passes run time store check    
    7. String s = lsa[1].get(0); //run-time error - ClassCastException  


    因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List<Integer>类型的对象,而不是 List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。
    [java] view plain copy print?
    1. List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type    
    2. Object o = lsa;    
    3. Object[] oa = (Object[]) o;    
    4. List<Integer>li = new ArrayList<Integer>();    
    5. li.add(new Integer(3));    
    6. oa[1] = li; //correct    
    7. String s = (String) lsa[1].get(0);// run time error, but cast is explicit    
    8. Integer it = (Integer)lsa[1].get(0); // OK    
     
    展开全文
  • 说到泛型,大家应该都很熟悉,最常用的就是List<T> list这种。如果不是这几到方法的封装处理,利用反射,泛型类如果直接写的话其实没什么需要特别注意的,缺少泛型,编译器自然会提醒,加上也就行了。 好了,...

    说到泛型,大家应该都很熟悉,最常用的就是List<T> list这种。如果不是对方法的封装处理,利用反射,泛型类直接写的话,其实没什么需要特别注意的,缺少泛型,编译器自然会提醒,加上也就行了。

    好了,我们先引入一个简单的泛型。

         /**
             * 这个Integer可以写成int么?为什么?
             */
            List<Integer> list = new ArrayList<>();

    我们知道创建一个集合,都是要定义泛型的,当然也可以模糊定义,比如List<T> list等。那么这个Integer可以写成int么?这个当然是不能的,如下图,我们可以看到,泛型一般都是继承于Object,也就是说是一个包装类,int只是一个基本类型,是无法作为泛型的

    好了,我们接着说,<T>和<?>这两种泛型的使用不少伙伴应该都遇到过,可能在某一段时间内都比较茫然,这俩哥们都是类型的代表,有啥子区别啊?

    这个呢,说开了其实很简单,<T>(当然<E>也是一样,都是一个字母代替)这种标识,大家可以看到,<T>这个字母是固定的,一个字母也就是代替一个包装类,也就是说,这个泛型是一个类,比如说T表示的是Integer,那么只要使用List<T>或者其他包含T的使用,都是代表的是Integer。

    <?>泛型是一个问号,其实表示的也很清楚,就是说不知道会传来什么类型,什么类型都可以表示。

    我们知道,泛型的使用一般是方法的封装,利用反射等技术,抽取代码中的公共部分,这个时候一般才会使用<T>,<?>,可能有些伙伴还不是很明白,我们展开下。

    Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法

    概念的解释,我不是很擅长, 直接copy一份,下面直接讲使用哈。

    先创建几个类(可以跳过)

    /**
     * 动物(祖父类级别)
     */
    @Data
    public class Animal {
    
        /**
         * 判断条件,默认为false
         */
        private Boolean flag = false ;
    
        /**
         * 名字
         */
        private String name;
        /**
         * 重量
         */
        private String weight;
    
    }
    
    /**
     * 鸟类
     */
    @Data
    public class Bird extends Animal{
        /**
         * 飞多高
         */
        private String flyHeight;
    
    }
    
    /**
     * 鹰
     */
    @Data
    public class Eagle extends Bird{
    
        /**
         * 住在山上
         */
        private String mountain;
        /**
         * 吃肉
         */
        private String meat;
    
    }
    
    /**
     * 鹦鹉
     */
    @Data
    public class Parrot extends Bird {
    
        /**
         * 住的笼子
         */
        private String cage;
    
        /**
         * 吃米饭
         */
        private String rice;
    }
    
    

    我们写个来一步步看哈,先来个简单的,就是传来一个对象Animal,根据规则判断,然后进行赋值name,不用泛型,抽取方法中的公共部分,就是传来一个Bird会处理,传来一个Parrot也会处理,写一个简单的方法setAnimalName(Animal animal),实现下逻辑。

      /**
         * 设置动物名称
         * 无泛型,入参直接写父类名称,子类也可以传进来处理
         * 不过要处理的类型最好是父类的,不然要特殊处理(用反射)
         * @param animal
         */
        private static void setAnimalName(Animal animal){
            if(animal.getFlag()){
                animal.setName("animal");
            }
        }
    
        public static void main(String[] args) {
            Eagle eagle = new Eagle();
            eagle.setMeat("beef");
            eagle.setName("jake");
    
            Parrot parrot = new Parrot();
            parrot.setFlag(true);
            parrot.setCage("red-cage");
    
            setAnimalName(eagle);
            setAnimalName(parrot);
            System.out.println(" eagle:"+ JSON.toJSONString(eagle)+
                     "\n parrot:"+JSON.toJSONString(parrot));
    
        }
    

    可以看下打印输出,虽然没有用泛型,但是继承关系可以实现:

     eagle:{"flag":false,"meat":"beef","name":"jake"}
     parrot:{"cage":"red-cage","flag":true,"name":"animal"}
    

    有些小伙伴总感觉这种应该要用泛型吧?为啥没用呢?当然,用也是可以的,我们来看下。

       /**
         * 使用泛型
         * @param t
         */
        private static <T extends Animal> void setAnimalName(T t){
            if(t.getFlag()){
                t.setName("animal");
            }
        }

    是不是感觉有点意思的样子?结果是一样的,就不贴出来了。

    下面延伸一下,假如我没有继承关系,我只知道传来的对象里有一个字段name和一个字段flag,这个时候,当然只能使用泛型了,就是使用简单的反射和递归,来看下。

        /**
         * 使用泛型
         * @param t
         */
        private static <T> void setAnimalName(T t){
            Class<?> temp = t.getClass();
            try {
                //flag 可能在父类中,使用clzz = clzz.getSuperclass() 递归
                Method method = getMethod(temp,"getFlag");
                Object invoke = method.invoke(t);
                if(invoke instanceof Boolean && (Boolean) invoke){
                    Method setNameMethod = getMethod(temp,"setName",String.class);
                    //设置名称
                    setNameMethod.invoke(t, "animal");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 递归获取方法
         * @param temp
         * @param methodName
         * @return
         */
        private static Method getMethod(Class<?> temp,String methodName,Class<?>... parameterTypes){
            Method method = null ;
            while (method == null && !temp.getName().equalsIgnoreCase("java.lang.Object")){
                try {
                    method = temp.getDeclaredMethod(methodName,parameterTypes);
                } catch (NoSuchMethodException e) {
                    //源码中抛出了异常,所以还要捕获处理掉
                }
                temp = temp.getSuperclass();
            }
            return method;
        }
    }

    常用的泛型处理就说到这,下面我们来看下PECS,简单的说就是 生产者(Producer)使用extends,消费者(Consumer)使用super,代码看的有点懵的话可以先看下面的附图。

     /**
         * PECS理解
         */
        static public void test(){
    
            //继承Bird 的类是有多个的,这个类不是固定的,所以使用?,使用T会报错
            List<? extends Bird> pList = new ArrayList<>();
    
            pList = Arrays.asList(new Bird(),new Bird());
    //        pList.add(new Bird());//报错
    //        pList.add(new Eagle());//报错
    //        pList.add(new Parrot());//报错
    //        pList.add(new Animal()); //报错
            Bird bird = pList.get(0);
            System.out.println("sList的大小:"+pList.size() +"\n bird"+bird);
    
            List<? super Bird> sList = new ArrayList<>();
            sList.add(new Bird());
            sList.add(new Parrot());
            sList.add(new Eagle());
    //        Object o = sList.get(0);  //返回的是Object,没啥意义
    
            System.out.println("sList的大小:"+sList.size());
        }
        

    上面就是PECS的操作代码,直接看的话有点晕,下面画了一张图,大家可以看看哈,不对的话可以指出。

     

    PECS原则总结:

    • 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
    • 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
    • 如果既要存又要取,那么就不要使用任何通配符。

     接着来看一个经典的应用JDK 8 Collections.copy()

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
            int srcSize = src.size();
            if (srcSize > dest.size())
                throw new IndexOutOfBoundsException("Source does not fit in dest");
    
            if (srcSize < COPY_THRESHOLD ||
                (src instanceof RandomAccess && dest instanceof RandomAccess)) {
                for (int i=0; i<srcSize; i++)
                    dest.set(i, src.get(i));
            } else {
                ListIterator<? super T> di=dest.listIterator();
                ListIterator<? extends T> si=src.listIterator();
                for (int i=0; i<srcSize; i++) {
                    di.next();
                    di.set(si.next());
                }
            }
        }

    大家可以看到,src是数据源头,赋值到dest,相当于src 生产(Producer)数据,dest消费(Customer)数据。dest重新赋值数据,数据类型是T或者T的子类,当然也是T父类的子类。而src进行去数据,获取的数据返回的类型都是T(其中可能有T的子类,不过向上转换了,也是T)。这段源码不是很难,但是其中的思想还是很经典的。大家可以看看哈。这个源码不是很繁琐,大伙应该都能看的懂,就不写注释了哈。

    No sacrifice,no victory~

    展开全文
  • 一、什么是泛型  “泛型” 意味着编写的代码可以被不同类型的对象所重用。泛型的提出是为了编写重用性更好的代码。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 比如常见的集合类 ...

    一、什么是泛型

            “泛型” 意味着编写的代码可以被不同类型的对象所重用。泛型的提出是为了编写重用性更好的代码。泛型的本质是参数化类型也就是说所操作的数据类型被指定为一个参数

    比如常见的集合类 LinkedList:

    public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>,Deque<E>,Cloneable,Serializable{
     //.....
     transient Link<E> voidLink;
    //.....    
    }
    

           可以看到,LinkedList<E> 类名及其实现的接口名后有个特殊的部分<E>而且它的成员的类型 Link<E> 也包含一个<E>这个符号的就是类型参数,它使得在运行中,创建一个 LinkedList 时可以传入不同的类型。

    二、为什么引入泛型

           在引入泛型之前,要想实现一个通用的、可以处理不同类型的方法,你需要使用 Object 作为属性和方法参数,比如这样:

    public class Generic{
        private Object[] mData;
    
        public Generic(int capacity){
            mData = new Object[capacity];
        }
        
        public Object getData(int index){
           //.....
           return mData[index];
        }
         
        public void add(int index,Object item){
           //.....
           mData[index] = item;
        }   
    
    }

    它使用一个 Object 数组来保存数据,这样在使用时可以添加不同类型的对象:

    Generic generic = new Generic(10);
    generic.add(0,"fangxing");
    generic.add(1,23);

         Object 是所有类的父类,所有的类都可以作为成员被添加到上述类中;当需要使用的时候,必须进行强制转换,而且这个强转很有可能出现转换异常:

    String item1 = (String) generic.getData(0);
    String item2 = (String) generic.getData(1);

    第二行代码将一个 Integer 强转成 String,运行时会报错 :

    可以看到,使用 Object 来实现通用、不同类型的处理,有这么两个缺点:

    1. 每次使用时都需要强制转换成想要的类型
    2. 在编译时编译器并不知道类型转换是否正常,运行时才知道,不安全

    根据《Java 编程思想》中的描述,泛型出现的动机在于:

    有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类。

    在 JDK 1.5 出现泛型以后,许多集合类都使用泛型来保存不同类型的元素,比如 Collection:

    public interface Collection<E> extends Iterable<E>{
       
         Iterator<E> iterator();
       
         Object[] toArray();
         
         <T> T[] toArray(T[] a);
    
         boolean add(E e);
         
         boolean remove(Object o);
     
         boolean  containsAll(Collecion<?> c);
    
         boolean addAll(Collection<? extends E> c);
    
         boolean removeAll(Collection<?> c);
       
    }

    实际上引入泛型的主要目标有以下几点:

    类型安全 

    • 泛型的主要目标是提高 Java 程序的类型安全
    • 编译时期就可以检查出因 Java 类型不正确导致的 ClassCastException 异常
    • 符合越早出错代价越小原则

    消除强制类型转换 

    • 泛型的一个附带好处是,使用时直接得到目标类型,消除许多强制类型转换
    • 所得即所需,这使得代码更加可读,并且减少了出错机会

    潜在的性能收益 

    • 由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改
    • 所有工作都在编译器中完成
    • 编译器生成的代码跟不使用泛型(和强制类型转换)时所写的代码几乎一致,只是更能确保类型安全而已

    三、泛型的使用方式

            泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

           类型参数的意义是告诉编译器这个集合中要存放实例的类型,从而在添加其他类型时做出提示,在编译时就为类型安全做了保证。

          参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

    public class GenericClass<F>{
        private F mContent;
        
        public GenericClass(F content){
          mContent = content;
        }
      
        /*
          泛型方法
       */
        public F getContent(){
          return mContent;
        }
        
        public void setContent(F content){
          mcontent = content;
        }
       
        /*
           泛型接口
        */
        public interface GenericInterface<T>{
            void  doSomething(T t);
        }
    
    }

    泛型类  

            泛型类和普通类的区别就是类名后有类型参数列表 <E>,既然叫“列表”了,当然这里的类型参数可以有多个,比如 public class HashMap<K, V>,参数名称由开发者决定。

           类名中声明参数类型后,内部成员、方法就可以使用这个参数类型,比如上面的 GenericClass<F> 就是一个泛型类,它在类名后声明了类型 F,它的成员、方法就可以使用 F 表示成员类型、方法参数/返回值都是 F 类型。

          泛型类最常见的用途就是作为容纳不同类型数据的容器类,比如 Java 集合容器类。

    泛型接口

           和泛型类一样,泛型接口在接口名后添加类型参数,比如上面的 GenericInterface<T>,接口声明类型后,接口方法就可以直接使用这个类型。

           实现类在实现泛型接口时需要指明具体的参数类型,不然默认类型是 Object,这就失去了泛型接口的意义。

    未指明类型的实现类,默认是 Object 类型:

    public class Generic implements GenericInterface{
        @Override
        public void doSomething(Object o){
          //...
        }
    }

    指明了类型的实现:

    public class Generic implements GericInterface<String>{
         @Override
         public void doSomething(String s){
           //.....
         }
    }

    泛型接口比较实用的使用场景就是用作策略模式的公共策略, Comparator就是一个泛型接口:

    public interface Comparator<T>{
        public int compare(T lhs, Trhs);
        public bollean equals(Object object);
    }

    泛型接口定义基本的规则,然后作为引用传递给客户端,这样在运行时就能传入不同的策略实现类。

    泛型方法

       泛型方法是指使用泛型的方法,如果它虽在的类是一个泛型类,那就很简单了,直接使用类声明的参数。

       如果一个方法所在的类不是泛型类,或者他想要处理不同于泛型类声明类型的数据,那它就需要自己声明类型。

    /*
        传统的方法,会有unchecked ... raw type 的警告 
    */
    public Set union(Set s1, Set s2){
        Set result = new HashSet(s1);
        result.addAll(s2);
        return result;
    }
    
    /*
        泛型方法,介于方法修饰符和返回值之间的称作 类型参数列表<A,V,F,E....>(可以有多个)
        类型参数列表 指定参数、返回值中泛型的参数类型范围,命名惯例与泛型相同。
    */
    public <E> Set<E> union2(Set<E> s1, Set<E> s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

    四、泛型的通配符

    通配符:传入的类型有一个指定的范围,从而可以进行一些特定的操作

    泛型中有三种通配符形式:

    1.<?>无限制通配符

    2.<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

    3.<? super E> super 关键字声明了类型的下界,表示参数化类型可能是指定类型,或者是此类型的父类。

    无限制通配符 < ?>

    要使用泛型,但是不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。

    ? 和 Object 不一样,List<?> 表示未知类型的列表,而 List<Object> 表示任意类型的列表。

    如传入个 List<String> ,这时 List 的元素类型就是 String想要往 List 里添加一个 Object这当然是不可以的。

    上界通配符 < ? extends E>

    在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

    • 如果传入的类型不是 E 或者 E 的子类,编辑不成功
    • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用

    下界通配符 < ? super E>

    在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

    private <E> void add(List<? super E> dst, List<E> Src){
        for (E e : src){
        dst.add(e);
       }
    }

    上面的 dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src。

    通配符比较

    无限制通配符 < ?> 和 Object 有些相似,用于表示无限制或者不确定范围的场景。

     

    < ? super E> 用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象。

     

    < ? extends E> 用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象。

    因此使用通配符的基本原则:

    • 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;(T 的子类)
    • 如果它表示一个 T 的消费者,就使用 < ? super T>;(T 的父类)
    • 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。

    小总结一下:

    • T 的生产者的意思就是结果会返回 T,这就要求返回一个具体的类型,必须有上限才够具体;
    • T 的消费者的意思是要操作 T,这就要求操作的容器要够大,所以容器需要是 T 的父类,即 super T;

    举个例子:

    private <E extends Comparable<? super E>> E max(List<? extends E> e1){
    
        if(e1 == null){
            return null;
        }
    
        //迭代器返回的元素属于 E 的某个子类型
       
        Iterator<? extends E> iterator = e1.iterator();
        E result = iterator.next();
        while (iterator.hasNext()){
         E next = iterator.next();
          if(next.compareTo(result)>0){
           result = next;
          }
        }
        return result;
    }

    1.要进行比较,所以 E 需要是可比较的类,因此需要 extends Comparable<…>(注意这里不要和继承的 extends 搞混了,不一样)

    2.Comparable< ? super E> 要对 E 进行比较,即 E 的消费者,所以需要用 super

    3.而参数 List< ? extends E> 表示要操作的数据是 E 的子类的列表,指定上限,这样容器才够大

    五、泛型的类型擦除

    Java 中的泛型和 C++ 中的模板有一个很大的不同:

    • C++ 中模板的实例化会为每一种类型都产生一套不同的代码,这就是所谓的代码膨胀。
    • Java 中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。

           在 Java 中,泛型是 Java 编译器的概念,用泛型编写的 Java 程序和普通的 Java 程序基本相同,只是多了一些参数化的类型同时少了一些类型转换。

         实际上泛型程序也是首先被转化成一般的、不带泛型的 Java 程序后再进行处理的,编译器自动完成了从 Generic Java 到普通 Java 的翻译,Java 虚拟机运行时对泛型基本一无所知

        当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)。

         实际上无论你是否使用泛型,集合框架中存放对象的数据类型都是 Object,这一点不仅仅从源码中可以看到,通过反射也可以看到。

    List<String> strings = new ArrayList<>();
    List<Integer> integers = new ArrayList<>();
    System.out.println(Strings.getClass()==integers.getClass());//true

    上面代码输出结果并不是预期的false,而是true。其原因就是泛型的擦除。

    六、擦除的实现原理

    一直有个疑问,Java 编译器在编译期间擦除了泛型的信息,那运行中怎么保证添加、取出的类型就是擦除前声明的呢?

    Java 编辑器会将泛型代码中的类型完全擦除,使其变成原始类型。当然,这时的代码类型和我们想要的还有距离,接着 Java 编译器会在这些代码中加入类型转换,将原始类型转换成想要的类型。这些操作都是编译器后台进行,可以保证类型安全。总之泛型就是一个语法糖,它运行时没有存储任何类型信息。

    擦除导致的泛型不可变性

    泛型中没有逻辑上的父子关系,如 List 并不是 List 的父类。两者擦除之后都是List,所以形如下面的代码,编译器会报错:

    /*
        两者并不是方法的重载,擦除之后就是同一方法,所以编译不会通过。
        擦除之后:
        void m(List numbers){}
        void m(List Strings){}   //编译不通过,已经存在形同方法签名 
    */
       void method(List<Object> numbers){}
       void method(List<String> strings){}
    
    

    泛型的这种情况称为 不可变性,与之对应的概念是 协变、逆变:

    • 协变:如果 A 是 B 的父类,并且 A 的容器(比如 List< A>) 也是 B 的容器(List< B>)的父类,则称之为协变的(父子关系保持一致)
    • 逆变:如果 A 是 B 的父类,但是 A 的容器 是 B 的容器的子类,则称之为逆变(放入容器就篡位了)
    • 不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变

    Java 中数组是协变的,泛型是不可变的。

    擦除的拯救者:边界

    我们知道,泛型运行时被擦除成原始类型,这使得很多操作无法进行.

    如果没有指明边界,类型参数将被擦除为 Object。

    如果我们想要让参数保留一个边界,可以给参数设置一个边界,泛型参数将会被擦除到它的第一个边界(边界可以有多个),这样即使运行时擦除后也会有范围。

    比如:

    public class GenericErasure {
        interface Game{
          void play();
        }
        interface Program{
          void code();
       }
       public static calss People<T extends Program & Game>{
           private T mPeople;
           
           public People(T people){
             mPeople = people;
           }
           public void habit(){
             mPeople.code();
             mPeople.play();
            }
       }
       
    
    }

    上述代码中, People 的类型参数 T 有两个边界,编译器事实上会把类型参数替换为它的第一个边界的类型。

    七、泛型的规则

    • 泛型的参数类型只能是类(包括自定义类),不能是简单类型。
    • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
    • 泛型的类型参数可以有多个
    • 泛型的参数类型可以使用 extends 语句,习惯上称为“有界类型”
    • 泛型的参数类型还可以是通配符类型,例如 Class

    泛型的使用场景

           当类中要操作的引用数据类型不确定的时候,过去使用 Object 来完成扩展,JDK 1.5后推荐使用泛型来完成扩展,同时保证安全性。

    八、总结

        1.上面说到使用 Object 来达到复用,会失去泛型在安全性和直观表达性上的优势,那为什么 ArrayList 等源码中的还能看到使用 Object 作为类型?

          泛型出现时,Java 平台即将进入它的第二个十年,在此之前已经存在了大量没有使用泛型的 Java 代码。人们认为让这些代码全部保持合法,并且能够与使用泛型的新代码互用,非常重要。

    这样都是为了兼容,新代码里要使用泛型而不是原始类型。

    2.泛型是通过擦除来实现的。因此泛型只在编译时强化它的类型信息,而在运行时丢弃(或者擦除)它的元素类型信息。擦除使得使用泛型的代码可以和没有使用泛型的代码随意互用。

    3.如果类型参数在方法声明中只出现一次,可以用通配符代替它。

    private <E> void swap(List<E> list, int i, int j){
        //....
    }

    只出现了一次 类型参数,没有必要声明,完全可以用通配符代替:

    private void swap(List<?> list, int i, int j){
        //...
    }

    对比一下,第二种更加简单清晰吧。

    4.数组中不能使用泛型

    Array 事实上并不支持泛型,这也是为什么 Joshua Bloch 在 《Effective Java》一书中建议使用 List 来代替 Array,因为 List 可以提供编译期的类型安全保证,而 Array 却不能。

    5.Java 中 List<Object> 和原始类型 List 之间的区别?

    • 在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查
    • 通过使用 Object 作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String 或 Integer
    • 你可以把任何带参数的类型传递给原始类型 List,但 却不能把 List< String> 传递给接受 List< Object> 的方法,因为泛型的不可变性,会产生编译错误。

    九、补充

    静态资源不认识泛型

    接上一个话题,如果把<T>去掉,那么:

    private static T ifThenElse(boolean b, T first, T second){
        return b ? first : second;
    }

    报错,T未定义。但是如果我们再把static去掉:

    public class TestMain<T>{
        public static void main(String[] args){}
        
       @SuppressWarnings("unused")
       private List<T> ifThenElse(boolean b,T first, T second){
         return null;
       }
    }

    这并不会有任何问题。两相对比下,可以看出static方法并不认识泛型,所以我们要加上一个<T>,告诉static方法,后面的T是一个泛型。既然static方法不认识泛型,那我们看一下static变量是否认识泛型:

    public class TestMain<T>{
        private List<T> notStaticList;
        private static List<T> staticList;
    }

    这证明了,static变量也不认识泛型,其实不仅仅是staic方法、static变量、static块,也不认识泛型,可以自己试一下。总结起来就是一句话:静态资源不认识泛型。

    展开全文
  • Java泛型深入理解

    2019-04-19 20:11:50
    https://blog.csdn.net/sunxianghuang/article/details/51982979
    展开全文
  • 深入理解java泛型详解

    1970-01-01 08:00:00
    主要介绍了Java中的泛型详解,什么是泛型,作用以及基础实例等,喜欢的朋友可以参考
  • 无意间都到一篇《走心的安卓工程师跳槽经验分享》,发现自己工作几年了,技术方面虽然有了飞跃的进步,可是不知道自己的技术到了什么地步,...06 深入理解 Java 泛型 最近我的 Effective Java 也刚好编写到泛型,有...
  • JDK1.5开始实现了对泛型的支持,但是java泛型支持的底层实现采用的是类型...看了部分Java编程思想中泛型一章的内容,现在很晕,越看越觉得晕,这篇文章先欠着吧,等我再看一遍深入理解Java虚拟机JVM高级特性与最佳实
  • 深入理解 Java 泛型

    万次阅读 多人点赞 2017-01-18 19:48:40
    Java 泛型的作用是什么?泛型擦除是什么?泛型一般用在什么场景? 如果这个问题你答不上来,那这篇文章可能就对你有些价值。 读完本文你将了解到: 什么是泛型 为什么引入泛型 泛型的使用方式 泛型泛型接口 ...
  • 本文参考java 泛型详解、Java中的泛型方法、 java泛型详解 1. 概述泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”。一...
  • Java泛型-类型擦除

    万次阅读 多人点赞 2011-05-08 13:10:00
    正确的使用Java泛型需要深入的了解Java的一些概念,如协变,桥接方法,以及这篇笔记记录的类型擦除。Java泛型的处理几乎都在编译器中进行,编译器生成的bytecode是不包涵泛型信息的,泛型类型信息将在编译处理是被...
  • java泛型与数组

    千次阅读 2018-02-27 14:16:17
    泛型与数组 ...我对于该问题的理解是:由于泛型只是在编译的时候,起到类型检测,及类型转换字节码的生成,也就是说具有泛型java文件,最终生成的字节码会将泛型信息抹去,具体数据的引用类型一般...
  • 深入理解java泛型

    千次阅读 2020-05-22 10:07:19
    深入理解java泛型 简介 泛型是JDK 5引入的概念,泛型的引入主要是为了保证java中类型的安全性,有点像C++中的模板。 但是Java为了保证向下兼容性,它的泛型全部都是在编译期间实现的。编译器执行类型检查和类型...
  • 面试题--你是怎么理解泛型的?

    千次阅读 2015-04-10 17:14:27
    在没有泛型之前,一旦吧一个对象“丢进”java集合中,集合就会忘记对象的类型,把所有的对象都当成是Object类型处理。当程序从集合中取出对象之后,就要进行强制类型转换,这种强制类型转换不仅代码臃肿还容易引起...
  • Java 泛型,你了解类型擦除吗?

    万次阅读 多人点赞 2019-09-04 16:18:19
    泛型Java 中一个很小巧的概念,但同时也是一个很容易让人迷惑的知识点,它让人迷惑的地方在于它的许多表现有点违反直觉。文章开始的地方,先给大家奉上一道经典的测试题。List&lt;String&gt; l1 = new ...
  • 下面的代码将介绍如何利用ParameterizedType获取java泛型参数类型,进而通过反射获取java类。//超类 package test;import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type;@SuppressWarnings(...
  • Java泛型的实现:原理与问题

    万次阅读 2016-07-23 12:46:41
    昨天看到Java泛型相关的内容,有些疑惑,查资料之后发现这部分很有些有意思的东西,比如类型擦除带来的重写问题等等,一并记录在这篇文章里。1. 泛型定义看了很多泛型的解释百度百科,解释1,解
  • java泛型详解

    万次阅读 多人点赞 2016-04-29 01:03:29
    1、什么是java泛型? 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 2、...
  • java泛型(尖括号里看不懂的东西)

    千次阅读 2019-08-13 19:40:28
    对于对java理解不深的程序员,对很多的java高级特性不是很懂,以前看别人家的代买的时候,经常会看到方法名前面...直到最近准备好好的学一下java,了解到这是java泛型,之前看的方法叫泛型方法,今天就来记录一下泛型。
  • 本文对java泛型的概念和使用做了详尽的介绍。 本文参考https://blog.csdn.net/s10461/article/details/53941091 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 文章首发于我的个人博客:...
  • 前言:Java泛型真是狗屎! 一、泛型里的强制转换 在阅读 Core Java 和 ArrayList 源码时,一直有一个问题困扰着我:既然泛型都会被擦出成raw type(这里被擦除为Object),那么为什么还会有(T) 或(T[ ])这种...
1 2 3 4 5 ... 20
收藏数 16,899
精华内容 6,759
关键字:

java泛型深入理解