1. 创建泛型

1.1 创建

  1. //泛型类
  2. public class Wrapper<T> {
  3. T instance;
  4. public T get() {
  5. return instance;
  6. }
  7. public void set(T newInstance) {
  8. instance = newInstance;
  9. }
  10. }
  11. //泛型接口
  12. public interface Shop<T> {
  13. T buy();
  14. float refund(T item);
  15. }
  16. //多个类型
  17. public interface Map<K, V> {
  18. public void put(K key, V value);
  19. public V get(K key);
  20. }

1.2 继承

  1. public class WrapperNew extends Wrapper<Test> {
  2. @Override
  3. public Test get() {
  4. return super.get();
  5. }
  6. @Override
  7. public void set(Test newInstance) {
  8. super.set(newInstance);
  9. }
  10. }

1.3 为什么要使用泛型?

将Wrapper.class中的替换成Object:

  1. public class Wrapper {
  2. Object instance;
  3. public Object get() {
  4. return instance;
  5. }
  6. public void set(Object newInstance) {
  7. instance = newInstance;
  8. }
  9. }
  10. //使用:
  11. Wrapper wrapper = new Wrapper();
  12. wrapper.set(new Test());
  13. Test test = (Test) wrapper.get();

乍一看;从达到「目的」上,不管用不用泛型都没太大区别。仔细分析会发现使用 get()方法的时候多了一步强制转换。但是:假设set()的时候是一个TestNew而不是Test,在get()时强转为Test;此时就会报转换异常

因此得出结论:泛型可以自动强制转型

1.4 泛型常见的方式以及实例化:

  • 这个String其实就是确定T的值,也就是List的类型参数的实例化
    1. List<String> data = ArrayList<>();
    如果往一个泛型类型为String的List添加一个Int值或者非String的值,会报错
    1. List<String> data = ArrayList<>()
    2. data.add(1)//报错
    因此得出结论:泛型可以帮助检查代码中的类型、提前报错

1.5 是什么

泛型括号<>内的名称可以自定义,但不能重复,T只是一个代号

1.6 「创建一个泛型类型」到底是为了什么?

  • 本质目标或原因:这个类型的不同实例的具体类型可能会有不同,针对的是实例
  • 因此,静态字段和静态方法不能使用泛型类型的类型参数(也就是那个 T )
  • 静态字段不可以有自己的泛型,静态方法可以,称为「泛型方法」


2. 泛型类型实例化的上界与下界

2.1 上界 ?extends Xxx

设置泛型内的类型是Xxx的子类,一般在「场景化」下使用
ps:在声明时不会这么用:

  1. ArrayList<? extends Fruit> data = new ArrayList<Apple>();

使用方式:假设需要获取商店内出售货物的总重量:
1636962496437.jpg
使用?extends Xxx不能使用set()系列方法,但是可以使用get()系列方法

2.2 下界 ?super Xxx

假设代码这么写:

  1. List<Apple> data1 = new ArrayList<Fruit>();//这是报错的
  2. //使用 ?super Xxx使其不报错
  3. List<? super Apple> data2 = new ArrayList<Fruit>();
  4. data2.add(new Apple());//没有问题
  5. Apple apple = data2.get(0);//报错
  1. 也是在**「场景化**」下使用<br />![1636962460089.jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/22637940/1636962474847-83503e3b-c1b8-4e29-a12e-4e07d9f778ea.jpeg#clientId=u37d8955e-dc76-4&crop=0&crop=0&crop=1&crop=1&from=ui&height=189&id=u779ec4fd&margin=%5Bobject%20Object%5D&name=1636962460089.jpg&originHeight=526&originWidth=1552&originalType=binary&ratio=1&rotation=0&showTitle=false&size=78427&status=done&style=none&taskId=ub68db213-d27b-4097-8683-365b59aa44d&title=&width=558)<br />使用? Xxx不能使用`get()`系列方法,但是可以使用`set()`系列方法

3. 泛型方法


最典型的莫过于View的

  1. @SuppressWarnings("TypeParameterUnusedInFormals")
  2. @Override
  3. public <T extends View> T findViewById(@IdRes int id) {
  4. return getDelegate().findViewById(id);
  5. }

当一个方法需要返回「不确定」类型的值时,使用泛型方法可解决类型推导以及解决这种「不确定」性。

泛型方法的生命:

  1. interface Test {
  2. //参数和返回值一致
  3. <E> E test(E item);
  4. //或者
  5. <R> R test();
  6. }
  7. //使用
  8. TestCLass class = test.<TestClass>test(new TestClass);
  9. //泛型被实例化的时候,程序可以自动推断出类型参数的实际类型,从而不必自己指明类型
  10. TestCLass class = test.test(new TestClass);

4. 本质

到底什么时候使用泛型?
通常情况下,使用Object也能达到同样的目的,但面对多类型的时候,Object就进退两难了。所以在「不确定」类型的情况下可使用泛型。泛型的意义在于:**泛型的创建者让泛型的使用者可以在使用时(实例化时) 细化类型信息,从而可以触及到「使用者所细化的子类」的 API。 或者,泛型是「有远⻅的创造者」创造的「方便使用者」的工具**
在方法返回值上同等;不确定返回值的情况下使用泛型代替

以上文字说明的经典案例:Comparable接口,提供了compareTo方法,其参数是一个「不确定」类型

泛型的延伸用途:类型约束
可规定返回值的类型在某某范围之内(某个父类下的全部子类)

5. 场景再归纳

  1. T的场景归纳,Type ParameterType Argument
    public class Shop<T> 这个T就是Type Parameter。new Shop<Apple>这个Apple就是Type Argument
  2. ?的场景归纳
    • 只能往Type Argument的地方写
    • ?extends或者?super表示「 这个类型是什么都行,只要不超出 ? extends 或者 ? super 的限制 」
    • 不能用于Type Parameter的时候
      new Shop<? extends Xxx>(); //不能这么写
    • 类型还待确认:嵌套泛型实例化。(注意区分Parameter和Argument)
      List<? extends Shop<? extends Fruit>> shops = new ArrayList<>();
  3. extends场景归纳

?号出现的地方,对类型参数设置上界

  1. super场景归纳
    ?号出现的地方,对类型参数设置下界

    6. 类型擦除

所有的<>在运行时都会被擦除

  1. public interface Shop<T> {
  2. T buy();
  3. float refund(T item);
  4. }
  5. public interface AppleShop extends Shop<Apple> {
  6. @Override
  7. Apple buy();
  8. @Override
  9. float refund(Apple item);
  10. }
  11. //类型擦除后
  12. public interface AppleShop extends Shop {
  13. @Override
  14. Object buy();
  15. @Override
  16. float refund(Object item);
  17. }
  18. //bridge method 桥接方法
  19. //其实最终的效果如下
  20. float refund(Apple item)
  21. @Override
  22. float refund(Object item) {
  23. return refund((Apple) item)
  24. }
  25. //再比如
  26. List<String> strings = new ArrayList<>();
  27. //类型擦除后
  28. List strings = new ArrayList();

为什么会有「类型擦除」?因为「兼容性」和「性能」。兼容还没有泛型功能的JDK以及如果不用擦除的内存问题
总结:

  • 运行时,所有的 T 以及尖括号里的东⻄都会被擦除;
  • List 和 List 以及 List<Integer 都是一个类型;
  • 但是所有代码中声明的变量或参数或类或接口,在运行时可以通过反射获取到泛型信息;
  • 但但是,运行时创建的对象,在运行时通过反射也获取不到泛型信息(因为 class 文件里面没有);
  • 但但但是,有个绕弯的方法就是创建一个子类(哪怕用匿名类也行),用这个子 类来生成对象,这样由于子类在 class 文件里就有,所以可以通过反射拿到运行 时创建的对象的泛型信息。

    比如 Gson 的 TypeToken 就是这么干的。

7. Kotlin的泛型

  • 场景跟 Java 一样,不过用法有一点不一样;
  • Java 的 <? extends> 在 Kotlin 里写作 ;Java 的 <? super> 在 Kotlin 里写作 ;
  • 另外,Kotlin 还增加了 out T in T 的修饰,来在类或接口的声明处就限制使用,这样你在使用时就不必再每次都写;
  • Kotlin 的 * 号相当于 Java 的 ? 号,基本一样,只是有些细节不一样。