目录
instanceof操作可以通过引入类型标签,使用动态的isInstance()来进行补偿
可以通过传递一个工厂对象,并使用它来创建新的实例的方式对new表达式进行补偿。
泛型数组,一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList
引入泛型
泛型可以把使用Object的错误提前到编译后,而不是运行后,提升安全性。泛型的本质便是类型参数化,通俗的说就是用一个变量来表示类型。
泛型的具体形式
泛型类
class Test<T>
{
private T t;
Test(T t)
{
this.t = t;
}
public T getT()
{
return t;
}
public void setT(T t)
{
this.t = t;
}
}
泛型方法
public <T> void show()
{
operation about T...
}
如果使用泛型方法可以取代将整个类的泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
泛型接口
public interface Generator<T> { T next(); }
例:用接口实现Fibonacci(斐波那契)数列
public class Fibonacci implements Generator<Integer> {
private int count = 0;
public Integer next() { return fib(count++); }
private int fib(int n) {
if(n < 2) return 1;
return fib(n-2) + fib(n-1);
}
public static void main(String[] args) {
Fibonacci gen = new Fibonacci();
for(int i = 0; i < 18; i++)
System.out.print(gen.next() + " ");
}
}
/* Output:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
*///:~
泛型擦除
在JAVA的虚拟机中并不存在泛型,泛型是JAVA的语法糖,它是增加程序员编程的便捷性以及安全性而创建的一种机制,在JAVA虚拟机中对应泛型的都是确定的类型,在编写泛型代码后,java虚拟中会把这些泛型参数类型都擦除,用相应的确定类型来代替,泛型类型参数将擦除到它的第一个边界(它可能会有多个边界,多个边界直接用 & 表示)。
例:
泛型类:
class Test<T>
{
private T t;
public void show(T t)
{
}
}
擦除后
class Test
{
private Object t;
public void show(Object t)
{
}
}
带边界的泛型类:
class Test<? extends Comparable>
{
private T t;
public void show(T t)
{
}
}
擦除后:
class Test
{
private Comparable t;
public void show(Comparable t)
{
}
}
泛型擦除带来的问题
因为编译后代码所有的参数的类型信息都被擦除了,所以泛型不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。
例如:
public class Erased<T> {
private final int SIZE = 100;
public static void f(Object arg) {
if(arg instanceof T) {} // Error
T var = new T(); // Error
T[] array = new T[SIZE]; // Error
T[] array = (T)new Object[SIZE]; // Unchecked warning
}
}
- 不能实例化类型变量
因为存在类型擦除,所以类似于”new T(…)”这样的语句就会变为”new Object(…)”, 而这通常不是我们的本意,所以不能以诸如“new T(…)”, “new T[...]“, “T.class”的形式使用类型变量。
- 不能创建参数化类型的数组
在Java中,Object[]数组可以是任何数组的父类(因为任何一个数组都可以向上转型为它在定义时指定元素类型的父类的数组)。考虑以下代码:
String[] strs = new String[10];
Object[] objs = strs;
obj[0] = new Date(...);
在上述代码中,我们将数组元素赋值为满足父类(Object)类型,但不同于原始类型(String)的对象,在编译时能够通过,而在运行时会抛出ArrayStoreException异常。
基于以上原因,假设Java允许我们通过以下语句声明并初始化一个泛型数组:
Pair<String, String>[] pairs = new Pair<String, String>[10];
那么在虚拟机进行类型擦除后,实际上pairs成为了Pair[]数组,我们可以将它向上转型为Object[]数组。这时我们若往其中添加Pair<Date, Date>对象,便能通过编译时检查和运行时检查,而我们的本意是只想让这个数组存储Pair<String, String>对象,这会产生难以定位的错误。因此,Java不允许我们通过以上的语句形式声明并初始化一个泛型数组。
可用如下语句声明并初始化一个泛型数组:
Pair<String, String>[] pairs = (Pair<String, String>[]) new Pair[10];
泛型补偿
instanceof操作可以通过引入类型标签,使用动态的isInstance()来进行补偿
例如:
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 =
new ClassTypeCapture<Building>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 =
new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
} /* Output:
true
true
false
true
*///:~
可以通过传递一个工厂对象,并使用它来创建新的实例的方式对new表达式进行补偿。
- 最便利的工厂对象就是Class对象,可以使用newInstance来创建这个类型的新对象,但通过这种方法的缺点是,只能通过默认构建函数来创建,如果没有默认构建函数将无法创建(例如Integer)所以建议使用显式的工厂,井将限制其类型,使得只能接受实现了这个工厂的类。
interface FactoryI<T> {
T create();
}
class Foo2<T> {
private T x;
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create();
}
// ...
}
class IntegerFactory implements FactoryI<Integer> {
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class Factory implements FactoryI<Widget> {
public Widget create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
} ///:~
- 还有另一种方式是模板方法设计模式
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() { element = create(); }
abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
X create() { return new X(); }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator c = new Creator();
c.f();
}
} /* Output:
X
*///:~
泛型数组,一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList
import java.util.*;
public class ListOfGenerics<T> {
private List<T> array = new ArrayList<T>();
public void add(T item) { array.add(item); }
public T get(int index) { return array.get(index); }
} ///:~
泛型边界
将参数限制为某个类型子集,就可以用这些类型子集来调用方法。Java泛型重用了extends关键字来表示边界。
例如:
interface HasColor { java.awt.Color getColor(); }
class Colored<T extends HasColor> {
T item;
Colored(T item) { this.item = item; }
T getItem() { return item; }
// The bound allows you to call a method:
java.awt.Color color() { return item.getColor(); }
}
class Dimension { public int x, y, z; }
// This won't work -- class must be first, then interfaces:
// class ColoredDimension<T extends HasColor & Dimension> {
// Multiple bounds:
class ColoredDimension<T extends Dimension & HasColor> {
T item;
ColoredDimension(T item) { this.item = item; }
T getItem() { return item; }
java.awt.Color color() { return item.getColor(); }
int getX() { return item.x; }
int getY() { return item.y; }
int getZ() { return item.z; }
}s
泛型通配符
(1)假设Student是People的子类,List<Student,>却不是List<People >的子类,它们之间不存在”is-a”关系。
(2)List <T >与它的原始类型List之间存在”is-a”关系,List<T>在任何情况下都可以转换为List类型。
- 泛型限定是通过?(通配符)来实现的,表示可以接受任意类型
- <? extends SomeClass >这种限定,说明的是只能接收SomeClass及其子类类型,所谓的“上限”
- <? super SomeClass>这种限定,说明只能接收SomeClass及其父类类型,所谓的“下限”
现在考虑这样一个方法:
public static void printName(Pair<People, People> p) {
People p1 = p.getFirst();
System.out.println(p1.getName()); //假设People类定义了getName实例方法
}
在以上的方法中,我们想要同时能够传入Pair<Student, Student>和Pair<People, People>类型的参数,然而二者之间并不存在”is-a”关系。在这种情况下,Java提供给我们这样一种解决方案:使用Pair<? extends People>作为形参的类型。也就是说,Pair<Student, Student>和Pair<People, People>都可以看作是Pair<? extends People>的子类。
形如”<? extends BoundingType>”的代码叫做通配符的子类型限定。与之对应的还有通配符的超类型限定,格式是这样的:<? super BoundingType>。
现在我们考虑下面这段代码:
Pair<Student> students = new Pair<Student>(student1, student2);
Pair<? extends People> wildchards = students;
wildchards.setFirst(people1);
以上代码的第三行会报错,因为wildchards是一个Pair<? extends People>对象,它的setFirst方法和getFirst方法是这样的:
void setFirst(? extends People)
? extends People getFirst()
对于setFirst方法来说,会使得编译器不知道形参究竟是什么类型(只知道是People的子类),而我们试图传入一个People对象,编译器无法判定People和形参类型是否是”is-a”的关系,所以调用setFirst方法会报错。而调用wildchards的getFirst方法是合法的,因为我们知道它会返回一个People的子类,而People的子类“always is a People”。(总是可以把子类对象转换为父类对象)
而对于通配符的超类型限定的情况下,调用getter方法是非法的,而调用setter方法是合法的。
除了子类型限定和超类型限定,还有一种通配符叫做无限定的通配符,它是这样的:<?>。这个东西我们什么时候会用到呢?考虑一下这个场景,我们调用一个会返回一个getPairs方法,这个方法会返回一组Pair<T, T>对象。其中既有Pair<Student, Student>, 还有Pair<Teacher, Teacher>对象。(Student类和Teacher类不存在继承关系)显然,这种情况下,子类型限定和超类型限定都不能用。这时我们可以用这样一条语句搞定它:
Pair<?>[] pairs = getPairs(...);
对于无限定的通配符,调用getter方法和setter方法都是非法的。
<E extends SomeClass> 与 <? extends SomeClass>的区别
首先我们明确一下两边的名字,限制类型 & 通配符类型,<E extends SomeClass>表示后续都只能使用E进行某些判断或操作,而<? extends SomeClass>?表示后续使用时可以是任意的。
通过List方法对泛型的使用,来梳理一下泛型的用法
用法 | 名称 |
List<String> | 参数化的类型 |
List<E> | 泛型 |
List<?> | 无限制通配符类型 |
<E extends SomeClass> | 有限制类型参数 |
List <? extends SomeClass> | 有限制通配符类型 |
<T extends Comparable<T>> | 递归类型限制 |
static <E> List<E> asList(E[] a) | 泛型方法 |
其他注意事项
- 泛型的类型参数必须为类的引用,不能用基本类型(int, short, long, byte, float, double, char, boolean)
也就是说,以下语句是非法的:
List<int> pair = new ArrayList<>(); //我们可以用相应的包装类型来代替。
- 泛型类的静态上下文中不能使用类型变量
注意,这里我们强调了泛型类。因为普通类中可以定义静态泛型方法,如上面我们提到的ArrayAlg类中的getMiddle方法。关于为什么有这样的规定,请考虑下面的代码:
public class People<T> {
public static T name;
public static T getName() {
...
}
}
我们知道,在同一时刻,内存中可能存在不只一个People<T>类实例。假设现在内存中存在着一个People<String>对象和People<Integer>对象,而类的静态变量与静态方法是所有类实例共享的。那么问题来了,name究竟是String类型还是Integer类型呢?基于这个原因,Java中不允许在泛型类的静态上下文中使用类型变量。
- 不能抛出也不能捕获泛型类实例
泛型类扩展Throwable即为不合法,因此无法抛出或捕获泛型类实例。但在异常声明中使用类型参数是合法的:
public static <T extends Throwable> void doWork(T t) throws T {
try {
...
} catch (Throwable realCause) {
t.initCause(realCause);
throw t;
}
}
- 类型擦除后的冲突注意
例如:
class Pair<T>
{
public boolean equals(T value) //error
{ss
....
}
}
此处的错误的原因不能存在同一个方法,在类型擦除后,Pair的方法为,public boolean equals(Object value),这与从Object.class中继承下来的equals(Object obj)冲突。
一个类不能成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。
例如:
class Calendar implements coparable<Calendar>{}
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{} //error
当类型擦除后,Calendar实现的是Comparable,而GregorianCalendar继承了Calendar,又去实现Comparable,必然出错!