一、为什么使用泛型设计
使用泛型机制编写的程序代码要比那些杂乱地使用 Object 变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。
在 Java 中增加范型类之前, 泛型程序设计是用继承实现的。ArrayList 类只维护一个 Object 引用的数组:
这种方法有两个问题。当获取一个值时必须进行强制类型转换;此外,这里没有错误检査。可以向数组列表中添加任何类的对象。
泛型提供了一个更好的解决方案: 类型参数(type parameters)。ArrayList 类有一个类型 参数用来指示元素的类型。
类型参数的魅力在于:使得程序具有更好的可读性和安全性。
下面是标准类库的设计者们肯定产生争议的一个典型问 题。AirayList 类有一个方法 addAll 用来添加另一个集合的全部元素。 程序员可能想要将 ArrayList<Manager> 中的所有元素添加到 ArrayList<Employee> 中去。然而, 反过来就不行了。如果只能允许前一个调用, 而不能允许后一个调用呢? Java语言的设计者发明了一个具 有独创性的新概念,通配符类型(wildcard type), 它解决了这个问题。通配符类型非常抽象, 然而,它们能让库的构建者编写出尽可能灵活的方法。
二、定义简单泛型类
一个泛型类( generic class) 就是具有一个或多个类型变量的类。
Pair 类引人了一个类型变量 T,用尖括号 ( < >) 括起来,并放在类名的后面。泛型类可 以有多个类型变量。例如, 可以定义 Pair 类,其中第一个域和第二个域使用不同的类型:
public class Pair<T,U>{...}
类定义中的类型变量指定方法的返回类型以及域和局部变量的类型.换句话说,泛型类可看作普通类的工厂。
package pair1;
/**
* @version 1.00 2004-05-10
* @author Cay Horstmann
*/
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) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
public class PairTest1
{
public static void main(String[] args)
{
String[] words = { "Mary", "had", "a", "little", "lamb" };
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
class ArrayAlg
{
/**
* Gets the minimum and maximum of an array of strings.
* @param a an array of strings
* @return a pair with the min and max value, or null if a is null or empty
*/
public static Pair<String> minmax(String[] a)
{
if (a == null || a.length == 0) return null;
String min = a[0];
String max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
三、泛型方法
实际上,还可以定义一个带有类型参数的简单方法。
public static <T> T get(T...a){}
注意,类型变量放在修饰符(这里是 public static) 的后面,返回类型的前面。 泛型方法可以定义在普通类中,也可以定义在泛型类中。 当调用一个泛型方法时 ’ 在方法名前的尖括号中放人具体的类型:
String middle = ArrayAlg.<String>get("JohnM, "Q")
在这种情况(实际也是大多数情况)下,方法调用中可以省略类型参数。编译 器有足够的信息能够推断出所调用的方法。它用 names 的类型(即 String[ ]) 与泛型类型 T[ ] 进行匹配并推断出 T 一定是 String。
几乎在大多数情况下,对于泛型方法的类型引用没有问题。偶尔,编译器也会提示错 误, 此时需要解译错误报告。看一看下面这个示例:
double middle = ArrayAlg.getMiddle(B.14, 1729, 0);
错误消息会以晦涩的方式指出(不同的编译器给出的错误消息可能有所不同): 解释这句 代码有两种方法,而且这两种方法都是合法的。简单地说, 编译器将会自动打包参数为 1 个 Double 和 2 个 Integer 对象,而后寻找这些类的共同超类型。事实上;找到 2 个这样的超类 型:Number 和 Comparable 接口,其本身也是一个泛型类型。在这种情况下,可以采取的补救措施是将所有的参数写为 double 值。其本身也是一个泛型类型。在这种情况下,可以采取的补救措施是将所有的参数写为 double 值。
如果想知道编译器对一个泛型方法调用最终推断出哪种类型, PetervonderAM 推 荐了这样一个窍门: 有目的地引入一个错误, 并研究所产生的错误消息。例如, 看一下 调用 ArrayAlg.getMiddle(“ Hello”, 0 , null)。将结果赋给 JButton, 这不可能正确。将会得 到一个措误报告:
found:
java.1ang.0bject&java.io.Seriallzable&java.lang.Comparable<? extends
java.lang.0bject&java.io.Serializable&java.lang.Comparable<?>>
大致的意思是: 可以将结果賦给 Object、Serialiable 或 Comparable
四、类型变量的限定
class ArrayAIg
{
public static <T> T min(T[] a) // almost correct
{
if (a==null || a.length ==0) return null ;
T smallest = a[0];
for (int i = 1; i < a.length; i ++)
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
return smallest;
}
}
但是,这里有一个问题。请看一下 min方法的代码内部。 变量 smallest 类型为 T, 这意 味着它可以是任何一个类的对象。怎么才能确信 T 所属的类有 compareTo 方法呢? 解决这个问题的方案是将 T 限制为实现了 Comparable 接口(只含一个方法 compareTo 的 标准接口)的类。可以通过对类型变量 T 设置限定(bound) 实现这一点:
public static <T extends Comparable> T min(T[] a);
表示 T 应该是绑定类型的子类型 (subtype)。 T 和绑定类型可以是类, 也可以是接口
一个类型变量或通配符可以有多个限定, 例如: T extends Comparable & Serializable 限定类型用“ &” 分隔,而逗号用来分隔类型变量。 在 Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用 一个类作为限定,它必须是限定列表中的第一个。
package pair2;
import java.time.*;
/**
* @version 1.02 2015-06-21
* @author Cay Horstmann
*/
public class PairTest2
{
public static void main(String[] args)
{
LocalDate[] birthdays =
{
LocalDate.of(1906, 12, 9), // G. Hopper
LocalDate.of(1815, 12, 10), // A. Lovelace
LocalDate.of(1903, 12, 3), // J. von Neumann
LocalDate.of(1910, 6, 22), // K. Zuse
};
Pair<LocalDate> mm = ArrayAlg.minmax(birthdays);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
class ArrayAlg
{
/**
Gets the minimum and maximum of an array of objects of type T.
@param a an array of objects of type T
@return a pair with the min and max value, or null if a is
null or empty
*/
public static <T extends Comparable> Pair<T> minmax(T[] a)
{
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];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
五、泛型代码与虚拟机
虚拟机没有泛型类型对象—所有对象都属于普通类。
https://blog.youkuaiyun.com/mccand1234/article/details/52013918Java代码编译和执行过程。
5.1 类型擦除
无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型 ( raw type )。原始类型 的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变M, 并替换为限定类型(无限定的变量用 Object。)
在程序中可以包含不N类型的 Pair, 例 如, Pair 或 Pair 。 而擦除类 型后就变成原始的 Pair 类型了。
原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。
读者可能想要知道切换限定: class Interval<T extends Serializable & Comparable> 会发生什么。如果这样做, 原始类型用 Serializable 替换 T, 而编译器在必要时要向 Comparable 插入强制类型转换。为了提高效率,应该将标签(tagging) 接口(即没有方 法的接口)放在边界列表的末尾。
5.2 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型, 编译器插入强制类型转换。
Pair<Employee> buddies = . .
Employee buddy = buddies.getFirstO;
擦除 getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee 的强制类型转换。 也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
•对原始方法 Pair.getFirst 的调用。
•将返回的 Object 类型强制转换为 Employee 类型。
当存取一个泛型域时也要插人强制类型转换。假设 Pair 类的 first 域和 second 域都是公 有的(也许这不是一种好的编程风格,但在 Java 中是合法的)。表达式: Employee buddy = buddies.first;也会在结果字节码中插人强制类型转换。
5.3 翻译泛型方法
方法的擦除带来了两个复杂问题。看一看下面这个示例:
class Datelnterval extends Pair<LocalDate> {
public void setSecond(LocalDate second) {
if (second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
}
一个日期区间是一对 LocalDate 对象, 并且需要覆盖这个方法来确保第二个值永远不小 于第一个值。这个类擦除后变成:
class Datelnterval extends Pair {
public void setSecond(LocalDate second) {
if (second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
}
令人感到奇怪的是,存在另一个从Pair继承的 setSecond方法,即:
public void setSecond(Object second)
这显然是一个不同的方法,因为它有一个不同类型的参数 Object, 而不是LocalDate。 然而,不应该不一样。考虑下面的语句序列:
Datelnterval interval = new Datelnterval(...);
Pair<LocalDate> pair =interval; // OK assignment to superclass
pair.setSecond(aDate);
这里, 希望对 setSecond 的调用具有多态性, 并调用最合适的那个方法。由于 pair引用 Datelnterval 对象,所以应该调用 Datelnterval.setSecond。问题在于类型擦除与多态发生了冲 突。要解决这个问题, 就需要编译器在 Datelnterval 类中生成一个桥方法(bridge method):
public void setSecond(Object second) { setSecond((Date) second); J
public class A<T>{
public T get(T a){
//a进行一些操作
return a;
}
}
public class B extends A<String>{
@override
public String get(String a){
//a进行一些操作
return a;
}
}
擦除后:
public class A{
public Object get(Object a){
//a进行一些操作
return a;
}
}
public class B extends A{
@override
public String get(String a){
//a进行一些操作
return a;
}
}
@override意味着B对父类A中的get方法进行了重写,但是依上面的程序来看,只是重载,依然可以执行父类的方法,这和期望是不附的,也不符合java继承、多态的特性。
注:子类重写父类方法,只要此实例的实际类型是子类类型了(不是引用类型),便不能调用父类的方法,这是语言的多态特性。
为了解决这个问题,java在编译期间加入了桥接方法。编译后再翻译为java原文件,应如下所示:
public class A{
public Object get(Object a){
//a进行一些操作
return a;
}
}
public class B extends A{
@override
public String get(String a){
//a进行一些操作
return a;
}
public Object get(Object a){
return get((String)a)
}
}
总之,需要记住有关 Java 泛型转换的事实:
•虚拟机中没有泛型,只有普通的类和方法。
•所有的类型参数都用它们的限定类型替换。
•桥方法被合成来保持多态。
•为保持类型安全性,必要时插人强制类型转换。
5.4 调用遗留代码
1.1)设计java 泛型的目的: 允许泛型代码和遗留代码间能够相互操作;
1.2)看个荔枝:要想设置一个 JSlider 标签, 使用 void setLabelTable(Dictionary table),
- 1.2.1)在这里, Dictionary是一个原始类型, 因为实现JSlider 类时Java中还不存在泛型。不过填充字典时, 要使用泛型类型。
Dictionary<Integer, Component> labelTable = new Hashtable<>();
labelTable.put(0, new JLable(new ImageIcon("nine.gif")));
- 1.2.2)将 Dictionary《Interger, Component》对象传递给 setLabelTable 时, 编译器会发出一个警告;
slider.setLable(labelTable); // Warnning
- 因为, 编译器无法确定setLabelTable 可能会对Dictionary对象做什么操作;这个警告对操作并不会产生什么影响, 最多考虑一下 JSlider 有可能用 Dictionary对象做什么就可以了;
1.3)再看个荔枝:(由一个遗留的类得到一个原始类型的对象)。可以将它赋给一个参数化的类型变量, 这样做会产生一个警告;
- 1.3.1)如:
Dictionary <Integer, Components> labelTable = slider.getLabelTable(); // Warning
- 1.3.2)这种情况并不会比 有泛型之前的case 更糟糕,最差的case 是程序抛出一个异常;
- 1.3.3)在查看了警告之后, 可以利用注释 使之消失。注释必须放在生成这个警告的代码所在的方法之前, 如下:
@SuppressWarnings("unchecked")
Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // no warning
- 或者, 可以标注整个方法, 如:
@SuppressWarnings("unchecked")
public void configureSlider(){......}
- 这个标注会关闭对方法中所有代码的检查;
六、约束与局限性
6.1 不能用基本类型实例化类型参数
不能用类型参数代替基本类型。因此, 没有 Pair<double>, 只 有 Pair<Double> 。 当然, 其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double。会报错
6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。为提醒这一风险, 试图查询一个对象是否属于某个泛型类型时,倘若使用 instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。同样的道理, getClass方法总是返回原始类型。例如:
Pair<String> stringPair = . .
Pair<Employee〉employeePair = . .
if (stringPair.getClas() == employeePair.getClass()) // they are equal
其比较的结果是 true, 这是因为两次调用 getClass 都将返回 Pair.class。
6.3 不能创建参数化类型的数组
不能实例化参数化类型的数组, 例如:
Pair<String>[] table = new Pair<String>[10]; // Error
这有什么问题呢? 擦除之后, table 的类型是 Pair[]。可以把它转换为 Object[]:
Object[] objarray = table;
objarray[0] = new Pair<Employee>();能够通过数组存储检査, 不过仍会导致一个类型错误。出于这个原因, 不允许创建参数 化类型的数组。
需要说明的是, 只是不允许创建这些数组, 而声明类型为 Pair[] 的变量仍是合法 的。不过不能用 new Pair[10] 初始化这个变量。
如果需要收集参数化类型对象, 只有一种安全而有效的方法:使用 ArrayList:ArrayList<Pair<String>>????
6.4 Varargs 警告
public static <T> void addAll(Collections coll, T... ts)
{
for (t : ts) coll.add⑴;
}
为了调用这个方法,Java 虚拟机必须建立一个 Pair 数组, 这就违反了前面的规 则。不过,对于这种情况, 规则有所放松,你只会得到一个警告,而不是错误。 可以采用两种方法来抑制这个警告。一种方法是为包含 addAll 调用的方法增加注解 @ SuppressWamings("unchecked"。) 或者在 Java SE 7中, 还 可 以 用@SafeVarargs 直 接 标 注 addAll 方法
6.5 不能实例化类型变置
不能使用像 new T(...,) newT[...] 或 T.class 这样的表达式中的类型变量。比较传统的解决方法是通过反射调用 Clasmewlnstance 方法来构造泛型对象。在 Java SE 8之后, 最好的解决办法是让调用者提供一个构造器表达式。例如:
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());}
比较传统的解决方法是通过反射调用 Clasmewlnstance 方法来构造泛型对象:
public static <T> Pair<T> makePair(Class<T> cl)
{
try { return new Pair(d.newInstance(). cl.newInstance();) }
catch (Exception ex) { return null; }
}
这个方法可以按照下列方式调用:
Pair<String> p = Pair.makePair(String.class);
注意,Class类本身是泛型。 例如,String.class 是一个 Class<String> 的实例(事实上, 它是唯一的实例)。
6.6 不能构造泛型数组
就像不能实例化一个泛型实例一样, 也不能实例化数组。
利用lamda表达式:
String口 ss = ArrayAlg.minmax(String[]:new,"Tom","Dick");
public static <T extends Comparable〉T[] minmax(IntFunction<T[]> constr, T... a)
{ T[] mm = constr.apply(2); }
利用反射可以实现:
package Test;
import java.lang.reflect.Array;
/**
*
* @author QuinnNorris
* 在泛型方法中创建泛型类型的数组
*/
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
String a = "ccc";//创建一个String,作为泛型类型
String[] ar = creArray(a);
for(String art :ar)//循环打印
System.out.println(art);
}
//泛型静态方法
public static <T> T[] creArray (T obj){
T[] arr = (T[])Array.newInstance(obj.getClass(), 5);
arr[1] = obj;
System.out.println(arr[1]);
return arr;
}
}
上述的方法是完全可行的,我们通过用Array类的newInstance方法来构造了可指定类型的数组。使用反射来完成这个工作也应该是在情理之中。因为泛型类型T在中运行时才可能被确定下来,我们能创建泛型数组也必然是在java运行时想办法,在java运行时能起作用的技术莫过于反射。
6.7 泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。例如, 下列高招将无法施展:
public class Singleton<T> {
private static T singlelnstance; // Error
public static T getSinglelnstance() // Error
{
if (singleinstance == null)
//construct new instance
return singlelnstance;
}
}
因此, 禁止使用带有类型变量 的静态域和方法。如果这个程序能够运行, 就可以声明一个 Singleton<Random> 共享随机数生成器, 声明 一个 Singlet0n<JFileChooSer> 共享文件选择器对话框。但是, 这个程序无法工作。类型擦除 之后, 只剩下 Singleton类,它只包含一个 singlelnstance 域。 因此, 禁止使用带有类型变量 的静态域和方法。
6.8 不能抛出或捕获泛型类的实例
既不能抛出也不能捕获泛型类对象。实际上, 甚至泛型类扩展 Throwable 都是不合法的。
catch 子句中不能使用类型变量:
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(...)
}
}
不过, 在异常规范中使用类型变量是允许的。以下方法是合法的:
public static <T extends Throwable〉void doWork(T t) throws T // OK
try
{
do work
}
catch (Throwable real Cause)
{
t.initCause(real Cause);
throw t;
}
}
6.9 可以消除对受查异常的检查
Java 异常处理的一个基本原则是, 必须为所有受查异常提供一个处理器。不过可以利用 泛型消除这个限制。关键在于以下方法:
@SuppressWamings("unchecked")
public static <T extends Throwable〉void throwAs(Throwable e) throws T
{
throw (t) e;
}
以下代码会把所有异常都转换为编译器所认为的 非受查异常:
try { do work } catch (Throwable t) { B1ock.<RuntimeException>throwAs(t) ; }
这有什么意义呢? 正常情况下, 你必须捕获线程 run 方法中的所有受查异常, 把它们 “ 包装” 到非受查异常中, 因为 run 方法声明为不抛出任何受查异常。 不过在这里并没有做这种“ 包装”。我们只是抛出异常, 并“ 哄骗” 编译器, 让它认为 这不是一个受查异常。 通过使用泛型类、 擦除和 @SuppressWamings 注解, 就能消除 Java 类型系统的部分基本 限制。
6.10 注意擦除后的冲突
- 假定像下面这样将 equals 方法添加到 Pair 类中:
public class Pair { public boolean equals(T value) { return first,equals(value) && second,equals(value); }
考虑一个 Pair。从概念上讲, 它有两个 equals 方法:
boolean equals(String) // defined in Pair
boolean equals(Object) // inherited from Object 但是,直觉把我们引入歧途。
方法擦除 boolean equals(T) 就是 boolean equals(Object) 与 Object.equals 方法发生冲突。
当然,补救的办法是重新命名引发错误的方法。
- 泛型规范说明还提到另外一个原则:“ 要想支持擦除的转换, 就需要强行限制一个类或类 型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。” 例如, 下述代码是非法的:
class Employee implements Comparab1e<Employee> { . . . }
class Manager extends Employee implements Comparable<Hanager>
{ . . . } // Error
其原因非常微妙, 有可能与合成的桥方法产生冲突。实现了 C0mpamble 的类可以获得一 个桥方法:
public int compareTo(Object other) { return compareTo((X) other); }
对于不同类型的 X 不能有两个这样的方法。
七、泛型类型的继承规则
考虑一个类和一个子类, 如 Employee 和 Manager。Pair 是 PaiKEmployee> 的一个子类吗? 答案是“ 不是”
无论 S 与 T 有什么联系 。通常, Pair<T> 与 Pair<S>没有什么联系。
最后, 泛型类可以扩展或实现其他的泛型类。就这一点而言,与普通的类没有什么 区别。例如, ArrayList<T> 类实现 List<T> 接口。这意味着, 一个 ArrayList<Manager> 可 以被转换为一个 List<Manager>。但是, 如前面所见, 一个 ArrayList<Manager> 不是一个 ArrayList<Emplyee> 或 List<Emplyee>.
八、通配符类型
通配符类型中, 允许类型参数变化。 例如, 通配符类型:
Pair<? extends Employee〉
表示任何泛型 Pair 类型, 它的类型参数是 Employee 的子类, 如 Pair<Manager>, 但不是 Pair<String>。
通配符的超类型限定
通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超 类型限定 (supertypebound), 如下所亦:
? super Manager
带有超类型限定的通配符可以向泛型对象写人,带有子类型限定的通配符可 以从泛型对象读取。带有extends的相反。
还可以使用无限定的通配符, 例如,Pair<?> 。初看起来,这好像与原始的 Pair 类型一样。 实际上, 有很大的不同。类型 Pair 有以下方法: ? getFi rst() void setFirst(?). getFirst 的返回值只能赋给一个 Object。setFirst 方法不能被调用, 甚至不能用 Object 调 用。Pair 和 Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法。 0 注释: 可以调用 setFirst(null)。
通配符捕获
package pair3;
/**
* @version 1.01 2012-01-26
* @author Cay Horstmann
*/
public class PairTest3
{
public static void main(String[] args)
{
Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
Pair<Manager> buddies = new Pair<>(ceo, cfo);
printBuddies(buddies);
ceo.setBonus(1000000);
cfo.setBonus(500000);
Manager[] managers = { ceo, cfo };
Pair<Employee> result = new Pair<>();
minmaxBonus(managers, result);
System.out.println("first: " + result.getFirst().getName()
+ ", second: " + result.getSecond().getName());
maxminBonus(managers, result);
System.out.println("first: " + result.getFirst().getName()
+ ", second: " + result.getSecond().getName());
}
public static void printBuddies(Pair<? extends Employee> p)
{
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result)
{
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);
}
public static void maxminBonus(Manager[] a, Pair<? super Manager> result)
{
minmaxBonus(a, result);
PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
}
// Can't write public static <T super manager> ...
}
class PairAlg
{
public static boolean hasNulls(Pair<?> p)
{
return p.getFirst() == null || p.getSecond() == null;
}
public static void swap(Pair<?> p) { swapHelper(p); }
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
}
九、反射与泛型
反射允许你在运行时分析任意的对象。如果对象是泛型类的实例,关于泛型类型参数则 得不到太多信息,因为它们会被擦除。在下面的小节中,可以了解利用反射可以获得泛型类 的什么信息。
泛型 Class 类
package genericReflection;
import java.lang.reflect.*;
import java.util.*;
/**
* @version 1.10 2007-05-15
* @author Cay Horstmann
*/
public class GenericReflectionTest
{
public static void main(String[] args)
{
// read class name from command line args or user input
String name;
if (args.length > 0) name = args[0];
else
{
try (Scanner in = new Scanner(System.in))
{
System.out.println("Enter class name (e.g. java.util.Collections): ");
name = in.next();
}
}
try
{
// print generic info for class and public methods
Class<?> cl = Class.forName(name);
printClass(cl);
for (Method m : cl.getDeclaredMethods())
printMethod(m);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
public static void printClass(Class<?> cl)
{
System.out.print(cl);
printTypes(cl.getTypeParameters(), "<", ", ", ">", true);
Type sc = cl.getGenericSuperclass();
if (sc != null)
{
System.out.print(" extends ");
printType(sc, false);
}
printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
System.out.println();
}
public static void printMethod(Method m)
{
String name = m.getName();
System.out.print(Modifier.toString(m.getModifiers()));
System.out.print(" ");
printTypes(m.getTypeParameters(), "<", ", ", "> ", true);
printType(m.getGenericReturnType(), false);
System.out.print(" ");
System.out.print(name);
System.out.print("(");
printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
System.out.println(")");
}
public static void printTypes(Type[] types, String pre, String sep, String suf,
boolean isDefinition)
{
if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class })) return;
if (types.length > 0) System.out.print(pre);
for (int i = 0; i < types.length; i++)
{
if (i > 0) System.out.print(sep);
printType(types[i], isDefinition);
}
if (types.length > 0) System.out.print(suf);
}
public static void printType(Type type, boolean isDefinition)
{
if (type instanceof Class)
{
Class<?> t = (Class<?>) type;
System.out.print(t.getName());
}
else if (type instanceof TypeVariable)
{
TypeVariable<?> t = (TypeVariable<?>) type;
System.out.print(t.getName());
if (isDefinition)
printTypes(t.getBounds(), " extends ", " & ", "", false);
}
else if (type instanceof WildcardType)
{
WildcardType t = (WildcardType) type;
System.out.print("?");
printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
printTypes(t.getLowerBounds(), " super ", " & ", "", false);
}
else if (type instanceof ParameterizedType)
{
ParameterizedType t = (ParameterizedType) type;
Type owner = t.getOwnerType();
if (owner != null)
{
printType(owner, false);
System.out.print(".");
}
printType(t.getRawType(), false);
printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
}
else if (type instanceof GenericArrayType)
{
GenericArrayType t = (GenericArrayType) type;
System.out.print("");
printType(t.getGenericComponentType(), isDefinition);
System.out.print("[]");
}
}
}