Java基础知识扫盲(四)——泛型

泛型程序设计意味着代码可被不同类型的对象所重用

通配符(wildcard type),使用大写。在Java苦衷,使用变量E表示集合的元素类型,K和V分别表示表的关键字和值的类型,T/U/S表示“任意类型”

泛型类

可以看作普通类的工厂。

public class Pair<T> {
  private T min;
  private T max;
  
  public Pair() {
    min = null;
    max = null;
  }
  
  public Pair(T min, T max) {
    this.min = min;
    this.max = max;
  }
  
  public T getMin() {return min; }
  
  public void setMin(T min) {this.min = min;}
  
  public T getMax() {return max;}
  
  public void setMax(T max) {this.max = max;}
}

泛型方法

类型变量放在修饰符之后,返回类型之前。
泛型方法可以定义在普通类中,也可以定义在泛型类中。

public static <T> T getMiddle(T... a) {
  return a[a.length / 2];
}

声明泛型类型调用:
System.out.println(ArrayAlg.<Integer> getMiddle(1, 2, 3));

声明泛型类型.png

未声明泛型类型调用:
System.out.println(ArrayAlg. getMiddle(1.0, 2, 3));// 注意这里是1.0,2,3,自动封装为Number类型。

未声明泛型类型.png

多类型调用:
System.out.println(ArrayAlg. getMiddle("hello", 2, 3));

多类型时抽象为Serializable.png

类型变量的限定

对类/方法的类形变量加以约束。通过对类型变量 T 设置限定(bound) 实现。例如下面这段代码,要获取最小值,需要保证T实现了Comparable接口关键字extends

public static <T extends Comparable> T min(T[] a){
  if (a == null || a.length == 0)
    return null;
  T smallest = a[0];
  for (T num : a){
    if (smallest.compareTo(num) > 0)
      smallest = num;
  }

  return smallest;
}

为什么不是implements呢?记法<T entends BoundingType>
(1)表示T应该是绑定类型的子类型,T和绑定类型可以是类,也可以是接口。选择关键字 extends 的原因是更接近子类的概念。
(2)多个限定类型,用“&”分隔:
T extends Comparable & Serializable
(3)多个类型变量,用“,”分割
(4)可以拥有多个接口超类型, 但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个

泛型代码和虚拟机

虚拟机没有泛型类型对象——所有对象都是普通类。
类型擦除
定义的泛型类型,都会自动提供一个相应的原始类型(raw type)。原始类型名即删掉泛型类型后的名字。擦除类型变量,并替换为限定类型(无限定类型的变量用Object)。例如Pair<T>的原始类型:

public class Pair {
  private Object min;
  private Object max;
  
  public Pair() {
    min = null;
    max = null;
  }
  
  public Pair(Object min, Object max) {
    this.min = min;
    this.max = max;
  }
  
  public Object getMin() {return min; }
  
  public void setMin(Object min) {this.min = min;}
  
  public Object getMax() {return max;}
  
  public void setMax(Object max) {this.max = max;}
}

有限定类型的情况:

public class Interval <T extends Comparable & Serializable> implements Serializable{
  private T lower;
  private T upper;

  public Interval (T first, T second){
    if (first.compareTo(second) <= 0) {
      lower = first;
      upper = second;}
    else {
      lower = second;
      upper = first;
    }
  }
}

原始类型Interval,如下:

public class Interval implements Serializable{
  private Comparable lower;
  private Comparable upper;

  public Interval (Comparable first, Comparable second){
    if (first.compareTo(second) <= 0) {
      lower = first;
      upper = second;}
    else {
      lower = second;
      upper = first;
    }
  }
}

切换限定class Interval <T extends Serializable & Comparable >后会发生什么?
编译器在必要时要向 Comparable 插入强制类型转换。为了提高效率, 应该将标签(tagging) 接口(即没有方法的接口)放在边界列表的末尾。

翻译泛型表达式
擦除getFirst方法的返回类型,并返回Object类型的情况:

Pair<Employee> buddies = . . .;
Employee buddy = buddies.getFirst();

编译器自动插入Employee的强制类型转换。

翻译泛型方法
擦除类型参数,留下限定类型的情况:

public static <T extends Comparable〉T min(T[] a)
...
// 类型参数擦除后变为
public static Comparable min(Comparable[] a)
...
// DateInterval 继承 Pair 所用类型为LocalDate 
class DateInterval extends Pair{ // after erasure
  public void setSecond(LocalDate second)
}

同时在Pair中继承到的setSecond方法为:

 public void setSecond(Object second)

考虑下列语序:

DateInterval interval = new DateInterval(...);
Pair<LocalDate> pair = interval;
pair.setSecond(new LocalDate());

需要对setSecond的调用具有多态性。那么需要编译器在DateInterval 类中生成一个桥方法。

public void setSecond(Object second) {
   setSecond((Date) second); 
 }

即实际上调用的为DateInterval.setSecond(Date)方法,这个方法是合成桥方法。

注意:

  • VM中没有泛型,只有普通的类和方法
  • 所有的类型参数都用他们的限定类型/Object替换
  • 桥方法被合成来保持多态
  • 为保持类型安全性,必要时插入强制类型转换。

约束及局限性

  • 不能用基本类型代替类型参数
    Pair<Double>成立,Pair<double>不成立。
    原因:类型擦除。擦除之后,Pair含有Object类型的域。 而 Object 不能存储 double 值。


    Object 不能存储 double 值.png
  • 检查类型只适用于原始类型
    即检查mm是否是任意类型的一个Pair。同样的道理, getClass 方法总是返回原始类型

    if (mm instanceof Pair){ // 写成Pair<String> Pair<T>均会报错
      System.out.println("yes");
    }
    
getClass 方法总是返回原始类型.png
  • 不能创建参数化类型的数组
    可以声明通配类型的数组, 然后进行类型转换
Pair<String>[] pair0 = new Pair<String>[10]; // ERROR
Pair<String>[] pair = (Pair<String>[]) new Pair<?>[10];// CORRECT
  • 不能构造泛型数组
    如下:直接构造数组实例是会报错的。通过强制转换方式,运行时当Object[]引用给Comparable[]变量时,会发生类转换异常
 T[] mm = new T[2]; // ERROR
  ...改造为
public static <T extends Comparable> T[] minmax(T... a){
  T[] mm = (T[]) new Object[2]; // Compiles with warning,
  mm[0] = a[0];
  mm[1] = a[0];
  for (T word : a) {
    if (mm[0].compareTo(word) > 0)
      mm[0] = word;
    if (mm[1].compareTo(word) < 0)
      mm[1] = word;
  }
  return mm;
}
类转换异常.png

调整minmax方法为:

public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T... a){
// minmax 方法使用这个参数生成一个有正确类型的数组
  T[] mm = constr.apply(2);
...
}
...
String[] strings = Pair.minmax(String[]::new,"Hello","How","Are","You");
  • Varargs警告
    既然Java 不支持泛型类型的数组,我们传递一个泛型类型数组实例:
public static <T> void addAll(Collection<T> collection, T... ts) {
  for (T t : ts) {
    collection.add(t);
  }
}
会出现Varargs警告信息.png

为addAll方法添加下列注解之一即可:

@SafeVarargs
@SuppressWarnings("unchecked")
  • 不能实例化类型变量
    即 new T();是不存在的,本意是不希望调用new Object()的,利用Supplier优化:
public static <T> Pair<T> makePair(Supplier<T> constr) {
  return new Pair<>(constr.get(), constr.get());
}
...
Pair<String> pair1 = Pair.makePair(String::new);
  • 既不能抛出也不能捕获泛型类对象。实际上, 甚至泛型类扩展 Throwable 都是不合法的。

  • 注意擦除后的冲突。要想支持擦除的转换, 就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类, 而这两个接口是同一接口的不同参数化。

继承规则

问:Pair<Manager> 是Pair<Employee> 的一个子类吗?
答:不是。

无论S和T有什么联系,通常, Pair<S> 与 Pair<T> 没有什么联系。

泛型类可以扩展或实现其他的泛型类。就这一点而言,与普通的类没有什么区别。 例如, ArrayList<T> 类实现 List<T> 接口。这意味着, 一个 ArrayList<Manager> 可以被转换为一个 List<Manager>。但是, 如前面所见, 一个 ArrayList<Manager> 不是一个ArrayList <Employee> 或 List<Employee>

image.png

通配符类型

1.子类型限定。

Pair<? extends Employee>

表示任何泛型 Pair 类型,类型参数是 Employee 的子类,如Pair<Manager>

通配符的子类型关系.png

2.超类型限定

? super Manager

这个通配符限制为 Manager 的所有超类型。

带有超类型限定的通配符.png

例如:
计算一个 String 数组的最小值,T 就是 String类型的, 而 String 是Comparable<String> 的子类型。

public static <T extends Comparable<T>> min(T[] a)...

处理一个 LocalDate 对象的数组时, 会出现一个问题。
LocalDate 实现了 ChronoLocalDate, 而 ChronoLocalDate 扩展了 Comparable<ChronoLocalDate>。因此, LocalDate 实现的是 Comparable<ChronoLocalDate> 而不是 Comparable<LocalDate>。这种情况下,超类型来救助:

public static <T extends Comparable<? super T>> T min(T[] a) ...

int compareTo(? super T)

3.无限定通配符
Pair<?>

Pair<?> 和 Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法。一般用来测试一个Pair 是否包含一个null引用,不需要实际的类型。

反射和泛型

  1. 泛型Class类,Class<T>。例如String.class就是一个Class<String>类的对象
  2. 使用 Class<T> 参数进行类型匹配。例如下例执行makePair(Employee.class)
public static <T> Pair<T> makePair(Class<T> tClass) throws InstantiationException,IllegalAccessException{
  return new Pair<>(tClass.newInstance(), tClass.newInstance());
}
  1. 虚拟机中的泛型类型信息
public static <T extends Comparable<? super T>>T min(T[] a)
... 擦除后
public static Comparable min(Comparable[] a)

使用反射 API 来确定:

  • 有一个T的类型参数。<T>
  • T有一个子类型限定。<T extends Comparable>
  • 限定类型有一个通配符参数。?
  • 通配符参数有一个超类限定。<? super T>
  • 有一个泛型数组参数。T[]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,563评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,694评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,672评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,965评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,690评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,019评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,013评论 3 449
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,188评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,718评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,438评论 3 360
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,667评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,149评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,845评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,252评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,590评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,384评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,635评论 2 380