泛型编程

2019/02/24 00:04
阅读数 70

要点:

  •   泛型类是带有一个或多个类型参数的类
  •   泛型方法是带有类型参数的方法
  •   可以要求类型参数必须是一个或者多个类型的子类型
  •   泛型类型是不变得:当S是T的子类型是,G<S>和G<T>没有关系
  •   通过使用通配符G<? extends T>或者 G<? super T>,你可以指定一个方法接受一个带子类或父类参数的泛型类型的实例
  •   当泛型类和方法被编译时,类型参数会被擦除
  •   类型擦除对泛型有诸多限制。特别是,不能实例化泛型类或数组。不能转换成泛型类型,或者抛出一个泛型类型对象。
  •   即使泛型类和方法在虚拟机中被擦除,你也可以在运行时找出他们是如何声明的。

1、泛型类

  ※类型参数不能用基本类型实例化。例如Entry<String, int>在Java中是无效的。

  当构造一个泛型类对象时,可以在构造函数中省略类型参数。

1 Entry<String, Integer> entry = new Entry<>("1",1);

2、泛型方法

  当声明一个反洗方法时,类型参数要放在修饰符之后,返回类型之前。当调用时,不要指定类型参数。它可以从方法的参数和返回类型中推断出来。

  也可以在调用时候显示的写出来

1 public static <T> void swap(T[] array, int i, int j);
2 Arrays.<String>swap(friends,0,1);

3、类型限定

  泛型类或方法的类型参数需要满足某些要求,或者是需要能够调用一些通用的方法。可以对类型做出限定。

  T extends AutoCloseable

  类型参数可以有多个限定:  T extends Runnable & AutoCloseable

  ※可以有多个接口限定,但最多只能有一个是类。如果有一个限定是类,则它必须放在限定列表的第一位。

  子类通配符:? extends Employee  代表任意一个Employee的子类型

  父类通配符:? super Employee  代表Employee的一个父类型 (经常用作函数式对象的参数)

  一些程序员喜欢用通配的"PECS"助记码:生产者用extends,消费者用super,从List读用extends,赋值用super

4、通配符捕获

1 public static void swap(ArrayList<?> elements, int i,int j){
2     ? temp = elements.get(i); //不可行
3     ...
4 }

可以将?当做一个类型参数,但不能用作一个类型。但是可以用通配符捕获规则绕过他

public static void swap(ArrayList<?> elements, int i,int j){
    swapHelper(elements, i, j);
}

private static<T> void swapHelper(ArrayList<T> elements, int i,int j){
    T temp = elements.get(i);
    elements.set(i, elements.get(j));
    elements.set(j, temp);
}

编译器不知道?是什么,但知道它代表着某种类型,所以能调用一个泛型方法。方法的类型参数T捕获了通配符类型,这样用户就看见了一个容易理解的ArrayList<?>,而不是一个泛型方法。

5、泛型擦除

在java虚拟机中泛型类型会被擦除,它会被编译成一个原始类型。一般的T编译成Object。

如果泛型有限定,他就会被第一个限定所替代。

public class Entry<K extends Comparable<? super K> & Serializable>,V extends Serializable>
//擦除后
public class Entry{
  private Comparable key;
  private Serializable value;    
}

6、桥方法

  当使用泛型时,如果定义了与基类相同的方法,编译器会自动添加一个桥方法来调用自定义的方法P216,桥方法不仅用于泛型类型,也用在实现协变的返回类型。

7、泛型的约束

  1、无基本类型参数,泛型必须使用对象类型,这样为了处理Integer会出现大量的函数式接口

  2、所有类型在运行时都是原始的,需要做类型转换。

    getClass方法总是返回一个原始类型。ArrayList<String>,list.getClass()返回的是ArrayList.class。不存在ArrayList<String>.class

    在类名称中也不能有类型变量。没有T.class、T[].class或者ArrayList<T>.class

  3、不能实例化类型变量

    不能new T()或者new T[..]

public static <T>T[] repeat(in n,T obj){
     T[] result = new T[n];//错误,不能用new[...]构造一个数组
 }

为了解决这个问题,让调用者以引用的方式提供数组的构造函数

String[] greetings = Arrays.repeat(10,"Hi",String[]::new);

4、不能构造参数化类型的数组

  5、静态上下文中的类类型变量不是有效的,不能再静态变量或者静态方法中使用类型变量K和V

  6、类型擦除后的方法可能冲突

public interface Ordered<T> extends Comparable<T>{
    public default boolean equals(T value){
        //错误,擦除后与Object.equals冲突
        ...
    }
}

public class Employee implements Comparable<Employee>{
}
public class Manager extends Employee implements Comparable<Manager>{}
//这里有几个方法
public int comparaTo(Employee other)
public int compareTo(Manager other)
//但是,桥方法冲突
public int comparaTo(Object other)

8、虚拟机中的泛型类型信息

  使用TypeVariable<D>接口时泛型的,类型参数是Class<T>、Method或者Constructor<T>

//获取ArrayList类的类型变量
TypeVariable<Class<ArrayList>>[] vars = ArrayList.class.getTypeParameters();
String name = vars[0].getName();

//获得Method总的泛型类型
Method m = Collections.class.getMethod("", List.class);
TypeVariable<Method>[] vars = m.getTypeParameters();
String name = vars[0].getName();

关键点是泛型类和泛型方法的声明没有被擦除,并且你可以通过反射访问他们。

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部