随笔 - 148  文章 - 3  评论 - 2  阅读 - 11万

读后笔记 -- Java核心技术(第11版 卷I ) Chapter8 泛型程序设计

网上另一篇相关的描述链接:https://blog.csdn.net/wow1024/article/details/118601283

复制代码
泛型程序设计目的:编写的代码可以对多种不同类型的对象重用。

早期 ArrayList 类维护的 Object[],在类型强转时可能出错 => 现在,有了类型参数就可以确定类型了,如 var files = new ArrayList<String>;

Type parameters(类型参数):
1) Element type supplied in <...>, like this: 
  ArrayList<String> files = new ArrayList<>();

2) No cast required for calling get:
  String filename = files.get(0);

3) Illegal arugment to add caught at compile time, better than runtime error when type cast:
  files.add(new File("..."));     // Error, type mismatch
复制代码
复制代码
// 泛型的主要内容:
8.2:定义泛型类 -- 类接受任意元素类型
8.3:定义泛型方法 -- 方法接受任意类型
8.4:类型变量的限定 -- 使用 "extends" 对 类(8.2)和方法(8.3)的任意类型进行限定,这里的 extends 相当于其子类的意思
8.7:泛型继承 -- Pair<Employee> 和 Pair<Manager> 没有任何关系
8.8:通配符 ? -- 相对于 8.7,有更多功能
Pair<? extends Employee> 通配符的子类型限定,允许传参 Pair<Employee> 和 Pair<Manager>
Pair<? super Manager> 通配符的超类型限定,允许传参 Pair<Employee> 和 Pair<Object>
8.5:泛型代码和虚拟机 -- 因为虚拟机没有泛型类型,所以最终 T 都被转换成原始类型
8.6:限制与局限性 -- 泛型使用的一些局限

复制代码
返回目录
返回目录
返回目录
返回目录
返回目录
返回目录

8.2 定义简单泛型类

复制代码
Generic Class = Class with type variables(有类型变量的类)

// 定义 Pair 类:
public class Pair<T, U> {...} // T、U 组成二元组类型 类型变量使用大写,且比较短 E: 集合的元素类型; K、V: 表的关键字与值的类型; T/U/S: 任意类型

用 具体的类型 替换 类型变量 就可以 实例化(instantiate)泛型类型,如:Pair<String> 替换 Pair<T>。换句话说,泛型类 相当于 普通类的工厂
复制代码
复制代码
// 泛型 Pair 类的代码
class
Pair<T> { // The T in public class Pair<T> is a type variable. private T first; private T second; public Pair() { first = null; second = null; }   // 无参构造器 public Pair(T first, T second) { this.first = first; this.second = second; } // 有参构造器 public T getFirst() { return first; }
public T getSecond() { return second;}
public void setFirst(T first) { this.first = first; }
public void setSecond(T second) { this.second = second; } }
复制代码

 


8.3 泛型方法

1. Generic Method = Method with type variables(有类型变量的方法)

2. 类型变量 放在修饰符后,返回类型前;

   public static <T> T getMiddle(T...a)

  • static:修饰符
  • <T>:类型变量
  • T:返回类型
  • getMiddle:一个泛型方法(泛型方法可在泛型类中,也可不在泛型类中)
  • T...a:T 数组

3. 泛型方法 可定义在 普通类 或 泛型类 中; 

复制代码
class ArrayAlg {             // 该类不是泛型类
    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }
}

// 针对方法的调用,因为可以推导出类型,所以可以简化调用。原始的调用是: ArrayAlg.<String>getMiddle("John", "Q.", "Public");
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");
复制代码

 


8.4 类型变量的限定

复制代码
<T extends BoundingType>

1. 使用 extends 的目的限制类型的变量输入。
如下面的 PairTest2 的例子中,变量 min 是T,可以接受任意类型,如何确保所属的类有一个 CompareTo 方法?
=> 解决方法:通过 "T extends Comparable" 确保 T 实现了 Comparable 接口

Q: Comparable 是接口,为什么用关键字 extends,而不是 implements?
A: T 是 限定类型(bounding type)的子类型(subtype),T 和 限定类型 可以是类,也可以是接口;上面的 extends 更接近于“子类型”的概念,所以没用关键字 implements;
2. 一个类型变量 或 通配符 可以有多个限定,如: T extends Comparable & Serializable;
3. “&” 分隔 限定类型,"," 分隔 类型变量;
4. 在 Java 的继承中,可以根据需要拥有多个接口超类型,但最多有一个限定可以是类。如果有一个类作为限定,必须是限定列表中的第一个限定

复制代码
复制代码
public class PairTest2 {
  public static void main(String[] args) {
    LocalDate[] birthdays = {
            LocalDate.of(1906, 12, 9),
            LocalDate.of(1815, 12, 10),
            LocalDate.of(1910, 6, 22),
        };
    Pair<LocalDate> mm = ArrayAlg.minmax(birthdays);
    System.out.println("min = " + mm.getFirst());
    System.out.println("max = " + mm.getSecond());
  }
}

class ArrayAlg {
  public static <T extends Comparable> Pair<T> minmax(T[] a) {      // 使用 泛型方法 重写 minmax 方法
    if (a == null || a.length == 0) return null;
    T min = a[0];
    T max = a[0];
    for (int i = 1; i < a.length; i++) {
      if (min.compareTo(a[i]) > 0) min = a[i];    // 如何知道 min 有 compareTo 方法 -> 即 T 有 compareTo 方法 => (解决) T extends Comparable        
      if (max.compareTo(a[i]) < 0) max = a[i];    // 现在,泛型 min 方法只能在实现了 Comparable 接口的类(如 String, LocalDate 等)的数组上调用,但 Rectangle 不行,其没有实现 Comparable 接口
    }
    return new Pair<>(min, max);
  }
}
复制代码

 


8.5 泛型代码和虚拟机

1. 有关 Java 泛型转换:
  • 虚拟机中没有泛型,只有普通的类和方法;
  • 所有的类型参数都用它们的限定类型替换,无限定的变量被替换为 Object;
  • 桥方法(bridge method)被合成来保持多态;
  • 为保持类型安全性,必要时插人强制类型转换;
public class Interval<T extends Comparable & Serializable> implements Serializable { ... }
=> 原始类型用 Comparable 替换 T

public class Interval<T extends Serializable & Comparable> implements Serializable { ... }
=> 原始类型用 Serializable 替换 T,编译器在必要时向 Comparable 插入强制类型转换。但一般为了提高效率,将标签接口(tagging)放在限定列表的末尾,即一般采用上面的方式。
(标记接口包含:RandomAccess、Cloneable、Serializable、Remote

2. 调用遗留代码

复制代码
// 可以通过注解 来解决新的泛型类和遗留代码的 警告问题 () 
// 1. 注解局部变量
@SuppressWarnings("unchecked")
Directory<Integer, Components> labelTable = slider.getLabelTable();    // no warning

// 2. 注解整个方法
@SuppressWarnings("unchecked")
public void configureSlider() { ... }
复制代码

 


返回目录

8.6 限制与局限性

大多数限制都是由类型擦除引起的。 

8.6.1. 因为类型参数会擦除成为 Object,Object 不能存储基本类型的值,所以不适用于基本类型。不能用基本类型实例化类型参数;

    好的一种补救方式:用包装器类

    如 ArrayList<Integer>;

    还比如,Pair<Double> 合法,    Pair<double> 非法;

 

8.6.2. 运行时(runtime)类型查询只适用于 原始类型;

复制代码
1)所有的类型查询只产生原始类型 
    if (a instanceof Pair<String>)    // error,虚拟机中没有 Pair<String> 或 Pair<T> 类,只有 Pair 类
    if (a instanceof Pair<T>)         // error

2) 同样,getClass 方法总是返回原始类型(raw type):
    Pair<String> stringPair = ... ;
    Pair<Employee> employeePair = ... ;
    if (stringPair.getClass() == employeePair.getClass())   // they are euqal,两个 getClass() 返回的都是 Pair.class
复制代码

 

8.6.3. 不能创建参数化类型的数组

var table = new Pair<String>[10];   // 非法,不能实例化参数化类型的数组。
// 补救方法:如果需要收集参数化类型对象,建立存放 泛型类型的 ArrayList 更安全、有效,ArrayList<Pair<String>>

Pari<String>[] // 不能创建泛型类型数组,但声明是合法的

 

8.6.4. Varargs 警告

复制代码
// 应用场景:带可变参数的方法(genericsRestrict\GenericsRestrict)
注意区分于 8.6.3:实例化 类型参数的数组是非法的,因为要往数组存储数据。尽管能通过数组存储的检查,但仍会导致类型错误
但,使用可变参数的方法传递泛型类型的实例 是合法的(8.6.4)。因为认为大部分带可变参数的方法都是读入参数,并不在这些数组中存储任何内容。而且用注解来抑制警告。
public static <T> void addAll(ArrayList<T> list, T... moreElements) { for (T element : elements) {
list.add(element);
} }
// 考虑下面调用 ArrayList<Pair<String>> stringParis = ...; Pair<String> pair1 = ...; Pair<String> pair2 = ...; addAll(stringPairs, pair1, pair2); // 将 pair1,pair2 添加到 stringPairs(一个Pair<String> 类型的数组)末尾 如此,Java 虚拟机须建立一个 Pair<String> 数组,这就违法 上面8.6.3(不能创建参数化的数组)的规定,对此有两个方法可以抑制所报的警告: -- 1)为包含 addAll 调用的方法 增加注释 @SuppressWarning("unchecked")
-- 2)@SafeVarargs 标注 addAll 实现方法(after Java 7) @SafeVarargs // 1)对于任何只读取参数数组元素的方法,都可以用这个注解;
// 2)@SafeVarargs 仅用于声明为 static、final 或 private(after Java 9) 的构造器和方法。所有其他方法都可能被覆盖,使得注解无意义;
public static <T> void addAll(ArrayList<T> list, T... moreElements)
复制代码

 

8.6.5. 不能建立泛型类型的实例

复制代码
public Pair() { first = new T();   second= new T(): }   // ERROR,不能实例化泛型类型; new T() 会被擦除为 new Object()

// 解决办法
// 1)(better)让调用者提供一个构造器表达式(after Java 8),如
Pair<String> p = Pair.makePair(String::new); // Implementation: makePair 接收一个 Supplier<T>,这是一个函数式接口,表示一个无参数且返回类型为 T 的函数 public static <T> Pair<T> makePair(Supplier<T> constr) { return new Pair<>(constr.get(), constr.get()); }

// 2) 更传统的解决方法:通过反射调用 newInstance 方法来构造泛型对象
public static <T> Pair<T> makePair(Class<T> cl) throws ... {
  return new Pair<>(cl.newInstance(), cl.newInstance());
}
// 其方法调用为:
Pair<String> p = Pair.makePair(String.class);
复制代码

 

8.6.6. 不能构造泛型数组

public static <T extends Comparable> T[] minmax(T[] a) {
    T[] mm = new T[2];     // Error,不能创建泛型数组,类型擦除后,总是构造 Comparable[2] 数组
    ...
}
复制代码
// 应用场景:创建泛型数组,供用户使用(genericsRestrict\GenericArrayRestrict)
// 方法1:使用反射,并调用 Array.newInstance (If you have an existing array, you can use reflection) public static <T extends Comparable> T[] minmax(T... a) { var result = (T[]) Array.newInstance(a.getClass().getComponentType(), 2); // 长度为2的数组,仅保存 最小值、最大值 ... } // That's why there are two toArray methods in ArrayList: Object[] toArray() // 无参数,返回一个 Object 数组 T[] toArray(T[] result) // 获取数组的元素类型,然后填充结果 // 方法2:(更现代 Better)使用构造器表达式(类型名::new,此处 类型名是 String[]) String[] names = ArrayAlg.minmax(String[]::new, "Tom", "Dick", "Harry");

// 对应的 minmax 方法如下:
public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr, T... a) { // IntFunction<T[]> 是一个消费整数,生成一个 T 类型数组的函数
T[] mm = constr.apply(2); // 数组长度为2
...
}
复制代码

 

7. 泛型类的静态上下文中类型变量无效

public class Singleton<T> {      // <T> 表示泛型类
    private static T singleInstance;         // 此处为静态字段,Error, 禁止使用带类型变量的 静态字段
private static T getSingleInstance() { // 此处为返回类型为泛型的静态方法,Error,不能有参数或返回类型为类型变量的 静态方法
  if (sigleInstance == null) construct new instance of T   return singleInstance; } }

 

8. 不能抛出或捕获 泛型类的实例

复制代码
// case 1public class Problem<T> extends Exception { /*... */}     // Error -- 泛型类(Problem<T>)不能扩展 Throwable(Exception 是 Throwable 的子类)。(这里的 extends 是 方法 扩展

case 2public static <T extends Throwable> void doWork(Class<T> t) {    // 这里的 extends 是类型限定,故是合法的try {do work}
    catch (T e) {       // Error -- 不能捕获一个泛型类型的对象
      Logger.global.info("It didn't work"); }
}

case 3在异常规范中,使用类型变量是允许的
public static <T extends Throwable> void d {    // Okay
    try {  do work  }
    catch (Throwable realCause) {
        t.initCause(realCause);
        throw t;
    }
}
复制代码

 

10. 当泛型类型被擦除后,不允许创建引发命名冲突的条件:

复制代码
public class Pair<T> {
    public boolean equal(T value) { return first.equals(value) && second.equals(value);  }  
    ...
}
// case1:  考虑一个 Pair<String>,那么从 Pair<T> 中定义的 boolean equals(String) 将和从 Object 继承的 boolean euqals(Object) 发生冲突,即同一个方法不同参数

case2为了支持擦除转换,有一个限制:倘若两个接口类型是同一个接口的不同参数化,一个类 或 类型变量 就不能同时作为这两个接口类型的子类。如:
class Employee implements Comparable<Employee>  { ... }
class Manager extends Employee implements Comparable<Manager> { ... }  // Error
// Manager 会实现 Comparable<Employee>,这样就变成了 和 Comparable<Manager> “同一接口的不同参数化”

// 根本原因是:可能是 桥方法冲突 (bridge methods clash),不能对不同类型的 X 有两个这样的方法。
public int compareTo(Object other)  { return compareTo((Employee) other); }
public int compareTo(Object other)  { return compareTo((Manager) other); }

// 延伸,如果是非泛型版本,则是合法的,因为 Manager 本身实现的 和从 Employee 继承的都是 Comparable:
class Employee implements Comparable {...}
class Manager extends Employee implements Comparable {...}
复制代码

 


8.7 泛型类型的继承规则

1. 泛型类型 和 数组类型的区别:

复制代码
1)泛型类型Pair<Manager> 不是 Pair<Employee> 的子类,两者没有关系,所以 不能将 Pair<Manager> 转换成 Pair<Employee>
var managerBuddies = new Pair<Manager>(ceo, cfo);
Pair<Employee> employeeBuddies = managerBuddies;  // illegal, but suppose it wasn't
employeeBuddies.setFirst(lowlyEmployee); // 该语法合法。但因为,employeeBuddies 和 managerBuddies 就指向同一个对象(第二句),就会出现 cfo 和一个普通员工组成一对,对于 Pair<Manager> 应该是不可能这样处理。
2)数组类型(与泛型类型的区别)可将 Manager[] 数组赋给 Employee[] 的变量: Manager[] managerBuddies = {ceo, cfo}; Employee[] employeeBuddies = managerBuddies; // 语法上 OK
employeeBuddies[0] = lowlyEmployee; // 不过,数组有特殊的保护。如果将低级别员工存储到 employeeBuddies[0],虚拟机将会抛出一个 ArrayStoreException 错误。实际上也不行,只是数组有类型保护机制。
复制代码

 

2. 泛型类可以扩展或实现其他的泛型类:

  • 1)ArrayList<T> 类实现了 List<T> 的接口 => 那么 ArrayList<Manager> 可转换为一个 List<Manager> ,ArrayList<Manager> 是 List<Manager>的子类,ArrayList<Employee> 是 List<Employee>的子类;
  • 2)ArrayList<Manager> 或 ArrayList<Employee> 都是原始类型 ArrayList 的子类型; List<Manager> 和 List<Employee> 是原始类型 List 的子类型;
  • 3)ArrayList<Manager> 不是一个 ArrayList<Employee> 或 List<Employee>;
// Raw type is a supertype:
ArrayList<Manager> bosses = ... ;
ArrayList rawlist = bosses;

 


8.8 通配符(?)类型(Wildcard Types,库设计使用,普通开发应用很少)

8.8.1. 通配符 

1. 通配符(子类型限制 extends)

   普通泛型类(<T>) v.s. 子类型通配符(<?>)

public static void printBuddies(Pair<Employee> p)              // 泛型类:参数仅能传入 Pair<Employee> 类型
public static void printBuddies(Pair<? extends Employee>) // 通配符的子类型限制:参数可以是 Employee 的子类。此时可以传入 Pair<Employee> 和 Pair<Manager>,或其他扩展了 Employee 类型的 Pair

// extends 关键字:子类型限制符,作为数据的生产者。目的:解决 8.7 第二点的泛型限制问题

 

 

2. Useful to implement methods that work for different kinds of pairs:

public static void printBuddies(Pair<? extends Employee> pair) {
    Employee first = pair.getFirst();
    Employee second = pair.getSecond();
    System.out.println(first.getName() + " and " + second.getName() + " are buddies.");  // 子类型限定后,传入的类型肯定都是 Employee 类型
}

 

3. You can't store anything into a Pair<? extends Employee> variable, just get is available:

Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);
Pair<? extends Employee> wildcardBuddies = managerBuddies;   // OK, 因为 Pair<? extends Employee> 是 Pair<Manager> 的超类
wildcardBuddies.setFirst(lowlyEmployee);                     // compile-time error

// 原因:类型 Pair<? extends Employee> 的方法是(下面的两个非真正Java语法,将 T 换成了 ?):
? extends Employee getFirst()        // legal, 将 getFirst 的返回值赋给一个 Employee 引用是完全合法的;
void setFrist(? extends Employee)    // 编译器只知道需要 Employee 的子类,但不知道具体什么类型(Employee 或 Manager),所以 除了 null,其拒绝任何传入的类型;

 

4. 总结:

有限定的通配符: 安全的访问器方法 和 不安全的更改器方法,即可以调用 get,但不能调用 set

 

8.8.2. 通配符的超类型限定

1.1. 超类型限定

? super Manager      // super 通配符,限制为 Manager 的所有超类型

 

1.2. Pair<? super Manager> 的方法(非 真正 Java 语法,对比 8.8.1_3)

void setFirst(? super Manager)    // 编译器无法知道 setFirst 方法的具体类型,因此不能接受 参数类型 为 Employee 或 Object 的方法调用; 
                                  // 只能传递 Manager 类型的对象,或某个子类型 (如 Executive 秘书)对象
? super Manager getFirst()       // 如调用 getFirst,不能保证返回对象的类型,只能把它赋给一个 Object

 

1.3. 实例:经理数组,把奖金最高和最低的经理放在一个 Pair 对象中 (对比 8.8.1_2)

复制代码
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) {    // use for pairs that consume data
    if (a.length == 0)  return;
    Manager min = a[0];
    Manager max = a[0];
    for (int i = 1; i < a.length; i++) {
        if (min.getBonus() > a[i].getBonus)) min = a[i];
        if (max.getBonus() < a[i].getBonus)) max = a[i];
    }
    result.setFirst(min);
    result.setSecond(max);
}
复制代码

 经过测试,超类型限定 Pair<? super Manager> 可以接受传参 Pair<Employee> 和 自身 Pair<Manager>。不接受 Manager 的子类 Executive 对应的 Pair<Executive>

 

1.4. 总结

PECS 法则: Producer extend, consumer super
1)带有超类型限定的通配符:允许写入一个泛型对象; 2)带有子类型限定的通配符:允许读取一个泛型对象;

 

关于泛型的入参和是否添加数据,请参考:genericsRestrict\RestrictFromBili

 

2. Comparable 接口应用(超类型限定 super 的另一个应用)

2.1 Generic Comparable interface:

public interface Comparable<T> {
    public int compareTo(T other);
}

 

2.2 Let's use it in min,比较数组的最小值:

public static <T extends Comparable<T>> min(T[] a)      // 实现了 Comparable 接口,并限制类型为 T
                                                // it works fine with Comparable<String>, but it doesn't work with a LocalDate array,因为 LocalDate 实现的是 Comparable<ChronoLocalDate> 而不是 Comparable<LocalDate>

// 解决办法(genericsVariable\ComparableRestrict):
public static <T extends Comparable<? super T>> T min(T[] a)
// 其对应的 CompareTo() 可以写成:
int compareTo(? super T)    // 此时,声明为使用类型 T 的对象,或使用 T 的一个超类型的对象;解决了上面的 T 为 LocalDate 对象的问题。具体参考例子 genericsVariable\ComparableRestrict

 

2.4 超类型限定 super 的另一个常见的用法:作为一个函数式接口的参数类型

// Collection 接口的一个方法:
default boolean removeIf(Predicate<? super E> filter) {...}     // 使用 ? super E,说明可以传入 Object,该方法可以传参 Predicate<Object>

// 应用:
ArrayList<Employee> staff = ...;
Predicate<Object> oddHashCode = obj -> obj.hashCode() % 2 != 0;    // 现在可以传入 Predicate<Object>,而不只是 Predicate<Employee>
staff.removeIf(oddHashCode);

 

8.8.3 无限定通配符 Unbounded Wildcards

复制代码
1. 写法: Pair<?>

2. 方法:
? getFirst()           // 只能赋给一个 Object
void setFirst(?)       // 不能被调用,甚至是 Object 也不能调用

3. 区别:
Pair<?> 和 Pair 本质的不同:可以用任意 Object 对象调用原始 Pair 类的 setFirst 方法
复制代码

 

2. 一个很有用的应用:可以存放任何类型的 Pair 对组,来检查是否含有 null

public static boolean hasNulls(Pair<?> p) {
    return p.getFirst() == null || p.getSecond() == null;
}

// 可以将 hasNulls 转换成 泛型方法,这样就避免使用通配符。但,上面带有通配符的版本可读性更好
public static <T> boolean hasNulls(Pair<T> p) {
    return p.getFirst() == null || p.getSecond() == null;
}

 

8.8.4 通配符捕获

1. 应用:Write a method that swaps the elements of pair

 

复制代码
// 用例:Pair3\PairTest3
public
static void swap(Pair<?> p) { swapHelper(p); // 2)由 swap 来调用 swapHelper
}
// 1)? t = p.getFirst(); 不能直接使用,? 不是一个类型。所以可以定义一个 泛型的辅助方法,如下: public static <T> void swapHelper(Pair<T> p) { T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); }
复制代码

 

 


8.9 反射和泛型

反射允许在运行时分析任意对象。如对象是泛型类的实例,关于泛型类实例参数将得不到太多信息,因为被擦除。

8.9.1 泛型 Class 类

Class 类是泛型的,String.class 是 Class<String> 类的唯一对象/实例。

Class<T> 的以下方法使用了类型参数:

  • T newInstance()
  • T cast(Object obj)
  • T[] getEnumConstants()
  • Class<? super T> getSuperclass()
  • Constructor<T> getConstructor(Class... parameterTypes)
  • Constructor<T> getDeclaredConstructor(Class... parameterTypes)

 

8.9.2 使用 Class<T> 参数进行类型匹配

public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException {
    return new Pair<>(c.newInstance(), c.newInstance());
}
// 调用: 
makePair(Employee.
class); // Employee.class 是一个 Class<Employee> 类型的对象,makePair()的类型参数 T 同 Employee 匹配(注意黄色的对应部分)。 那么由此推断,返回类型 Pair<T> 实际上返回 Pair<Employee>

 

8.9.3 虚拟机中的泛型类型信息

复制代码
# 如方法(擦除后的):
public static Comparable min(Comparable[] a) 

擦除前的泛型方法:
public static <T extends Comparable<? super T>> T min(T[] a)
1) 2) 3) 4) 5) 使用反射API可确定(和上面下标对应):
1)有一个 T 的类型参数; 2)类型参数有一个子类型限定(extends 关键字),其自身又是一个泛型类型(Comparable 泛型接口); 3)限定类型有一个通配符参数; 4)通配符参数有一个超类型限定(super 关键字); 5)有一个泛型数组参数;

// 类和方法知道他们来自哪里,但对象不知道自己来自哪里
复制代码

为了表述泛型类型声明,可使用 java.lang.reflect 包中提供的接口 Type。该接口包含下列子类型:

  • Class 类:描述具体类型;
  • TypeVariable 接口:描述类型变量(如 T extends Comparable<? super T>) ;
  • WildcardType 接口:描述通配符 (如 ? super T);
  • ParameterizedType 接口:描述泛型类或接口类型(如 Comparable<? super T>) ;
  • GenericArrayType 接口:描述泛型数组(如 T[]); 

 其他详细的说明:https://blog.csdn.net/m0_67698950/article/details/124992720

         https://blog.csdn.net/y5492853/article/details/121339608

 

8.9.4 类型字面量

复制代码
应用场景:由值的类型决定程序的行为。如在持久存储机制中,希望指定一种方法来保存某个特定类的对象。
实现方法:将 Class 对象与一个动作关联。

问题: 泛型类型在擦除后,都变成了同一个原始类型,如 ArrayList<Integer> 和 ArrayList<String> 擦除后都是 原始类型 ArrayList,如何让它们有不同的动作呢? 具体实现:捕获 Type 接口的一个实例,然后构造一个匿名子类,如
var type
= new TypeLiteral<ArrayList<Integer>>(){} // note the {} # TypeLiteral 构造器会捕获泛型超类型class TypeLiteral<T> { /** * This constructor must be invoked from an anonymous subclass as new TypeLiteral<...>() {} */ public TypeLiteral() { Type parentType = getClass().getGenericSuperclass(); if (parentType instanceof ParameterizedType) { type = ((ParameterizedType) parentType).getActualTypeArguments()[0]; } else throw new UnsupportedOperationException("Construct as new TypeLiteral<...>(){}"); } ... }
复制代码

备注:

我们无法从对象得到泛型类型,因为已被擦除。但,其字段和方法参数的泛型类型还留存在虚拟机中

CDI 和 Guice 等注入框架(Injection framework)就使用类型字面量来控制泛型类型的注入。

 

posted on   bruce_he  阅读(91)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

点击右上角即可分享
微信分享提示

目录导航