八、泛型程序设计
8.2 定义简单泛型类
-
简单泛型类
public class Pair<T>{ 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 newValue) { this.first = newValue; } public void setSecond(T newValue) { this.second = newValue; } }
8.3 泛型方法
-
泛型方法
// 定义 class ArrayAlg{ public static <T> T getMiddle(T... a){ return a[a.length / 2]; } } // 调用 String middle = ArrayAlg.<String>getMiddle("]ohnM", "Q.", "Public"); // 可省略<String>,由它用names的类型(即String[])与泛型类型T[]进行匹配并推断出T一定是String。 String middle = ArrayAlg.getMiddle("]ohnM", "Q.", "Public");
8.4 类型变量的限定
-
类型变量的限定
// 将T 限制为实现了Comparable 接口 public static <T extends Coiparab1e>(T a) ... // 一个类型变量或通配符可以有多个限定,限定类型用 “&” 分隔,而逗号用来分隔类型变量。 public static <T extends Comparable & Serializable>(T a) ...
8.5 泛型代码和虚拟机
-
类型擦除
- 无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型( raw type )。原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变M, 并替换为限定类型(无限定的变量用Object)。
public class Pair { private Object first; private Object second; public Pair(Object first, Object second){ this.first = first; this.second = second; } public Object getFirst() { return first; } public Object getSecond() { return second; } public void setFirst(Object newValue) { first = newValue; } public void setSecond(Object newValue) { second = newValue; } }
特例:
class Interval<T extends Serializable & Comparable>
原始类型用Serializable 替换T, 而编译器在必要时要向Comparable 插入强制类型转换。为了提高效率, 应该将标签( tagging) 接口(即没有方法的接口)放在边界列表的末尾。
-
翻译泛型表达式
Pair<Employee> buddies = . . Employee buddy = buddies.getFirstO;
擦除getFirst 的返回类型后将返回Object 类型。编译器自动插人Employee 的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
- 对原始方法Pair.getFirst 的调用。
- 将返回的Object 类型强制转换为Employee 类型。
-
翻译泛型方法
当存取一个泛型域时也要插人强制类型转换编译器可能产生两个仅返回类型不同的方法字节码, 虚拟机能够正确地处理这一情况。
关Java 泛型转换:
- 虚拟机中没有泛型, 只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保持多态。
- 为保持类型安全性,必要时插人强制类型转换。
8.6 约束与局限性
-
不能用基本类型实例化类型参数(如int, double, char, float, byte等)
-
运行时类型查询只适用于原始类型
- 虚拟机中的对象总有一个特定的非泛型类型。因此, 所有的类型查询只产生原始类型。
if (a Instanceof Pair<String>) // Error // 实际上仅仅测试 a 是否是任意类型的一个Pair // 试图查询一个对象是否属于某个泛型类型时,倘若使用Instanceof 会得到一个编译器错误,如果使用强制类型转换会得到一个警告。 // getClass 方法总是返回原始类型
-
不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10]; // Error Generic array creation Object[] objarray = table; objarray[0] = "Hello"; // Error component type is Pair StoreException // 下方代码能够通过数组存储检査, 不过仍会导致一个类型 objarray[0] = new Pair<Employee>() // Error
如果需要收集参数化类型对象, 只有一种安全而有效的方法:
ArrayList:ArrayList<Pair<String>>();
-
Varargs 警告
以下函数会创建一个参数可变的数组 ts
public static <T> void addAll(Collections coll, T... ts){ for (t : ts) coll.add(t); }
如果是使得T为Pair
,这样Java 虚拟机必须建立一个Pair 数组,这违反了前面的规则 Col1ection<Pair<String>> table = ...; Pair<String> pairl = ...; Pair<String> pair2 = ...; addAll(table, pairl, pair2);
对于这种情况, java会得到一个警告,而不是错误。两种方法来抑制这个警告
- 在调用方法的方法处使用注解
@SuppressWarnings("unchecked")
。 - 在产生可变的数组的方法处使用注解
@SafeVarargs
。
- 在调用方法的方法处使用注解
-
不能实例化类型变置
不能使用像 new T(…),new T[…] 或 T.class 这样的表达式中的类型变量。
- 解决办法
Pair<String> p = Pair.makePair(String::new); // makePair方法接收一个Supplier<T>,这是一个函数式接口,表示一个无参数而且返回类型为T的函数 public static <T> Pair<T> makePair(Supplier<T> constr) { return new Pair<>(constr.get(), constr.get()); }
-
泛型类的静态上下文中类型变量无效
// 如下代码均错误 public class Singleton<T> { private static T singleInstance; // Error public static T getSingleInstance() { // Error if (singleInstance == null) construct new Instance of T return singleInstance; } }
-
不能抛出或捕获泛型类的实例
- 不能抛出也不能捕获泛型类对象
// 以下代码是错误的 public static <T extends Throwable> void doWork(Class<T> t) { try { // do work } catch (T e) { // Error can't catch type variable Logger,global.info(...) } }
- 在异常规范中使用类型变量是允许的
// 以下代码是OK的 public static <T extends Throwable> void doWork(T t) throws T { try { // do work } catch (Throwable realCause) { t.initCause(realCause); throw t; } }
-
可以消除对受查异常的检查
- Java 异常处理的一个基本原则是, 必须为所有受查异常提供一个处理器。不过可以利用泛型消除这个限制。
@SuppressWarnings("unchecked") public static <T extends Throwable> void throwAs(Throwable e) throws T { throw (T) e; } // 假设这个方法包含在类Block 中,则调用方法为 Block.<RuntimeException>throwAs(t);
-
注意擦除后的冲突
- 当泛型类型被擦除时, 无法创建引发冲突的条件。
// 此方法产生冲突,因此不能如此定义 public class Pair<T> { public boolean equal s(T value) { return first.equals(value) && second.equals(value); } }
方法擦除
boolean equals(T)
并替换为boolean equals(Object)
,这与Object.equals
方法发生冲突。补救的办法是重新命名引发错误的方法。
8.7 泛型类型的继承规则
-
Pair<S>
与Pair<T>
没有什么联系,不能相互转化,如下 -
泛型类可以扩展或实现其他的泛型类
8.8 通配符类型
-
通配符类型
-
带有子类型限定(extends)的通配符可以从泛型对象读取。
-
Pair< ? extends Employee> 其方法如下,这样将不可能调用setFirst 方法。编译器只知道需要某个Employee 的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。
? extends Employee getFirst(); void setFirst(? extends Employee);
-
通配符类型中, 允许类型参数变化。
Pair< ? extends Employee>
表示任何泛型 Pair 类型, 它的类型参数是 Employee 的子类 ,如Pair<Manager>
,类型 Pair是 Pair< ? extends Employee> 的子类型,如下图
-
-
通配符的超类型限定
-
带有超类型限定(super)的通配符可以向泛型对象写人。
-
Pair< ? super Manager> 其方法如下,可以为方法提供参数, 但不能使用返回值。编译器无法知道setFirst 方法的具体类型, 因此调用这个方法时不能接受类型为 Employee 或 Object 的参数。只能传递 Manager 类型的对象, 或者某个子类型(如Executive)对象。另外, 如果调用getFirst , 不能保证返回对象的类型。只能把它赋给一个Object。
void setFirst(? super Manager); ? super Manager getFirst();
-
通配符限定与类型变量限定十分类似,但是,还有一个附加的能力, 即可以指定一个超类型限定
Pair< ? super Manager>
,这个通配符限制为Manager 的所有超类型,类型Pair< ?>
是Pair< ? super Manager>
的父类型,在这里,Pair<Employee>
是合理的,Pair<Object>
也是合理的,均为Manager的父类
-
-
无限定通配符(如:Pair< ?>)
-
Pair< ?> 其方法如下,getFirst 的返回值只能赋给一个Object。setFirst 方法不能被调用,甚至不能用Object 调用。 Pair< ?> 和 Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法。注意:可以调用setFirst(null)。
? getFirst(); void setFirst(?);
-
下面这个方法将用来测试一个 pair 是否包含一个 null 引用,它不需要实际的类型。
public static boolean hasNulls(Pair< ?> p) { return p.getFirst() = null || p.getSecond() =null; }
-
通过将 hasNulls 转换成泛型方法,可以避免使用通配符类型,但是带有通配符的版本可读性更强。
public static <T> boolean hasNulls(Pair<T> p)
-
-
通配符捕获
-
通配符不是类型变量, 因此, 不能在编写代码中使用“ ?” 作为一种类型。
// 如下代码是错误的 public static void swap(Pair< ?> p){ ? t = p.getFirst(); // Error p.setFirst(p.getSecond()); p.setSecond(t); }
-
新建 swapHelper 方法的参数T 捕获通配符。它不知道是哪种类型的通配符, 但是,这是一个明确的类型,并且
swapHelper 的定义只有在T 指出类型时才有明确的含义。 public static <T> void swapHelper (Pair<T> p){ T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); } public static void swap(Pair< ?> p) { swapHelper(p); }
-
当然,在这种情况下, 并不是一定要使用通配符(替代:
void swap(Pair )。在以下例子中,通配符捕获机制是不可避免的。p) public static void maxminBonus(Manager ] a, Pair< ? super Manager> result) { minmaxBonus(a, result); swap(result); // OK swapHelper 捕捉通配符类型 }
-
8.9 反射和泛型
-
泛型Class 类
-
Class
中的方法就使用了类型参数 T newInstance() T cast(Object obj) T[] getEnumConstants() Class< ? super T> getSuperclass() Constructor<T> getConstructor(C1ass... parameterTypes) Constructor<T> getDeclaredConstructor(Class... parameterTypes)
-
newInstance 方法返回一个实例,这个实例所属的类由默认的构造器获得。它的返回类型目前被声明为T, 其类型与Class
描述的类相同,这样就免除了类型转换。 -
如果给定的类型确实是T 的一个子类型,cast 方法就会返回一个现在声明为类型T 的对象, 否则,抛出一个BadCastException 异常。
-
如果这个类不是enum 类或类型T 的枚举值的数组, getEnumConstants 方法将返回null。
-
最后, getConstructor 与getdeclaredConstructor 方法返回一个Constructor
对象。Constructor 类也已经变成泛型, 以便newInstance 方法有一个正确的返回类型。
-
-
使用Class
参数进行类型匹配 public static <T> Pai r<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException{ return new Pairofc.newInstanceO » c.newInstanceO) ; }
-
虚拟机中的泛型类型信息
public static Comparable min(Comparable[] a) // 这是下方泛型方法的擦除 public static <T extends Comparable< ? super T>> T min(T[] a)
可以使用反射API获取:
- 这个泛型方法有一个叫做T 的类型参数。
- 这个类型参数有一个子类型限定, 其自身又是一个泛型类型。
- 这个限定类型有一个通配符参数。
- 这个通配符参数有一个超类型限定。
- 这个泛型方法有一个泛型数组参数。
java.lang.reflect 包中提供了接口Type。这个接口包含下列子类型:
- Class 类,描述具体类型。
- TypeVariable 接口, 描述类型变量(如T extends Comparable< ? super T>) 0
- WildcardType 接口, 描述通配符(如?super T)
- ParameterizedType 接口, 描述泛型类或接口类型(如Comparable< ? super T>)
- GenericArrayType 接口, 描述泛型数组(如T[ ])。
继承层次如下:
API注释
8.9.1 泛型Class 类
java.lang.Class
T newInstance()
// 返回无参数构造器构造的一个新实例。
T cast(Object obj)
// 如果obj 为null 或有可能转换成类型T, 则返回obj ; 否则拋出BadCastException异常。
T[] getEnumConstants() 5.0
// 如果T 是枚举类型, 则返回所有值组成的数组,否则返回mill。
Class< ? super T> getSuperclass()
// 返回这个类的超类。如果T 不是一个类或Object 类, 则返回null。
Constructor<T> getConstructor(Class.. parameterTypes) 1.1
Constructor<T> getDeclaredConstructor(Class... parameterTypes) 1.1
// 获得公有的构造器, 或带有给定参数类型的构造器。
java.lang.reflect.Constmctor
T newInstance(0bject... parameters )
// 返回用指定参数构造的新实例。
8.9.3 虚拟机中的泛型类型信息
java.lang.Class
TypeVariable[] getTypeParameters() 5.0
// 如果这个类型被声明为泛型类型, 则获得泛型类型变量,否则获得一个长度为0 的数组。
Type getGenericSuperclass() 5.0
// 获得被声明为这一类型的超类的泛型类型; 如果这个类型是Object 或不是一个类类型( class type), 则返回null。
Type[] getGenericInterfaces() 5.0
// 获得被声明为这个类型的接口的泛型类型(以声明的次序),否则, 如果这个类型没有实现接口,返回长度为0 的数组。
java.lang.reflect.Method 1.1API
TypeVariable[] getTypeParameters() 5.0
// 如果这个方法被声明为泛型方法, 则获得泛型类型变量,否则返回长度为0 的数组。
Type getGenericReturnType() 5.0
// 获得这个方法被声明的泛型返回类型。
Type[] getGenericParameterTypes() 5.0
// 获得这个方法被声明的泛型参数类型。如果这个方法没有参数, 返回长度为0 的数组。
java.lang.reflect.TypeVariable 5.0
String getName()
// 获得类型变量的名字。
Type[] getBounds()
// 获得类型变量的子类限定,否则, 如果该变量无限定, 则返回长度为0 的数组。
java.Iang.reflect.WildcardType 5.0
Type[] getUpperBounds()
// 获得这个类型变量的子类( extends) 限定, 否则, 如果没有子类限定,则返回长度为0 的数组。
Type[] getLowerBounds()
// 获得这个类型变量的超类(super) 限定,否则, 如果没有超类限定, 则返回长度为0的数组。
java.Iang.reflect.ParameterizedType 5.0
Type getRawType()
// 获得这个参数化类型的原始类型。
Type[] getActualTypeArguments()
// 获得这个参数化类型声明时所使用的类型参数。
Type getOwnerType()
// 如果是内部类型, 则返回其外部类型, 如果是一个顶级类型, 则返回null。
java.Iang.reflect.GenericAnrayType 5.0
Type getGenericComponentType()
// 获得声明该数组类型的泛型组件// 类型。
评论区