美文网首页安卓开发博客
关于泛型你要的所有内容都在这里了!

关于泛型你要的所有内容都在这里了!

作者: 坑王老薛 | 来源:发表于2018-08-15 19:18 被阅读222次

    泛型:

    本内容均属原创,一个字一个字敲的,如果要转载请注明出处:
    https://www.jianshu.com/p/2aa69dd9222d

    写这篇文章的初衷,是对于大家遇到的几道泛型(Generic)题目的不理解
    那不妨我们在这里展开,好好讨论一下泛型这个鬼东西。
    

    为了更好的说明泛型这个内容我会从以下几个方向依次展开讨论:

    1、什么是泛型?
        i、什么是类型安全的
        ii、泛型的语法格式
    2、为什么需要泛型?
    3、泛型主要分类:
        i、泛型类
        ii、泛型方法
        iii、泛型接口
        iv、泛型构造器
    4、泛型的注意事项:
        i、优点:   确保数据安全性
            a、泛型中能使用基本数据类型
            b、不同类型的泛型对象能互相转换
        ii、反射对于泛型的影响
        iii、泛型边界问题
        iv、泛型的擦除:
    5、jdk8增强的泛型类型推断
    6、拓展jdk10增强的局部变量的类型推断
    

    1、什么是泛型?

    什么是泛型呢?就是一个广泛的类型,注意它首先是一个类型,其次因为广泛,所以就是不明确类型。
    所以我们说泛型就是一个不明确的类型。那如果类型不明确我们如何使用呢?别着急我们往下继学。
    泛型是从jdk1.5之后引入的新特性。它不光增加了java的新的语法,同样也改变了核心API中的许多类以及方法。常见的比如java.util.*。通过泛型,我们可以创建类型安全的类、接口等。

    1-1:什么是类型安全的呢?

    基于这个问题,我会在第二块中详细阐述这个问题。这里先不深入

    1-2:泛型的语法格式

    我们在后续的泛型分类中会详细介绍泛型不同的创建方式和细节,这里我们要注意泛型的书写方式!

    通过一组<> 去定义泛型。然后在<>中编写任意的字母即可。但是不能以数字开头。
    我们通常会通过在<>中定义26个英文字母的大写表示。一般常见的字母为T,E,K,V等。
    

    例子:

    <T> //定义一个泛型类型 T
    <K,V> //定义了一个泛型 包含两个泛型类型分别是K和V
    

    2、为什么需要泛型?

    2-1:我们试想,如果没有泛型,假设在一下代码中,会出现什么问题?

    测试代码1:

            List ls = new ArrayList();
            ls.add("str");
            ls.add(new Integer(12));
            for(int i = 0;i<ls.size();i++){
                Integer s = (Integer) ls.get(i);
                System.out.println(s);
            }
    

    总结:

    以上代码会出现问题,ClassCastException<类型转换异常>。原因大家应该都很清楚,那我们试想,如果没有泛型,所有对于数据的操作都会按照显式的方式进行转换,所以我们说泛型在一定程度上帮助我们做到了确保数据安全性。当然安全性我们还会通过其他示例来深入阐述。

    测试代码2:

        public static void main(String[] args) {
            List lists = new ArrayList();
            lists.add("hello");
            lists.add(12);
            List<String> ls = new ArrayList<String>();
            ls.add("hello");
            ls.add(12);//the compile-time error
            
        }
    

    总结:

    以上代码我们能够很直观的看出来,如果对于一个没有声明泛型的泛型的List集合来讲,可以往集合中添加任意类型(注意只能是引用类型)。而对于声明类泛型类型为String的List集合而言只能添加Stirng对象,当要往List集合添加12时(注意,这里的12会做自动装箱),编译时会报错。很好帮我解决了数据验证的问题。其实我们很难有场景要在一个集合中填充不同对象,对于后期来讲这样是不太方便的。大多数场景下我们还是填充的统一类型数据。(当然有时候我们确实有添加不同对象的需求,我们在后续的例子中会展开讨论)。

    测试代码3:此时泛型的行为时期

    在这里我们将上述的代码编译之后,通过反编译工具打开再次查看:

    public static void main(String[] args)
      {
        List lists = new ArrayList();
        lists.add("hello");
        lists.add(Integer.valueOf(12));
        List ls = new ArrayList();
        ls.add("hello");
      }
    

    这里无论是lists集合还是ls集合,在编译完之后的我们发现,泛型都不存在了,而且12的填充确实调用了Integer.valueOf()进行了自动装箱。所以我们说泛型是一个编译器行为。那其实这也就为我们通过反射在运行期动态往一个泛型集合中添加不同数据类型埋下伏笔。

    3、泛型分类

    接下来我会通过四个方向依次详细说明泛型在不同情境下定义要遵守的一些规则。

    3-1:泛型类:

    3-1-1:定义普通泛型类

    public class Test01 {
        public static void main(String[] args) {
            //create generic type Gen instance and assign
            //generic type of Gen is Integer
            //it`s use of autoboxing to encapsulate 
            //the value 12 within an Integer Object
            Gen<Integer> g1 = new Gen<Integer>();
            g1.t = 12;
            g1.getT();
            //jdk1.7  enhancement type inference 
            Gen<String> g2 = new Gen<>();
            g2.t = "hello";
            g2.getT();
            //create generic type Gen instance and 
            //don`t assign generic type
            Gen g3 = new Gen();
            g3.t = new Date();
            g3.getT();
        }
    }
    //declared a generic type Gen
    class Gen<T>{
        T t;
        public void getT(){
            System.out.println(t.getClass().getName());
        }
    }
    
    

    总结

    在这里说明了一个Generic类->Gen,泛型类型是T,我们上文提到过,泛型就是一个类型,那么这里的T你不防理解为一个类型。并且为了获取T类型方便一些,我们将T类型也声明为了一个成员变量。我们在代码中通过getT()方法获取成员变量T的Class对象的名称。其次我们在测试类创建了3个Gen的对象。
    在第一个用例中:创建对象时指定类型为Integer,且通过给对象中的T类型赋值为12。我们发现获取到的Class对象的名称为Integer。
    第二个用例中:创建对象时指定类型为String,且这里我们在对象创建中只通过<>,而没有在括号中给具体的类型,这是jdk1.7中的增强类型推断,因为已经声明过时String类型,所以会自动推断出Gen对象的T类型是String。同样这里获取到的Class对象名称为String。
    第三个用例中:创建对象时没有指定泛型类型,我们这里直接赋值发型可以给T传入任何Object类型。其实这就是典型的擦除。我们后续分享会逐一解开这个面纱。这里获取到的泛型的name成为了Object。

    总结:在一个类声明时通过<T>会指定泛型类型。创建对象时可以在<>指定具体的泛型类型。
    如果不指定则经过擦除之后,类型变为Object。我们可以将g1Gen<Integer>和g2<String>
    理解为一个Gen<T>类的一个特殊子类。这个子类是不存在。(注意只是这样理解,方便我们后续
    理解类型擦除)。
    

    注意:创建泛型类型对象时,构造器的类名还是原来的类名。不需要增加泛型声明。不需要写为Gen<T>。

    3-1-2:泛型类中的继承

    我们通过以下几个小例子阐述,在创建泛型子类时的编写问题:
    
    class Gen<T>{
        T t;
        public void getT(){
            System.out.println(t.getClass().getName());
        }
    }
    //declared a subclass for Gen
    //subclass don`t define generic tyoes,  allow the definition of declared
    class SubGen1 extends Gen{}
    //allows subclass to specify the superclass specific generic type
    class SubGen2 extends Gen<String>{}
    //don`t allows superclass definition generic type 
    class SubGen3 extends Gen<T>{}//compile-time error
    
    

    总结:

    1、定义子类继承泛型类,不做任何泛型方法,那么创建子类对象时泛型类中的类型自动转为Object
    2、定义子类继承泛型类,在父类中指定具体泛型类型,那么创建子类对象时泛型类中的类型为指定类型
    3、定义子类继承泛型类,在父类中指定不具体的泛型类型,那么是不允许的。
    

    3-2:泛型接口:

    测试用例:

    //define generic interface
    interface List<E> extends Iterator<E>{
        void add(E e);
        Iterator<E> iterator();
    }
    //define generic interface
    interface Iterator<E>{
        E next();
        boolean hasNext();
    } 
    //define subclass for interface
    class MyList implements List <String>{
        @Override
        public String next() {return null;}
        @Override
        public boolean hasNext() {return false;}
        @Override
        public void add(String e) {}
        @Override
        public Iterator<String> iterator() {return null;}
        //test generic interface
        public static void main(String[] args) {
            MyList ls = new MyList();
            ls.add("hehe");//execute add method can only add Stirng type 
        }
    }
    
    

    结论:

    1、定义泛型接口时,实现类如果声明时指定了泛型类型,那么后续调用时,在编译阶段只能使用该类型。
    2、如果定义泛型接口时,实现类不指定接口的泛型类型,那么会报警告。
      List is a raw type. References to generic type List<E> should be parameterized。说明当前泛型接口是一个 raw type.最好指定泛型是参数化的。
      而且对于后续程序来讲这样也是不太可取的。
    

    包含泛型的类型,不论是类、子类、实现类、对象。我们其实在理解层面上都可以认为是当前类、
    实现类的一个逻辑子类。比如Gen<String>可以理解为一个Gen<Object>的子类,List<String>是List<T>的一个子类,但是这个是一个逻辑,在物理上不存在。只是为了方便理解。

    3-3:泛型方法 jdk1.5支持

    定义方法,完成功能,将数组中的数据填充到集合中。

    测试用例:

    public static void main(String[] args) {
            Object[] obj = new Object[]{"hello",123.12,"java"};
            ArrayList<Object> as = new ArrayList<>();
            fillIntoColls(obj, as);
            
            obj = new Object[]{"hello",123.12,"java"};
            Collection<String> ass = new ArrayList<>();
            //fic(Object[], Collection<Object>) 
            //not applicable for the arguments (Object[], ArrayList<String>)
            fillIntoColls(obj, ass);//compile-time error
            
        }
        public static void fillIntoColls(Object[] objs,Collection<Object> cols){
            for(Object obj:objs){
                cols.add(obj);
            }
        }
    

    结论:

    虽然看着方法没有问题,但是在编译阶段Collection<String>对象不能作为Collection<Object>的对象使用。并且Collection<String>也不是Collection<Object>的子类型。

    解决办法:
        1、可以通过通配符以及上下限解决。后续分享中我们在讨论。
        2、泛型方法
    

    测试用例:

    public static void main(String[] args) {
            Object[] obj = new Object[]{"hello",123.12,"java"};
            ArrayList<Object> as = new ArrayList<>();
            fillIntoColls(obj, as);
            
            Collection<String> ass = new ArrayList<>();
            String[] strs = new String[]{"hello","java"};
            fillIntoColls(strs, ass);
            
        }
        public static <T> void fillIntoColls(T[] objs,Collection<T> cols){
            for(T t:objs){
                cols.add(t);
            }
        }
    

    结论:

    在方法中通过<>声明了一个泛型类型,在fillIntoColls方法中使用,将数组以及集合的类型都声明为泛型类型。当然也可以定义多个值。这里有个细节注意,因为是在static方法中声明的,所以当前的泛型方法中的泛型类型只能在当前方法中使用。如果是非static,也可以直接使用泛型类中声明的,具体大家也可以参照java.util.ArrayList编译器会在这里根据实际参数推断出类型的T的参数,所以其实当你传入的数组类型和集合的泛型类型不匹配,调用方法时也会报错。

    3-4:java7菱形语法以及泛型构造器

    3-4-1:java7增加的菱形语法:

    测试用例:

    List<String> l1 = new ArrayList<>();
    List<Integer> l2 = new ArrayList<>();
    l1.add("hello");
    l2.add(12);
    //compile-time error
    l1.add(12);
    l2.add("hello");
    

    结论:jdk1.7之后增强了泛型的类型推断。就是l1,l2对象创建时,指定了泛型的具体类型,那么构造器后就无序指定完整的泛型信息。并且后续的add()方法对于l1而言只能添加String,对于l2而言只能增加Integer对象。左右后面的两行代码都会在编译期间报错。

    3-4-2:泛型构造器:

    注意:java允许构造器声明时可以使用泛型的。这里的构造器就变成了泛型构造器。

    测试用例:

    public class Test {
        public static void main(String[] args) {
            new Gen(123);
            new <String>Gen("123");
            new <Integer>Gen(123.1);//compile-time error
            Gen<Integer> g = new Gen<>(123); //compile-time error
        }
    }
    class Gen{
        //define constructor use generic
        public <T> Gen(T t){
            System.out.println(t);
        }
    }
    
    

    结论:

    • 泛型构造器如果调用时不指定泛型类型,具体传入的类型决定了泛型类型。其实经过擦除之后就是Object

    • 泛型构造器调用时在new关键词后加入泛型构造器的泛型具体类型,那么传入值的时候,会自动推断出来是String类型(比如上述代码的第2、3行);但是如果声明的类型和实际传入的类型不一致,则会在编译器报错。

    • 这里千万注意,泛型构造器使用时,书写是new <类型> 类名();而泛型类创建对象时是new 类名<>();所以这一点千万注意。但是这是泛型构造器和泛型类只存在一个的情况下。

    泛型构造器的坑

    你觉的下面几行代码那几行会报错呢?

    public class Test05 {
        public static void main(String[] args) {
            Gen<Integer> g1 = new Gen<>(123); 
            
            Gen<String> g2 = new <Integer>Gen(123); 
            
            Gen<String> g3 = new <Integer>Gen<String>(123); 
            
            Gen<String> g4 = new <Integer>Gen<>(123); 
        }
    }
    class Gen<E>{
        //define constructor use generic
        public <T> Gen(T t){
            System.out.println(t);
        }
    }
    
    

    结论:

    • 第一行,指定了泛型类的类型是Integer,没有指定泛型构造器的具体类型,根据类型自动推断的类型是Integer。
    • 第二行,指定泛型构造器的类型是Integer,传入的实际类型是123 没有问题。
    • 第三行,指定了泛型了您先给为String,指定了泛型构造器为Integer,传入实参是123,没问题。
    • 第四行,如果声明了泛型构造器的实际类型,那么这时候千万注意,不能再使用jdk7的菱形语法了。这里会报错。不支持这种写法。其实就是不能写了泛型构造器而通过;菱形语法让编译器推断泛型类的具体类型。

    4、注意事项:

    4-1:确保数据的安全性

    我们之前已经写过类似的实例,比如在一个集合中插入数据,通过泛型可以确保数据的安全性。避免我们显示的强转,另一方面,请看一下代码,会有问题吗?

    实例代码1

    public class Test01 {
        public static void main(String[] args) {
            //create generic type Gen instance and assign
            //generic type of Gen is Integer
            Gen<Integer> g1;
            //it`s use of autoboxing to encapsulate 
            //the value 12 within an Integer Object
            g1 = new Gen<Integer>(12);
            Gen<Integer> g2;
            //there is a problem
            g2 = new Gen<Double>(12.0);
            //create eneric type Gen instance and type of Gen is String
            Gen<String> g3 = new Gen<>("test generic");
            //the compile-time error
            //Type mismatch: cannot convert from Gen<String> to Gen<Integer>
            g1 = g3;
        }
    }
    //declared a generic type Gen
    class Gen<T>{
        T t;
        public Gen(T t) {
            this.t = t;
        }
        public void getT(){
            System.out.println(t.getClass().getName());
        }
    }
    

    结论:

    以上代码编译会出错,当然这中类型检查其实也是泛型的优点之一,可以确保类型安全,当然从jdk1.7之后创建实例时可以通过类型推断直接将对象的类型推断出来,不需要在后面继续添加泛型类型了,因为两个尖括号放在一起很像一个菱形,所以也叫菱形语法。而且1.8之后对于泛型的类型推断有进一步增强了。其次到最后的g3赋值给g1时,虽然类型都是Gen对象,但是泛型的具体类型不同,所以是无法正常赋值的。

    实例代码2(相同的代码不加入泛型)

    public class Test01 {
        public static void main(String[] args) {
            //create non-generic type NonGen instance
            //it`s use of autoboxing to encapsulate 
            //the value 12 within an Integer Object
            NonGen ng1 = new NonGen(12);
            //show the type of data used by ng1
            ng1.show();
            //get the value of ng1,a case is necessary
            Integer in = (Integer) ng1.get();
            System.out.println(in);
            //create other NonGen instance and store a string in it;
            NonGen ng2 = new NonGen("test non-generic");
            //show the type of data used by ng2
            ng2.show();
            //get the value of ng2 again and case is necessary
            String str = (String) ng2.get();
            System.out.println(str);
            //the compile-time is right and No syntax errors 
            //but there are semantic issues 
            ng1 = ng2;
            in = (Integer) ng1.get();// run-time exception java.lang.ClassCastException:
            System.out.println(in);
        }
        
    }
    //declared a non-generic type Gen
    class NonGen{
        Object obj;//use object replace generic
        public NonGen(Object obj) {
            this.obj = obj;
        }
        public void show(){
            System.out.println(obj.getClass().getName());
        }
        public Object get(){
            return obj;
        }
    }
    
    

    结论:

    如果不加入泛型,这里我们要做大量的类型转换。并且到最后我们看到的这个代码,因为都是NonGen的实例对象,所以它们之间是可以互相赋值的。虽然在语法层面讲没有问题,但是语义上是有问题的,因为下面通过get方法获取且强转时,因为本身ng2存储的是字符串对象,而这里赋值给了ng2对象变量,再通过get()方法获取时,获取到的还是String对象,强转为Integer报错。所以泛型可以保证数据数据的安全性,将运行时异常变成了编译时错误

    本内容为坑王社群原创内容,如有疑问:请咨询微信:lukun0402

    4-2:泛型中能使用基本数据类型

    请看下面代码:

    public static void main(String[] args) {
            //Syntax error, insert "Dimensions" to complete ReferenceType
            List<int> ls = new ArrayList<>();
            ls.add(123.123);
            
        }
    

    结论:

    这里编译出错,不能这样写。需要插如的是一个引用类型。很多人回想那我需要插如一个int数据怎么办呢?其实通过包装类就可以完成。而且包装类在某些时候确实要更加方便,当然包装类和基本数据类型的内容要展开说,还是有很多坑,我们下次再填补。

    4-3:不同类型的泛型对象能互相转换

    我们在泛型确保安全性上面已经阐述过,这里在简单强调一下:

    //the compile-time error
    //Type mismatch: cannot convert from Gen<String> to Gen<Integer>
    g1 = g3;
    

    统一类型(比如都是Gen泛型类的对象)不同的泛型类型(g1是泛型类型是String,g2是Integer类型)不是兼容类型,这点一定要注意。

    结论:泛型能够确保类型安全,其实用一句话简单概括就是:只要通过使用泛型不存在编译时的警告,那么就不会出现运行时的ClassCastException;【注意编译都不出警告。报错更不会了,这个很好理解吧】

    4-4: 反射对于泛型的影响:

    测试用例:

    public static void main(String[] args) throws Exception{
            List<String> ls = new ArrayList<>();
            //compile-time error not applicable for the arguments (Integer)
            ls.add(new Integer(12));
            //Gets the Class object filler value 
            Class clz = ls.getClass();
            Method m = clz.getMethod("add",Object.class);
            m.invoke(ls, new Integer(12));
            //out 12 in this list
            System.out.println(ls);
        }
    

    结论:

    通过反射可以在运行期间填充泛型没有指定的类型数据。因为泛型其实是一个编译器行为,而反射是运行期行为。所以我们通过反射可以在运行期间动态的往集合中填充泛型未指定的数据类型。但是如果直接填充Integer对象,在编译器就会报错。

    4-5:测试并不存在的泛型类型

    测试用例:

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

    结论:

    对于java来说,不论泛型的类型具体是什么,运行期间两个List都拥有同一个List对应的Class对象。所以无法再一个泛型类,接口中定义静态的内容去直接使用泛型中的定义的泛型类型,比如,不允许下面代码出现:

    测试用例:

    class Gen<T>{
        static T t;//Cannot make a static reference to the non-static type T
    }
    

    4-6:不能使用instanceof对于泛型判定

    系统不会给每一个泛型的实例对象创建一个泛型对象,也就意味着无法使用instanceOf运算符。比如:

    测试用例:

    List<String> l1 = new ArrayList<>();
            List<Integer> l2 = new ArrayList<>();
            System.out.println(l1.getClass()==l2.getClass());
            //Cannot perform instanceof check against parameterized type
            //List<String>. Use the form List<?> instead since further 
            //generic type information will be erased at runtime
            System.out.println(l1 instanceof List<String>);
    

    结论:

    我们泛型以上的内容,大概是说是不能用instanceOf去检查参数化类型的,也就是我们的泛型。这样不允许做。因为泛型类型的类型信息在运行期就会被抹去。也叫类型擦除。

    5、泛型边界问题

    5-1: 类型通配符:

    测试用例:编写一个方法用来遍历当前集合中的元素:

    public static void main(String[] args) throws Exception{
            List<String> ls = new ArrayList<>();
            showAll(ls);
        }
        
        public static void showAll(List ls){
            for(int i = 0;i<ls.size();i++){
                System.out.println(ls.get(i));
            }
        }
    

    这个方法本质上没有问题,但是注意这个方法在编译时会出警告,需要指定showAll方法中的参数化类型,其实就是指定List局部变量的泛型类型。改进一版如下:

    测试用例1:

    public static void main(String[] args) throws Exception{
            List<String> ls = new ArrayList<>();
            //compile-time error
            showAll(ls);
        }
        
        public static void showAll(List<Object>ls){
            for(int i = 0;i<ls.size();i++){
                System.out.println(ls.get(i));
            }
        }
    

    这个结论我们在上面已经说过了,那这个时候如何解决呢?

    测试用例2

    public static void main(String[] args) throws Exception{
            List<String> ls = new ArrayList<>();
            //compile-time error
            showAll(ls);
        }
        
        public static void showAll(List<?>ls){
            for(int i = 0;i<ls.size();i++){
                System.out.println(ls.get(i));
            }
        }
    

    结论:

    我们将?称之为通配符,占位符。注意最后经过类型擦除以后,我们也可以说?是Object,但是注意,我们这里其实将List<?>理解为所有List泛型的父类其实更好明白一点。但是这里还是有问题的,我们在showAll方法做操作:

    测试用例3

    image

    这个例子特别有意思:

    1、虽然我们说通过<?>带通配符的方式可以传入任何List对象,不论具体的泛型类型是什么,但是在编译阶段也就导致,List<?> ls也无法确定集合中的具体的类型是什么,因为我们查看List中的源码,public boolean add(E e) {},这里必须传入一个E类型的子类或者时候E类型,但是?是无法确定,所以无法传入。所以你也无法传入一个Object对象。

    2、但是我们可以传入null值,因为它是所有引用类型的实例。

    3、那为什么我们调用get()方法可以?我们查看get方法的源码public E get(int index) {};我们发现这个返回的时一个E类型,未知类型,那么肯定是一个Obejct,我们输出会自动调用该方法的toString,所以没有问题,我们甚至可以获取到之后赋值给一个Object类型的变量,也没有问题,但是如果要赋值给一个其他类型,要小心了,因为不可避免的可能会出现类型转换异常。

    5-2: 泛型的上限问题:

    测试用例:

    public class Test01 {
        public static void main(String[] args) throws Exception{
            List<F> lsF = new ArrayList<>();
            showAll(lsF);
            List<S1> lsS1 = new ArrayList<>();
            List<S2> lsS2 = new ArrayList<>();
            //compile-time error 
            showAll(lsS1);
            showAll(lsS2);
            
        }
        public static void showAll(List<F> ls){
        }
    }
    class F {}
    class S1 extends F{}
    class S2 extends F{}
    
    

    结论:

    其实这个结论上文已经提到过了,编译期间List<S1>和List<F>并不是一会事情。一个泛型实例指定了不同的泛型类型,这里不能进行互相转换。如果用?通配符,似乎能解决问题。但是不能描述清楚S1、S2和F的关系。那这里怎么办呢?我们采用泛型的上限解决该问题。? extends F。这里?代表传入的泛型对象的实际泛型类型,查看当前类型是否继承自F,如果继承就可以直接传入。

    public static void showAll(List<? extends F>){}
    
    

    但是注意:

    public static void showAll(List<? extends F> ls){
            ls.add(new S1());
    }
    

    这个代码不能这样写。因为传入的实际类型不确定,导致无法在集合中再添加元素,和我们之前测试的泛型通配符遇到的问题是一致的。有时候我们还会这样写:

    class Stack <T extends Number & Serializable>{
        
    }
    

    声明一个Stack类,该类中的泛型声明为必须是Number的子类,而且还需要实现Serializable接口。和我们想象的一样,这里的接口的是可以实现多个的。实现多个用&连接。

    5-3:泛型下限

    如果你还记得泛型方法,应该知道我们写了方法,将一个数组中的元素添加到集合中。我们在上文也提到可以通过下限操作。接下来我们编写一个实例。完成集合到集合拷贝,也就意味着src集合中的类型要完全兼容dest集合中的类型。

    public static void main(String[] args) {
            Collection<Integer> dest = new ArrayList<>();
            Collection<Number> src = new ArrayList<>();
            fillIntoColls(dest,src);
        }
        public static <T> void fillIntoColls(Collection<T> dest ,Collection<? super T> src){
            for(T t:dest){
                src.add(t);
            }
        }
    

    这里注意泛型Collection<? super T> src 表示最小是T类型,或者是它的父类。实际传入的T是Integer类型,而这里泛型下限的值是Number。这里面采用的泛型方法和泛型的下限解决的。

    5-4:泛型擦除

    测试用例:

    public class Test01 {
        public static void main(String[] args) {
            Gen<Integer> g1;
            g1 = new Gen<Integer>(12);
            Gen<Integer> g2;
            Gen<String> g3 = new Gen<>("test generic");
            //Type erasure followed by conversion
            Gen gen = g1;
            Gen<String> g4 = gen;
        }
    }
    //declared a generic type Gen
    class Gen<T>{
        T t;
        public Gen(T t) {
            this.t = t;
        }
        public void getT(){
            System.out.println(t.getClass().getName());
        }
    }
    

    结论:最开始定义的g1包含了泛型类型为Integer类型,然后将g1赋给了一个gen对象,编译器在这里会丢失掉g1本身的Integer的泛型信息。这就擦除,其实我们可以理解为泛型类型由Integer变为了Object。java允许给一个对象赋给一个具体的泛型类型。这里只会包检查警告,但是如果真的通过g4做一些操作,还是会有可能出现异常的。

    6、jdk8增强的泛型类型推断

    测试代码:

    public class Test01 {
        public static void main(String[] args) {
            Gen<Integer> g = new Gen<>();//1
            Gen<String> g2 = Gen.test01();//2
            Gen.test02(123, new Gen<String>());// 3compile-time error
            Gen<Integer> g3 = Gen.test02(123, new Gen<>());//4
        }
    }
    //declared a generic type Gen
    class Gen<T>{
        public static <Z> Gen<Z> test01(){
            return null;
        }
        public static <Z> Gen<Z> test02(Z z,Gen<Z> g){
            return null;
        }
        public T test03(){
            return null;
        }
    }
    

    结论:
    1、第一行代码创建对象时指定泛型类型,后续通过菱形语法直接输出。这是jdk1.7就支持的
    2、第二行代码调用test01方法,没有指定泛型类型,但是通过调用方法之后可以,显示指定了泛型类型是String类型,推断出test01方法的Z泛型类型是String类型
    3、编译报错,因为调用方法是传入的时Z泛型指定的类型是Integer,而传入的Gen对象的泛型类型变为了String类型,所以编译报错。
    4、第四行代码么问题,因为调用test02方法时传入的Z类型指定是Integer类型,显式的指定返回的Gen类型也是Integer,可有推断出传入的Gen泛型类型是Integer类型,所以直接使用菱形语法。

    注意类型推断不是万能的。比如下面的代码

        //在上面的main方法加入以下代码会报错
        String str = Gen.test01().test03();
    
    

    这里并不能推断出test01方法返回的时Gen<String>,所以也无法推断出test03方法返回的时String类型。如果要推断,可以通过以下的方式:

     String str = Gen.<String>test01().test03();
    

    7:拓展jdk10增强的局部变量的类型推断

    注意:这里的局部变量的类型推断和泛型无关,主要作为扩展知识。建议使用
    Intellij IDEA 2018.1.1以上版本。可以知识jdk10。或者是JShell
    

    测试实例:这里是基于JShell

        jshell> /list
    
       1 : var lists = new ArrayList<String>();
       2 : lists.add("hello");
       3 : lists.forEach(System.out::println);
    

    结论:允许通过使用var类型进行类型的声明。会自动推断出当前var的类型是ArrayList:

    jshell> System.out.println(lists.getClass());
    class java.util.ArrayList
    
    

    但是注意,var是一个保留字,而不是关键词,也以为着你可以通过以下编码:

    jshell> var var = 10;
    var ==> 10
    
    

    但是这里注意,不能通过var去声明类,接口等。可以作为变量名或者是方法名。

    jshell> interface var{}
    |  错误:
    |  从发行版 10 开始,
    |    此处不允许使用 'var', 'var' 是受限制的本地变量类型, 无法用于类型声明
    |  interface var{}
    |            ^
    
    jshell> class Var{}
    |  已创建 类 Var
    jshell> /list
    
       1 : var lists = new ArrayList<String>();
       2 : lists.add("hello");
       3 : lists.forEach(System.out::println);
       4 : System.out.println(lists.getClass());
       5 : var var = 10;
       6 : class Var{}
       7 : class Test{void var(){}}
    
    

    以上算是对于10 的一些尝鲜吧,其实这只是冰山一角,我们后续继续填坑。嘿嘿,挖坑我们是认真的。

    相关文章

      网友评论

        本文标题:关于泛型你要的所有内容都在这里了!

        本文链接:https://www.haomeiwen.com/subject/pqxgbftx.html