Java泛型

Java泛型的实现

提到Java的泛型一般人都会想到类型擦除(Type Erasure)机制。
如果你没想到,请去补一补。。。。

创建泛型数组

Java不允许创建泛型数组

1
Hello<String>[] hello = new Hello<String>[12];

这句话是编译不通过的。

答案有点让人失望的简单,因为数组必须知道里面的元素是什么。

但是这句话很误导人
真的不允许创建我们想要的泛型数组么,那种可以编译时检查的泛型数组。
答案是可以的,不过得通过一些技巧。

好,那么怎么创建呢。

1
2
3
4
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List<String>[] lists = (List<String>[])new ArrayList[12];
}

这是其中之一的方法,可能有人觉得这样和

1
2
3
public static void main(String[] args) {
List[] lists = new ArrayList[12];
}

没有区别。

划重点!!!!!
第一种方法相对于第二种方法还是具有编译时类型检查的。

泛型类中的泛型数组

怎么在范型类中创建泛型数组呢

其实第一来说没必要, 比如在ArrayList中维护的底层的数组是Object数组。

如果你硬是想创建,可以这样

1
T[] arr = (T[])new Object[12];

很多人或许觉得奇怪为什么,这不是向上转型么。

然后举出例子为什么

1
List<String>[] list = (List<String>[])new Object[10];

这种就不行。

答案还是Java泛型实现的核心类型擦除。
在编译时

1
2
3
T[] arr = (T[])new Object[12];
//会变成
//Object[] arr = (Object[])new Object[12];

这样就是毫无问题。

协变,逆变

协变和逆变

当A是B的子类时,如果有f(A)也是f(B)的子类,那么f叫做协变;
当A是B的子类时,如果有f(B)是f(A)的子类,那么f叫做逆变;

数组是协变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Genetic {
public static void main(String[] args) {
Fruit[] fruits;
Apple[] apples = new Apple[10];
fruits = apples;
}
}
class Apple extends Fruit {

}
class Fruit {

}

这段代码能编译通过。

但是这一段就不行了

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Genetic {
public static void main(String[] args) {
List<Fruit> list;
List<Apple> list1 = new ArrayList<>();
list = list1;
}
}
class Apple extends Fruit {

}
class Fruit {

}

就是说List和List没有关系。

java通过通配符解决这个问题,还是上面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Genetic {
public static void main(String[] args) {
List<? extends Fruit> list;
List<Apple> list1 = new ArrayList<>();
list = list1;
}
}
class Apple extends Fruit {

}
class Fruit {

}

extends解决了协变的问题,逆变通过super解决。

但是,没有那么简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Genetic {
public static void main(String[] args) {
List<? extends Fruit> list = new ArrayList<Apple>();
//这句话并不能通过编译
//list.add(new Apple());
}
}
class Apple extends Fruit {

}
class Fruit {

}

<? extends Fruit>不是表示里面的元素只要是Fruit的子类就行,而是加入的元素必须可以向上转型为Fruit的任意一个子类。
那么大概只有null可以add了。

同样的<? super Fruit>表示里面元素可以转型为Fruid的任意一个父类。

要想可以编译通过,我们可以使用super

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Genetic {
public static void main(String[] args) {
List<? super Fruit> list = new ArrayList<>();
list.add(new Apple());
//ok
}
}
class Apple extends Fruit {

}
class Fruit {

}

那到底应该什么时候用super和extends呢。
Effective Java中总结了一个原则

producer-extends, consumer-super

看看ArrayList的源码就知道了

1
2
3
4
5
6
7
8
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}