读后笔记 -- 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 泛型代码和虚拟机
- 虚拟机中没有泛型,只有普通的类和方法;
- 所有的类型参数都用它们的限定类型替换,无限定的变量被替换为 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 1: public class Problem<T> extends Exception { /*... */} // Error -- 泛型类(Problem<T>)不能扩展 Throwable(Exception 是 Throwable 的子类)。(这里的 extends 是 方法 扩展) case 2: public 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)就使用类型字面量来控制泛型类型的注入。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~