zoukankan      html  css  js  c++  java
  • Java笔记06

    泛型是一种"代码模板"

    什么是泛型

    • T可以是任何class. 编写一次模板, 创建任意类型的ArrayList
    • 泛型是定义一种模板, 例如: ArrayList<T>, 然后再代码中为用到的类创建ArrayList<类型>
    • 编写一次, 万能匹配, 通过了编译器又保证了类型安全

    向上转型

    • 通过应用接口, 实现向上转型
    • ArrayList<T> implements List<T>: ArrayList<T>向上转型为List<T>
    • 模板之间没有任何继承关系: ArrayList<Integer>ArrayList<Number>两者完全没有继承关系
    • 在向上继承时, T不可变!

    使用泛型

    • 使用时, 如果不定义泛型类型, 默认是Object.
    • 如果泛型定义为Object没有发挥泛型的优势.
    • 编译器自动推断, 可以省略List<Number> list = new ArrayList<>;

    泛型接口

    • 可以在接口中定义泛型类型, 实现此类接口必须实现正确的泛型类型

    编写泛型类

    • 一般来说, 泛型类用在集合类中, 我们很少编写泛型类
    • 就是这个类, 可以针对某个特定的类型, 成为一个专门的类
    • 在类后面定义一个<T>, 然后在这个类里面就有了这么一种类型T, 任意使用
    • 泛型类型<T>不能用于静态方法
    • 静态方法, 可以单独改写为"泛型"方法.
    • 静态方法的泛型, 和实例类型的泛型不是一个, 应该清楚的区分开
    public class Pair<T> {
      private T first;
      private T last;
      public Pair(T first, T last) {
        this.first = first;
        this.last = last;
      }
      public T getFirst() {
        return first;
      }
      public T getLast() {
        return last;
      }
      
      // 对于静态方法使用<T>
      public static<K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
      }
    }
    

    多个反省类型

    • 泛型可以定义多种类型.<T, K>
    • 使用的使用, 分别指出两种类型即可Pair<String, Integer>

    擦拭法

    • 泛型是一种模板技术, 不同语言实现方式不同, java是擦拭法
    • 擦拭法: 虚拟机对泛型其实一无所知, 所有的工作都是编译器做的
    • Java的泛型是由编译器在编译时实行的, 编译器内部永远吧所有类型T视为Object处理.
    • 在需要转型的时候, 编译器会根据T的类型, 自动为我们实行安全地强制类型.

    java发型的局限

    • <T>不能是基本类型, 例如int, 因为实际类型是Object.
    • 无法取得带泛型的Class: <T>Object, 无论T的类型是什么, getClass()返回同一个Class实例
    • 无法判断带泛型的Class: 不存在Pair<String>.class, 只存在唯一的Pair.class
    • 不能实例化T类型: 只能通过反射传入实现.

    不恰当的覆写方法

    • 定义的equals(T t)不会覆写成功, 因为摩擦成equals(Object t), 这就是一个覆写方法了

    泛型继承

    class IntPair extends Pair<Integer> {
      public IntPair(Integer first, Integer last) {
        super(first, last);
      }
    }
    public class Pair<T> {
      private T first;
      private T last;
      public Pair(T first, T last) {
        this.first = first;
        this.last = last;
      }
    }
    // 可以直接使用: Integer ip = new IntPair(1, 2)
    
    // 获取继承泛型类型方法
        Class<IntPair> clazz = IntPair.class;
        Type t = clazz.getGenericSuperclass();
        if (t instanceof ParameterizedType) {
          ParameterizedType pt = (ParameterizedType) t;
          Type[] types = pt.getActualTypeArguments();
          Type firstType = types[0];
          Class<?> typeClass = (Class<?>) firstType;
          System.out.println(typeClass);
        }
    
    • 因为java中引入了泛型, 所以只用Class来标识泛型不够用. 所以: java类型的体系如下:
      ┌────┐
      │Type│
      └────┘


      ┌────────────┬────────┴─────────┬───────────────┐
      │ │ │ │
      ┌─────┐┌─────────────────┐┌────────────────┐┌────────────┐
      │Class││ParameterizedType││GenericArrayType││WildcardType│
      └─────┘└─────────────────┘└────────────────┘└────────────┘

    extends通配符

    • Pair<Integer>类型, 符合参数Pair<? extends Number>: 上界通配符, 类型T上界限定在Number
    • 方法参数签名setFirst(? extends Number)无法传递任何Number类型给setFirst(? extends Number)
    • 唯一的例外可以传入null, // ok, 但是后面排除NullPointerException

    extends通配符的作用

    • 可以通过get获取一个指定类型的返回结果
    • 无法通过set进行设置和修改类型
    • 使用extends通配符表示可以读, 不能写.

    使用extends限定T类型

    • 在定义泛型类型Pair<T>的时候, 可以使用extends通配符来限定T的类型

    super通配符

    • 允许set(? super Integer)传入Integer的引用
    • 不允许调用get()方法获得Integer的引用
    • 使用<? super Integer>通配符作为方法参数, 便是方法内部代码对于参数只能写, 不能读

    对比extendssuper通配符

    • <? extends T>允许调用方法T get()获取T的引用, 但不允许调用方法set(T)传入T的引用(传入null除外)
    • <? super T>允许调用方法set(T)传入T的引用, 但不允许调用方法T get()获取T的引用(获取Object除外)
    class Collections {
      public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        //  编辑器可以避免: 意外读取dest, 意外操作src. 安全操作数组
        for (int i = 0; i < src.size(); i++) {
          T t = src.get(i);
          dest.add(t);
        }
      }
    }
    

    PECS原则

    • Producer Extends Consumer Super
    • 如果需要返回T, 它是生产者(Producer), 需要使用extends通配符
    • 如果需要写入T, 它是消费者(Consumer), 需要使用super通配符
    • 需要返回Tsrc是生产者, 声明为<? extends T>
    • 需要写入Tdest是消费者, 声明为<? super T>

    无限定通配符

    • 无限定通配符(Unbounded Wildcard Type), 即只定义一个?
    • void sample(Pair<?> p) {}
    • <?>具体作用:
      • 不允许调用set<T>方法传入引用T(null除外)
      • 不允许调用T get<>方法并获取T引用(只能获取Object引用)
      • 换句话说: 既不能读, 也不能写, 只能做一些null判断
    • 只做一些null判断, 大多数情况下, 引入泛型参数<T>消除<?>通配符
    • Pair<?>是所有的Pair<T>的超类
    Pair<Integer> p = new Pair<>(123, 456);
    Pair<?> p2 = p; // 安全的向上转型
    

    泛型和反射

    • Class<T>就是泛型
    • 调用ClassgetSuperclass()方法返回的Class类型是Class<? super T>
    • 构造方法Constructor<T>也是泛型
    Class<Integer> clazz = Integer.class;
    Constructor<Integer> cons = clazz.getConstructor(int.class);
    Integer i = cons.newInstance(123);
    
    • 可以声明但泛型的数组, 但不能用new操作符创建, 必须通过强制转型实现.
    // Pair<String>[] ps = null; // ok
    // Pair<String>[] ps = new Pair<String>[2]; // Cannot create a generic array of Pair<String>
      Pair<String>[] ps = (Pair<String> []) new Pair[2];
    
    • 使用泛型数组, 是有风险的, 必须扔到初始数组的引用, 直接进行强制转换
    class ABC<T> {
    //  T[] createArray() {
    //    return new T[5];
    //  }
      T[] createArray(Class<T> cls) { // 借助`Class<T>创建泛型数组`
        return (T[]) Array.newInstance(cls, 5);
      }
    }
    

    谨慎使用泛型可变参数

    疑问

    • intInteger两种类型的关系和区别?
    • 谨慎使用泛型可变参数中, 为什么第二个会报错的原因没看懂.
  • 相关阅读:
    Bash快捷键
    Java Web学习笔记--JSP for循环
    Python学习笔记--简介
    Java学习笔记-数组
    JavaScript学习笔记一
    Java数组
    MongoDB学习---安装配置
    Java Web学习笔记-Servlet不是线程安全的
    Java Web学习笔记-重定向Redirect
    获取汇率的BAPI
  • 原文地址:https://www.cnblogs.com/zhangrunhao/p/12655285.html
Copyright © 2011-2022 走看看