Java 泛型从精通到陌生

1.什么是泛型

Java 泛型从精通到陌生_第1张图片

泛型即参数化类型,使用时通过传入具体的类型

2.为什么用泛型

  • 在编译时进行更强的类型检查
  • 代码无需强转
  • 可读性更好
  • 适用与多种数据类型执行相同的代码

3.如何使用泛型

下面例子中 的类 关系

C–继承–>B–继承–>A

public class A<T>{
     
    
}
  • <> 泛型标识
  • T 泛型类型 (可以任意命名,命名规范:大写)

3.1 泛型作用范围

static class A<T>{
     
    
}

继承:

  • 继承父类时不带泛型
 static class B extends A{
     
 
 }

继承父类时不带泛型时,A类的T因为类型擦除会变为Object

  • 继承父类时指定泛型为不明确类型
 static class B<T,TT,TTT> extends A<T>{
     
 
 }

当继承父类时指定泛型为不明确类型时,需要子类带一个与 继承的父类的泛型类型名称相同,这样在new B指定泛型时会一并指定了A的,如何B的<>不带这个T,那么将会报错,因为没人指定A的T

可以理解为 继承父类时指定泛型为不明确类型时,具有延迟加载的性质,需要子类指定

在这里插入图片描述

  • 继承父类时指定泛型为明确类型
 static class B extends A<String>{
     
 
 }

  • 继承父类时泛型带关系
    static class A<T extends A>{
     
      
    }

    static class B extends A<B(需要符合 T继承A)>{
     
      
    }

问题:

为什么List不能传递给List?

泛型具有子类型化的规则,List是原生类型List的一个子类型,而不是参数化类型List的子类,那么如何才能传递呢!接下来的通配符会解释道。

接口

    interface D<T>{
     

    }

接口的继承和实现 与泛型类继承的规则一样

方法

    static class A<T>{
     
        public <T1> void set(T1 t1){
     
            System.out.println(t1);
        }
    }

调用:

        A<Object> a = new A<>();
        a.<String>set("hh");

如果 T与T1名称相同 ,方法里面的T就像局部变量 和类上面的互不干涉

    static class A<T>{
     
        public <T> void set(T t1){
     
            System.out.println(t1);
        }
    }

调用:

        A<String> a = new A<>();
        a.<Integer>set(1);

作用在返回值

 static class A<T>{
     
        public  T set(T t1){
     
            System.out.println(t1);
            return t1;
        }
    }

调用:

        A<String> a = new A<>();
        String s = a.set("");

不用强转,以后走路都带风

泛型方法的类型推断

如果 泛型方法 有多少个泛型类型参数,调用时不指定泛型类型,那么返回值的类型是什么?

public  static <T> T set(T x,T y){
     
        return y;
}
        String v1 = Main.set("", "");
        Number v2 = Main.set(1, 1.2f);
        A v3 = Main.set(new A<>(), new B<>());
        Integer v4 = Main.set(1, 1);

会取同一父类中最小级的

3.2泛型类型

常用泛型 说明
E Element 表示在集合中存放的元素
T Type 表述Java类
K Key 映射-键
V Value 映射-值
N Number 数值类型

3.3 通配符

表示未知类型

使用范围:

  • 参数
  • 字段
  • 局部变量

不能用作泛型方法调用,泛型类的类型参数

无限

表示未知类型

泛型上限

关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类

        List<? extends A> l=new ArrayList<>();
        List<B> l1=new ArrayList<>();
        l=l1;
泛型下限

关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

        List<? super B> s=new ArrayList<>();
        List<A> s1=new ArrayList<>();
        s=s1;

问题:

1.为什么需要泛型上下限?

我们之前提到 泛型具有子类型化的规则,List是原生类型List的一个子类型,而不是参数化类型List的子类,那么我如何才能把List传过去呢?

        List<? extends Object> e=new ArrayList<>();
        List<String> e1=new ArrayList<>();
        e=e1; //编译成功

就是为了解决泛型的多态

有的好奇宝宝可能还发现了其他问题 为什么 我e.add("")不能添加了!直接编译错误! 往下看PECS原则!! 隔着探案呢…一环扣一环

3.4 PECS原则

Producer Extends Consumer Super extends 为生产者,super为消费者

生产者顾名思义 产出东西的 对应 List的get,消费这就是买入东西的 List的add;

问题:

1.为什么 < ? extends >只能读取 不能传入,< ? supe> 传入不需要读取?

< ? extends >

        List<? extends A> l=new ArrayList<>();
        List<B> l1=new ArrayList<>();
        l=l1;

A的子类有B和C,如果可以添加

        List<? extends A> l=new ArrayList<>();
        List<B> l1=new ArrayList<>();
        List<C> l2=new ArrayList<>(); 
        l=l1;  
        l.add(new B());
        l.add(new C());

l 引用的是 l1 我 l1 只能添加类型为B的,你确可以添加个C,那我 l1 的泛型有什么用,所以不能添加

不管你l是等于 l1 还是 l2 里面添加的都是A的子类,所以可以读取返回父类A

< ? supe >

可以添加但是只能添加当前类和其子类,因为编译器无法判断究竟是哪个超类,避免像Serializable也可以添加进去,所有编译器直接静止添加其父类,当前类和子类都是可以确定的所有可以添加!

        List<? super Number> v1 = new ArrayList<Object>();
        List<? super Number> v2 = new ArrayList<Serializable>();
        List<? super Number> v3 = new ArrayList<Number>();

建议:

以集合为例子

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)

如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)

如果既要存又要取,那么就不要使用任何通配符。

4.泛型原理

1.类型擦除

Java中的泛型是伪泛型,即在语法上支持泛型,但在编译阶段进行类型擦除保留为原始类型

原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

2.类型检查

  ArrayList<String> objects = new ArrayList<>();
        objects.add("");
        objects.add(1);

报错:

java: 对于add(int), 找不到合适的方法
    方法 java.util.Collection.add(java.lang.String)不适用
      (参数不匹配; int无法转换为java.lang.String)

类型检查只针对它的调用者

        ArrayList objects = new ArrayList<String>();
        objects.add("");
        objects.add(1);

编译通过

     new ArrayList<String>().add(1);

编译不通过

3.类型强转

我们看一下

  ArrayList<String> objects = new ArrayList<>();
        objects.add("");
        String s1 = objects.get(0);

字节码

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String
        11: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        14: pop
        15: aload_1
        16: iconst_0
        17: invokevirtual #6                  // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
        20: checkcast     #7                  // class java/lang/String
        23: astore_2
        24: return
      LineNumberTable:
        line 49: 0
        line 50: 8
        line 51: 15
        line 52: 24
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      25     0  args   [Ljava/lang/String;
            8      17     1 objects   Ljava/util/ArrayList;
           24       1     2    s1   Ljava/lang/String;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            8      17     1 objects   Ljava/util/ArrayList<Ljava/lang/String;>;
}
SourceFile: "main.java"

Process finished with exit code 0

我们看到 Code表里面 序号20 是 checkcast(类型强转) #7(#7 表示常量池中的) 也就是 class java/lang/String 将类型强转为 String

引入泛型最终的目的就是为了避免繁琐的强转

5.疑问

5.1 List和List和List< Object >的区别?

引用变量类型 名称 可以接收的类型 能否添加元素 安全性 便利性 表述性
List 原始类型 任意的List 可以添加任何元素 × ×
List 通配符类型 可以接收List< E >的参数化类型 包括 原始类型List 除了null 不可以添加任何元素 ×
List< Object > 实际类型为Object的参数化类型 只可以接收List和其本身类型 可以添加任何元素

5.1 为什么 泛型类型 不能用于静态变量,静态方法?

    static class C<T> extends B{
     
        
        // 编译错误 'Main.C.this' cannot be referenced from a static context 不能从静态上下文中引用
         static T t1;
        //编译错误 'Main.C.this' cannot be referenced from a static context
         public static T get(){
     
             return t1;
         }
    }

因为泛型参数实例化是在 new对象的时候指定的,而静态变量和静态方法不需要创建对象

欢迎关注公众号
Java 泛型从精通到陌生_第2张图片

参考:

https://docs.oracle.com/javase/tutorial/java/generics/index.html

https://www.pdai.tech/md/java/basic/java-basic-x-generic.html#%E6%B3%9B%E5%9E%8B%E7%9A%84%E4%B8%8A%E4%B8%8B%E9%99%90

https://blog.csdn.net/hanchao5272/article/details/79346471

Effective Java

群里老哥夏洛克
在这里插入图片描述

你可能感兴趣的:(Java,泛型,PECS,?,super,?,extends,泛型入门)