学习泛型完整篇

/**
   泛型(?为通配符,上限为Object类型)
一 菱形语法如下(java7开始)赋值号的左右只要一边带有泛型即可.
         List<String> list = new ArrayList();
         List list2 = new ArrayList<String>();
   1  var 声明类型也可使用菱形语法var
   2  java9增强的菱形语法: 允许在创建匿名内部类时使用菱形语法
        interface A<T>{T info();}
        var a = new Generic<String>(){public String info(){return "tom";}};
二 定义泛型接口,泛型类
 所谓泛型: 允许在定义类,接口,方法时使用类型形参(泛型),这个泛型将在声明变量,创建对象,调用方法时等实际使用时动态地指定类型实参
            重点: 动态指定泛型实参,第一次指定,将作用与那整个对象(如果在传入与泛型实参不一致的类型对象将报错).
    泛型的使用: 泛型形参终究是对象传入,1和2其实就是菱形语法,3也是通过实参对象传入,如下
        1   创建对象时指定泛型实参 Generic g1 = new Generic<String>();
        2   声明变量时指定 Generic<String> g2 = new Generic(); 或者 Generic<String> g2 = null; g2 = new Generic();
        3   调用方法时通过传入的实参对象指定: 实际上是传入地实参携带了参数类型(仅支持静态方泛型方法完整支持,而实例泛型方法的泛型会变成Object类型)
                比如static <T> void test(List<T> list){},在调用方法时 test(new ArrayList<String>);
        为什么实例方法和实例泛型方法不可以
        如果对象在创建前泛型形参没有传入实际类型,它实例类型和以及它的实例方法的返回值的类型的整个泛型信息都将被擦除,它们将变成原始类型(不用深究, 系统偷偷做了一次转为原始类型的类型转换,泛型擦除)
        单独的泛型将变成Object类型,但是实例方法的参数列表仍然保持泛型形参信息,所以传入的实参对象不会报错而已
            比如泛型返回值: List<List<T>> test(){...}调用该方法实际的返回值类型为List整个泛型被擦除了,丢掉了
            泛型实例也是如此 public class AP{List<List<T>> list;}在给list赋值后,在取出list元素结果也变成List类型了

三 继承泛型类或实现泛型接口(泛型形参的传递)
    1   没传入具体参数
            要么都加 class C<T>  implements A<T> {@Override void test(T t){}
            要么都不加泛型 class C  implements A {}
             定义时类或接口时没传入具体参数,却重写或实现的带泛型的方法,则泛型类或泛型接口和它的父子都需加上那个方法上的相同的泛型,
            否则都不要加(很好理解,因为泛型就来源与他们上级)
    2   如果父类传入具体参数,则子类都不需要指定类型参数
            class C implements A<String> {@Override void test(String t){}
    注意:
    1 传入实际形参并没有产生新的类型,只是在创建实例时把泛型替换成了实际类型而已,并没有产生新的类型,证明如下
         System.out.println(new ArrayList<Integer>() instanceof ArrayList<Integer>);//报错,并没有ArrayList<Integer>类型
         System.out.println(new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass());//true
    //当然,如果为了理解方便,完全可以把 ArrayList<String>理解成List的逻辑子类,而List<E>可以有无数个子类,但物理上并不存在这样的
    //包括后面出现的类型通配符. List<Integer>是List<? extends Number>逻辑子类,嗯,这样理解还是很科学的,但心里要清楚根本没有这两种类,它们其实都是List类.
    2 泛型不支持型变 比如List<String>并不是List<Object>的子类,如下证明
         定义 static void test(List<Object> list){}
         测试 test(new ArrayList<String>());//报错,这个错误很迷惑,要极其小心
    3 数组不一样支持型变(说白了支持类型转换)
        Number[] numbers = new Integer[]{1,32,32};
        System.out.println(numbers[0].getClass());
    4 类型通配符 ?通配符,可匹配任何对象,包括Object本类型,帮助泛型实现类型转换(伪类型转换)
            List<?> list1 = new ArrayList<String>();//?通配符,可匹配任何对象,比如String
            List<?> list2 = new ArrayList<Object>();//?通配符,可匹配任何对象,包括Object本类型
            //Set<?> List<?> Map<?,?> Collection<?>
            //添加通配符位置的元素只能添加null,null是所有引用类型的实例,此外不可以添加任何类型的对象,因为程序并不知道?代表具体是什么类型
            //获取元素时总是Object对象,只能除非你知道具体是什么类型,进行强制类型转换
    5 泛型不支持型变,说白了就是泛型不支持实际的类型转换,但当类可以进行类型转换,有了通配符,泛型可以实现伪类型转换(效果等效于类型转换)
            List<String> list3 = new ArrayList<String>();//看到了,又回到了我们常见到的
           // List<Object> list4 = new ArrayList<String>();
            //上面报错,ArrayList可以转为List,但泛型String只能是String,不可以是其他具体类型
            //只要脑海里想到上面提到的逻辑子类的概念,就很容易理解和使用了

四 泛型方法(一般指静态泛型方法)
        1 巧妙用法,定一个方法,将一个数组中的元素添加到一个集合里如下,如下没了,这是为了引出泛型方法,具体不做深究
        public static  <T> void  test(T[] t, Collection<T> collection){}
        2 实例泛型方法声明的泛型会覆盖类或接口声明的泛型
        3  静态泛型方法不可以使用类或接口上的泛型形参,因为类的加载早与创建实例,那时根本不知道传入什么类型实参.只有创建实例时才知道.

五 通配符的限定(有了通配符,泛型可以实现伪类型转换(效果等效于类型转换))
    (I) extends用法表示泛型形参的范围和x的取值一样的
            <? extends T>声明变量上(形参变量,成员变量)                 ?<=T
            <? extends Number> 声明变量上(形参变量,局部变量,成员变量)   ?<=Number
            <S extends Number>  泛型类,泛型接口,泛型方法              S~Number
            <S extends T> 泛型类,泛型接口,泛型方法                    S~T
    (I) super用法表示泛型形参的范围和x的取值一样的
            <? super T>声明变量上(形参变量,成员变量)                  ?>=T
            <? super Number> 声明变量上(形参变量,局部变量,成员变量)    ?>=Number

    注意
      1 通配符+extends限定变量的对象属于协变: 只出不进,只能取出元素,不可以添加元素
            对于协变的泛型而言,它只能调用泛型类型作为返回值类型的方法(编译器会将通配符的上限作为该方法的返回值类型输出)
            而不能调用泛型类型作为参数的方法
      2 通配符+super限定变量的对象属于型变: 只进不出,只能添加元素,取出元素当成Object变量的对象(会忘记类型,变成Object类型)
      3 为泛型形参设定多个上限(最多一个父类上限,再后面多个接口上限,接口上限在'&'符号之后)
        public class Apple<T extends Number & java.io.Serializable,java.lang.Comparable>
      4 通配符只能充当变量的泛型形参
      5 泛型形参T产生的效果,可以在不同的调用点传入不同的实际类型.注意如下区别
        void test(List<?> list){System.out.println(list.get(0));}//默认取出Object类型的对象
        void test(List<T> list){System.out.println(list.get(0));}//直接可取出T类型的对象
        test(new ArrayList<String>());
      6 指定的泛型形参,既可以添加也可取出
            List<Number> li = new ArrayList<>();
            li.add(6);
            li.add(9);
        Number number = li.get(0);

六 泛型方法和通配符的区别
    通配符:就是被用来支持灵活的子类化的(就是上面的说的伪类型转换,通配符全名为类型通配符,见名知意)
    泛型方法: 允许泛型形参被用来表示一个或多个参数之间的类型依赖关系,或方法返回值与参数之间的类型依赖关系,没有这样的依赖关系就不应该使用泛型方法

    注意: 如果形参a或返回值c的类型依赖于形参b的类型,则参数b不可以使用类型通配符.因为如果b的类型无法确定,则a或c的类型也无法确定,只能考虑泛型方法
        简而言之,被依赖的参数不可以使用通配符.只能使用泛型方法的泛型
七 泛型构造器(和泛型方法一个意思)
    可以在在创建对象时传入泛型,其他和泛型方法一模一样
    MyClass<String> mc3 = new <Integer> MyClass(5);
八 类型的自动推断
   1 通过调用方法的上下文来推断泛型的目标类型(就是通过参数类型推断的或返回值的变量类型)
   2 调用静态泛型方法也可以传入泛型实参(Good.<String>info();
九 泛型擦除和转换(都不会报错)
    (I)泛型擦除
      1 示例  List<Integer> a = new ArrayList<Integer>(); a.add(123);a.add(456);
            List<? extends Number> b;
            List c;//如下两种情况
            c = a; 当把一个带有泛型信息的对象a赋值给另一个不带泛型信息的变量b时,a所有的泛型信息都将被扔掉 Object obj = c.get(0);
            b = a; 当把一个带有泛型信息的对象a赋值给另一个带泛型信息的变量b时,该对象的泛型信息将以b泛型上限为准.Number number = b.get(0);
       2 原始类型:不带实参类型的接口或类,它泛型实参默认是泛型形参的上限.
            在定义类时声明了泛型,但使用时,声明这个类的变量却没有带泛型,则这个变量默认为它的类的泛型的上限.
            比如声明一个类 public class Apple<T extends Number>{ T t ;public Apple(T t){this.t = t;}}
            定义一个对象: Apple<Integer> apple1 = new Apple(123);
            在定义一个变量 Apple apple2 = apple1;
            //此时apple2的实参默认为Number,相当于Apple<Number>,
       3 具体理解泛型擦除
        逻辑子类对象的变量b赋值给原始类型变量a,a不会记得b对象的泛型信息,
        指的是这种泛型信息不会传递过来,会丢失,虽然我们都知道a对象,b对象其实都是是同一个对象
        虽然是同一个对象,但a对象是a类型,b对象是b类型这就是多态.
    (II) 泛型转换(原始类型赋值给给逻辑子类)
        List<Integer> li = new ArrayList<>();
        li.add(6);
        li.add(9);
        List list = li;
        // 下面代码引起“未经检查的转换”的警告,编译、运行时完全正常
        List<String> ls = list;     // ①
        // 但只要访问ls里的元素,如下面代码将引起运行时异常。
        System.out.println(ls.get(0));
        综上: 原始类型的泛型实参默认是泛型形参的上限.特别之处如下(所有带泛型的类或接口声明的变量都是原始类型)
            1 它既可以接收该类带泛型实参A的对象(泛型擦除不会报错)
            2 又可以将该变量指向的对象赋值给带泛型实参B的对象的变量(泛型转换不会报错,但运行有风险,谨慎操作)
                如果B实参类型是A实参类型的父类访问元素正常,如不是则报错

十 java不支持创建泛型数组


 * */
class A1{}
class A2 extends A1{}
class A3 extends A2{}
class A4 extends A3{}
class A5 extends A4{}
class A6 extends A5{}


class Apple<E extends A3>{
    E e;

    public E getE() {
        return e;
    }

    public void setE(E e) {
        this.e = e;
    }
    public Apple(List<E> list) { }
    public Apple() { }


}
class Generic<T> {
     List<? extends T> abcd ;
     T t;
     Apple apple;
    public T show1(T t) {
        return t;
    }

    public  T show2(T t) {
        if (t instanceof String) {
            System.out.println("------String-------" + true);
        }
        return t;
    }
    public  <K> K show3(K t) {
        return t;
    }
    public static <S> S show4(S s) {
        return s;
    }

    public List<List<T>> test(T t){
        var list = new ArrayList();
        list.add(t);
        List<List<T>> list2 = new ArrayList<>();
        list2.add(list);
        return list2;
    }
}

public class 泛型{
    public static void main(String[] args) {
        //创建对象时传入实参类型
        var g1 = new Generic<String>();
        String r1 = g1.show1("tom");
        System.out.println(r1);//tom

        //声明变量时传入实参类型
        Generic<String> g2;
        g2 = new Generic();
        String r2 = g2.show1("jerry");
        System.out.println(r2);//jerry

        //调用实例方法传入实参,无法获取实参类型
        Generic g3 = new Generic();
//        String r3= g3.show2("mary");//报错,无法直接生成String类型,既无法推断传入的对象的实例类型,默认Object类型
        String r3 = (String) g3.show2("mary");//只能强转String类型
        //show2方法内部还输出了------String-------true,说明示例方法可以通过方法实参读取泛型实参类型
        System.out.println(r3);//mary

        //调用实例泛型方法传入实参,也无法获取实参类型
        Generic g4 = new Generic();
//        String r4 = g4.show3("jack");//报错,直接赋值给String类型的变量报错,既无法推断传入的对象的实例类型,默认Object类型
        String r4 = (String)g4.show3("jack");//只能强转String类型
        System.out.println(r4);//jack

        //调用静态方泛型方法传入实参,正常
        String r5 =  Generic.show4("mary");
//        String r5 =  Generic.<String>show4("mary");//也可以在调用静态方法时传入泛型实参
        System.out.println(r5);//mary

//        动态指定泛型实参,第一次指定,将作用与那整个对象(如果在传入与泛型实参不一致的类型对象将报错).
        var aa = new A6();
        var ll = new ArrayList<A6>();
        ll.add(aa);
        Apple apple = new Apple(ll);//这条语句导致该对象的泛型的范围为A6-A3,如下语句传入A2类型对象,所以报错
        //上面语句通过ll对象传入了泛型实参A6,导致整个apple2对象的E实参为A6,效果作用于整个对象.因此如下语句报错
//       apple2.setE(new A2());


//为什么实例方法和实例泛型方法不可以
//如果对象在创建前泛型形参没有传入实际类型,它实例类型和以及它的实例方法的返回值的类型的整个泛型信息都将被擦除,它们将变成原始类型
//单独的泛型将变成Object类型,但是实例方法的参数列表仍然保持泛型形参信息,所以传入的实参对象不会报错而已
//比如泛型返回值: List<List<T>> test(){...}调用该方法实际的返回值类型为List,整个泛型被擦除了,丢掉了
//泛型实例也是如此 public class AP{List<List<T>> list;}在给list赋值后,在取出list元素结果也变成List类型了

        //如果成员变量带有泛型,则编译后整个泛型将被擦除,成员变量的泛型擦除,默认为该类型的上限
        // 如下示例abcd类型为List泛型上限为Object
        var g5 = new Generic();
        var list =  new ArrayList<String>();
        List<List<String>> list2 = new ArrayList<>() ;
        list.add("12");
        list2.add(list);
        g5.abcd = list2;//g5.abcd类型为List,此语句发生了泛型擦除
        List<Integer> abcd =  g5.abcd;//泛型转换
//        Integer integer = abcd.get(0);//编译正常,但运行报错,String不可以转换成Integer类型

        //如果成员变量类型为泛型,则编译变成Object类型
        var g6 = new Generic();
        g6.t = "吃饭了";//此时t的类型为Object;
        Object t = g6.t;
//        String t = g6.t;//编译报错,因为g6.t为Object类型
        System.out.println(t);//吃饭了

        //如果成员变量带有泛型,则编译后整个泛型将被擦除,成员变量的泛型擦除,泛型实参默认为定义该类型时指定的上限
        // 如下示例Generic的实例成员变量类型为Apple,定义时泛型上限为A3,则
        var g7 = new Generic();
        Apple<A6> apple2 = new Apple<>();
        apple2.setE(new A6());
        g7.apple = apple2;
        A3 a3 =  g7.apple.getE();//g7.apple为原始类型,它的类型Apple的在定义泛型为<E extends A3>所以泛型实参默认为A3,因此直接取出了A3类型的变量a3
        System.out.println(a3);//com.china.school.oop.A6@1e643faf

        var g8 = new Generic();
        List<HashMap<String,String>> tom =  g8.test("tom");
 //以上故意随便赋值给一个List类型的逻辑子接口,恰恰说明g8.test("tom")返回值类型为最终类型为原始类型,(系统偷偷将声明的返回值类型转为原始类型的类型转换,泛型擦除),而且该过程发生了泛型转换,
//      HashMap<String, String> stringStringHashMap = tom.get(0);
// 上面语句编译正常,但运行报错, class java.util.ArrayList cannot be cast to class java.util.HashMap

        //下面演示泛型擦除
        List<Integer> a = new ArrayList<Integer>();
        a.add(123);
        a.add(456);
        List<? extends Number> b;
        List c;
        b = a;//泛型的擦除
        c = a;//泛型的擦除
        Number num1 = b.get(0);//b变量具有泛型信息,但只能转为泛型的上限Number
        Object num2 = c.get(0);//c变量没有泛型信息,只能转为Object类型
        Integer num3 = a.get(0);
        System.out.println(num1);//123
        System.out.println(num2);//123
        System.out.println(num3);//123

        //下面演示泛型转换
        a = c;//泛型转换
        b = c;//泛型转换
        Integer num4 = a.get(0);
        Number num5 = b.get(0);
        System.out.println(num4);//123
        System.out.println(num5);//123



    }

}

运行结果如下

tom
jerry
------String-------true
mary
jack
mary
吃饭了
com.china.school.oop.A6@3b6eb2ec
123
123
123
123
123

posted @ 2022-05-17 19:36  -和时间赛跑-  阅读(27)  评论(0编辑  收藏  举报