定义泛型类
public class Hello {
public static void main(String[] args) {
// TODO Auto-generated method stub
Px<Integer> s = new Px<>(1);
System.out.println(s.getx());
Px<String> t = new Px<>("hello");
System.out.println(t.getx());
}
}
class Px<T> {
private T x;
public Px(T x) {
this.x = x;
}
public T getx() {
return x;
}
}
静态泛型方法
定义静态泛型方法时,应该要在static后面加上
public class Hello {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Px.getx("hello", "world", "hh"));
//will print hh
}
}
class Px {
public static <T> T getx(T... a) {
return a[a.length - 1];
}
}
类型变量的限定
import java.io.Serializable;
public class Hello {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Px.min("abba", "abbc"));
System.out.println(Px.min(4, 3));
}
}
class Px {
//用"&"分隔限定类型
//限定中至多有一个类,如果用一个类作为限定,必须放在限定列表的第一个
public static <T extends Comparable & Serializable> T min(T a, T b) {
T smaller = a;
if (a.compareTo(b) > 0)
smaller = b;
return smaller;
}
}
类型擦除
无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型 ( raw type )。原始类型 的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变M, 并替换为限定类型(无限定的变量用 Object)。
这是编译器看到的代码:
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
这是JVM看到的代码:
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。
类型擦除带来的局限性
不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型: ArrayList<int> arr = new ArrayList<>();//compile error无法取得带泛型的Class:
public class Hello { public static void main(String[] args) { ArrayList<Integer> arr1 = new ArrayList<>(); ArrayList<String> arr2 = new ArrayList<>(); Class cl1 = arr1.getClass(); Class cl2 = arr2.getClass(); System.out.println(cl1); //will print class java.util.ArrayList System.out.println(cl2); //will print class java.util.ArrayList System.out.println(cl1 == cl2); //will print true } }不能在泛型类中实例化T类型:
class p<T> { private T first; private T second; p() { first = new T(); second = new T(); } } //compile error要实例化T类型,应当传入Class
参数: public class Hello { public static void main(String[] args) throws InstantiationException, IllegalAccessException { //创建对象 P<String> p = new P(String.class); } } class P<T> { public T first; public T second; P(Class<T> clazz) throws InstantiationException, IllegalAccessException { first = clazz.newInstance(); second = clazz.newInstance(); } }
不恰当的覆写方法
定义泛型方法时,如果类型擦除后和Object的方法相同,编译器会报错。
class P<T> {
boolean equals(T t) {
return this ==t;
}
}
这是因为,定义的equals(T t)方法实际上会被擦拭成equals(Object t),而这个方法是继承自Object的。
修改:
class P<T> {
boolean equals(T t1, T t2) {
return t1 == t2;
}
}
或者
class P<T> {
boolean same(T t) {
return this == t;
}
}
泛型的继承规则
如图所示,Manager是Employee的子类,但是ArrayList
通配符
extends通配符
使用Pair<? extends Number>使得方法接收所有泛型类型为Number或Number子类的Pair类型。
public class Hello { public static void main(String[] args) { Pair<Integer> p = new Pair(123, 456); System.out.println(m1(p)); //will print 579 } public static int m1(Pair<? extends Number> p) { return p.getA().intValue() + p.getB().intValue(); } public static int m2(Pair<? extends Number> p) { p.setA(123); p.setB(456); //compile error return p.getA().intValue() + p.getB().intValue(); } } class Pair<T> { private T a; private T b; public Pair(T a, T b) { this.a = a; this.b = b; } public T getA() { return a; } public void setA(T a) { this.a = a; } public T getB() { return b; } public void setB(T b) { this.b = b; } }注意:不能调用set方法。编译器只知道需要某个Number 的子类型,但不知道具体是什么类型,比如说原来的值是Double类型,传入的Integer,这就出了问题。它拒绝修改。使用 get就不存在这个问题。extends是只读的。
使用extends限定T类型
在定义泛型类型Pair
的时候,也可以使用extends通配符来限定T的类型: public class Hello { public static void main(String[] args) { Pair<Integer> p1 = null; //allowed Pair<Double> p2 = null; //allowed Pair<Object> p3 = null; //error Pair<String> p4 = null; //error } } class Pair<T extends Number> { }如上,因为String和Object不是Number类型或Number的子类,所以无法通过编译。
super通配符
使用Pair<? super Integer>使得方法接收所有泛型类型为Interger或Interger父类的Pair类型。
public class Hello {
public static void main(String[] args) {
pair<Number> p = new pair<>();
m2(p, new Integer(100));
Object o = m1(p);//使用Object来接收返回值
System.out.println(o.toString());
}
public static Object m1(pair<? super Integer> p) {
return p.getA();
}
public static void m2(pair<? super Integer> p, Integer x) {
p.setA(x);
}
}
class pair<T> {
private T a;
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
}
- 方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);;
- 方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst()。
extends与super的比较
- extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
- super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
PECS原则
如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i); // src是producer
dest.add(t); // dest是consumer
}
}
}
PECS原则的原因:向上转型安全,向下转型不安全。
无限定通配符
不允许调用set(T)方法并传入引用(null除外);
不允许调用T get()方法并获取T引用(只能获取Object引用)。
只能做一些null判断
static boolean isNull(Pair<?> p) { return p.getFirst() == null || p.getLast() == null; }Pair<?>是Pair
的超类。所以,如Pair 可以安全地向上转型。 Pair<Integer> p = new Pair<>(123, 456); Pair<?> p2 = p; // 安全地向上转型
泛型和反射
部分反射api也是泛型。如Class
public class Hello {
public static void main(String[] args) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Class<String> clazz = String.class;
String str = clazz.getConstructor(String.class).newInstance("hello");
System.out.println(str);
// will print hello
Class<Integer> clazz2 = Integer.class;
Constructor<Integer> cons = clazz2.getConstructor(int.class);
Integer n = cons.newInstance(123);
System.out.println(n);
//will print 123
}
}
