泛型
什么是泛型
泛型是为了不再进行强制类型转化,而提供的一个可供传入类型的模板。T<>
理解
public class ArrayList {
private Object[] array;
private int size;
public void add(Object e) {...}
public void remove(int index) {...}
public Object get(int index) {...}
}
用Object表示就得进行强转
为了不进行强转需要
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}
public class IntegerArrayList {
private Integer[] array;
private int size;
public void add(Integer e) {...}
public void remove(int index) {...}
public Integer get(int index) {...}
}
。。。。会有很多
于是
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}
T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList:
泛型接口
可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型
除了ArrayList<T>使用了泛型,还可以在接口中使用泛型。
java内置的一个比较大小的接口
public interface Comparable<T> {
/**
* 返回-1: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回1: 当前实例比参数o大
*/
int compareTo(T o);
}
public class Main {
public static void main(String[] args) {
Person[] ps = new Person[] {
new Person("Bob", 61),
new Person("Alice", 88),
new Person("Lily", 75),
};
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
}
}
class Person implements Comparable<Person> {
String name;
int score;
Person(String name, int score) {
this.name = name;
this.score = score;
}
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
public String toString() {
return this.name + "," + this.score;
}
}
泛型类
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }
}
相当于Map<K,V>
注意点:静态方法不能引用泛型类型<T>,必须定义其他类型(例如<K>)来实现静态泛型方法;
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() { ... }
public T getLast() { ... }
// 静态泛型方法应该使用其他类型区分:
public static <K> Pair<K> create(K first, K last) {
return new Pair<K>(first, last);
}
}
擦拭法
泛型是一种类似”模板代码“的技术,不同语言的泛型实现方式不一定相同。
虚拟机对泛型其实一无所知,所有的工作都是编译器做的
还是进行的强转 不过强转是根据传入的类型编译器进行强转
由于java泛型的原理是擦拭法,会有以下几个局限性
1. <T>不能是基本类型
2. 无法取得带泛型的Class 这个是因为类型是我们传给编译器,编译器进行强转的,所以只有一个T在jvm里只是Object,当然没有获取具体的
换句话说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,
因为编译后它们全部都是Pair<Object>。
3. 无法判断带泛型的类型(和2是一个意思)
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>) {
}
4. 不能实例化T类型
public class Pair<T> {
private T first;
private T last;
public Pair() {
// Compile error:
first = new T();
last = new T(); 这个就是new Object 没有具体的类型了,所以编译器肯定不认可
}
}
可以这么写
public class Pair<T> {
private T first;
private T last;
public Pair(Class<T> clazz) {
first = clazz.newInstance();
last = clazz.newInstance();
}
}
不恰当的覆写方法
由于擦释法 会吧T看成Object所以 方法里不不起equals这样名字 会出现覆写的问题
泛型继承
一个类可以继承自一个泛型类
class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}
extends通配符
1. 作为方法参数如何使用
public class Main {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
int n = add(p);
System.out.println(n);
}
static int add(Pair<Number> p) { // 这个地方接受的是Number 但是传入的是Integer 泛型没有类型的继承 所以会报错
//怎么解决呢 应该用泛型的extends
//static int add(Pair<? extends Number> p) { //这样写就可以传入Integer了
// 这个函数用了extends 肯定再不能写setFirst() 因为这个时候的T无法确定了啊 可能是Number或者子类,编译器不再能确定
// 注意 这个相当于属性是只读的了,不能set了 这是另一个extends的作用,set null是会通过的
Number first = p.getFirst(); // 这个地方类型必须还是Number 因为擦拭法
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}
使用extends通配符表示可以读,不能写
2. 使用extends限定T类型,作为类的继承
public class Pair<T extends Number> { ... }
使用类似<T extends Number>定义泛型类时表示:
泛型类型限定为Number以及Number的子类
super通配符
考察Pair<? super Integer>的setFirst()方法,它的方法签名实际上是:
void setFirst(? super Integer);
因此,可以安全地传入Integer类型。
public static void main(String[] args) {
Pair<Number> p1 = new Pair<>(12.3, 4.56);
Pair<Integer> p2 = new Pair<>(123, 456);
setSame(p1, 100);
setSame(p2, 200);
System.out.println(p1.getFirst() + ", " + p1.getLast());
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
static void setSame(Pair<? super Integer> p, Integer n) {
p.setFirst(n);
p.setLast(n);
}
但是 这个时候getFirst就不知道用什么接受了 , Object类型可以
表示方法内部代码对于参数只能写,不能读
对比extends和super通配符
<? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
<? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
这个拷贝list的例子很安全的体现了这个特性
public class Collections {
// 把src的每个元素复制到dest中:
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);
dest.add(t);
}
}
}
Collections.copy 这个java源码就是这么写的
PECS原则
记住这个特性的口诀 Producer Extends Consumer Super
无限定通配符
无限定通配符<?>很少使用,可以用<T>替换,同时它是所有<T>类型的超类
只写了<?> 既没有extends 也没有super
void sample(Pair<?> p) {
}
换句话说,既不能读,也不能写,那只能做一些null判断:
大多数情况下,可以引入泛型参数<T>消除<?>通配符
所以:<?>通配符有一个独特的特点,就是:Pair<?>是所有Pair<T>的超类:
public class Main {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全地向上转型
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
}
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;
}
public void setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
泛型和反射
Class<String> s = String.class;
Class<? super String> sup = String.class.getSuperclass();
泛型的数组问题
Pair<String>[] ps = null; // ok
Pair<String>[] ps = new Pair<String>[2]; // compile error!
必须通过强制转型实现带泛型的数组:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
可以通过Array.newInstance(Class<T>, int)创建T[]数组,需要强制转型;