0%

Java 泛型(三):细节和局限性

1. 怎样理解泛型的局限性

  • 泛型的局限性主要与 Java 的实现机制有关
  • Java 中泛型是通过类型擦除来实现的,类型参数在编译时会被替换为 Object限定的上界
  • 运行时 Java 虚拟机不知道泛型这回事,这就带来了很多局限性

2. 下面写法是否合法,为什么,解决方法是

1
Pair<int> minmax = new Pair<int> (1, 100);
  • 结论:不合法
  • 原因:类型参数在编译时会被替换为 Object,所以 Java 泛型中不能使用基本数据类型
  • 解决方法:使用基本数据类型对应的包装类

3. 下面写法是否合法,为什么

1
Pair<Integer>.class;
  • 结论:不合法
  • 原因:这个泛型对象(反射)只有一份,与泛型无关

4. 下面代码的输出是什么,为什么

1
2
3
4
Pair<Integer> p1 = new Pair<Integer> (1, 100);
Pair<String> p2 = new Pair<String> ("hello", "world");
System.out.println(Pair.class == p1.getClass()); //true
System.out.println(Pair.class == p2.getClass()); //true
  • 结果:都是 true
  • 原因:一个泛型对象的 getClass() 方法的返回值与原始类型对象是相同的

5. 下面代码是否合法,为什么

1
2
3
4
5
// 代码1
if(p1 instanceof Pair<Integer>)

// 代码2
if(p1 instanceof Pair<?>)
  • 结果:代码 1 不合法;代码 2 合法
  • 原因:instanceof 关键字后面是接口或类名,instanceof 是运行时判断,也与泛型无关,但支持通配符

6. 下面代码是否合法,为什么,解决方法是

1
2
3
class Child extends Base Implements Comparable<Child> {
// 主体代码
}
  • 结论:Java 编译报错

  • Comparable 接口不能被实现两次,且两次实现的类型参数还不同,一次是 Comparable<Base>,一次是 Comparable<Child>。因为类型擦除后,实际上只能有一个

  • 解决方法:只能重写 Base 类的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Child extends Base {
    @Override
    public int compareTo(Base o) {
    if(!(o instanceof Child)) {
    throw new IllegalArgumentException();
    }
    Child c = (Child) o;
    // 比较代码
    return 0;
    }
    //其他代码
    }

7. 下面定义重载方法是否正确,为什么

1
2
public static void test(DynamicArray<Integer> intArr)
public static void test(DynamicArray<String> strArr)
  • 结论:错误
  • 原因:重载方法后,因为类型擦除,它们的声明是一样的

8. 下面代码是否合法,为什么

1
2
3
//T 是类型参数
T elm = new T();
T[] arr = new T[10];
  • 结论:非法
  • 原因:因为如果允许,那么用户会以为创建的就是对应类型的对象,但由于类型擦除,Java 只能创建 Object 类型的对象
    • Java 运行时根本不知道 T 是什么,也就无法创建 T 类型的对象。容易引起误解,所以 Java 干脆禁止这么做
    • 即:Java 不支持通过类型参数创建对象

9. 接上题,如果确实希望根据类型创建对象呢,举个栗子

  • 需要设计 API 接受类型对象,即 Class 对象,并使用 Java 中的反射机制

  • 如果类型有默认构造方法,可以调用 ClassnewInstance() 方法构建对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static <T> T create(Class<T> type) {
    try {
    return type.newInstance();
    } catch (Exception e) {
    return null;
    }
    }

    Date date = create(Date.class);
    StringBuilder sb = create(StringBuilder.class);

10. 下面代码是否合法,为什么

1
2
3
4
5
6
7
8
9
public class Singleton<T> {
private static T instance;
public synchronized static T getInstance() {
if(instance == null) {
//创建实例
}
return instance;
}
}
  • 结论:非法
  • 原因:如果合法,那么对于每种实例化类型,都需要有一个对应的静态变量和方法。但由于类型擦除Singleton 类型只有一份,静态变量和方法都是类型的属性,且与类型参数无关。所以,对于泛型类声明的类型参数,可以在实例变量和方法中使用,但在静态变量和静态方法中是不能使用的
  • 注意:对于静态方法,它可以是泛型的,可以声明自己的类型参数,这个参数与泛型类的类型参数是没有关系的

11. Java 泛型支持多个上界吗,举个栗子

  • 支持,多个上界之间以 & 分隔
  • DemoT extends Base & Comparable & Serializable;
    Base上界类ComparableSerializable上界接口
  • 如果有上界类,类应该放在第一个,类型擦除时,会用第一个上界替换

12. 下面代码是否合法,为什么

1
2
3
Pair<Object, Integer> [] options = new Pair<Object, Integer> [] {
new Pair("1 元", 7), new Pair("2 元", 2), new Pair("10 元", 1)
}
  • 结论:Java 编译报错
  • 原因:Java 不支持创建泛型数组

13. 下面代码是否合法,为什么

1
2
3
Integer[] ints = new Integer[10];
Number[] numbers = ints;
Object[] objs = ints;
  • 结论:合法
  • 原因:数组是 Java 直接支持的概念,它知道数组元素的实际类型,知道 ObjectNumber 都是 Integer 的父类型,所以后面两种赋值都是允许的

14. 下面代码是否正确,为什么

1
2
3
Integer[] ints = new Integer[10];
Object[] objs = ints;
objs[0] = "hello";
  • 结论:编译没有问题,运行时抛出 ArrayStoreException 异常
  • 原因:因为 Java 知道实际的类型是 Integer,所以写入 String 时会抛出异常

15. Java 为什么不支持创建泛型数组

  • 因为如果 Java 支持创建泛型数组,那么可能会出现类型不匹配的数组赋值的错误操作它既不会引起编译错误,也不会立即触发运行异常,却相当于埋下一颗定时炸弹💣,不定什么时候爆发
  • 为避免这种情况,Java 干脆就禁止创建泛型数组

16. 现实中需要能够存放泛型对象的容器,怎么办

  • 使用原始数组。比如

    1
    2
    3
    Pair[] options = new Pair[] {
    new Pair<String, Integer> ("1 元", 7), new Pair<String, Integer> ("2 元", 2), new Pair<String, Integer> ("10 元", 1)
    };
  • 更好的选择是,使用泛型容器。比如

    1
    2
    3
    4
    DynamicArray<Pair<String, Integer>> options = new DynamicArray<> ();
    options.add(new Pair<String, Integer> ("1 元", 7));
    options.add(new Pair<String, Integer> ("2 元", 2));
    options.add(new Pair<String, Integer> ("10 元", 1));

17. 有时,我们希望转换泛型容器为一个数组。比如,对于 DynamicArray,我们可能希望它有这么一个方法:public E[] toArray(),而希望可以如下这么用。即,先使用动态容器收集一些数据,然后转换为一个固定数组,对于这个常见的合理的需求,该怎样实现这个 toArray() 方法

1
2
3
4
DynamicArray<Integer> ints = new DynamicArray<Integer> ();
ints.add(100);
ints.add(34);
Integer[] arr = ints.toArray();
  • 可以利用 Java 中的运行时类型信息和反射机制

  • Java 必须在运行时知道要转换成的数组类型,类型可以作为参数传递给 toArray() 方法,比如

    1
    2
    3
    4
    5
    6
    7
    public E[] toArray(Class<E> type) {
    Object copy = Array.newInstance(type, size);
    System.arraycopy(elemendData, 0, copy, size);
    return (E[]) copy;
    }

    Integer[] arr = ints.toArray(Integer.class);

18. 泛型和数组的关系

  • 一般并不需要特别记忆,根据提示进行正确处理即可
    • Java 不支持创建泛型数组
    • 如果要存放泛型对象,可以使用原始类型的数组,或者使用泛型容器
    • 泛型容器内部使用 Object 数组,如果要转换泛型容器为对应类型的数组,需要使用反射
-------------------- 本文结束感谢您的阅读 --------------------