Java编程思想---泛型(2)
类型擦除
先上例子:
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
ArrayList< String >与ArrayList< Integer >是不同的类型,但是上面的程序输出true,也就是上面的程序认为它们是一样的类型。
再看接下来这个例子:
public class Apple {
}
public class Orange {
}
public class Pear<P> {
}
public class Watermelon<POSITION,MOMENTUM> {
}
public class LostInformation {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
Map<Apple,Orange> map = new HashMap<>();
Pear<Apple> pear = new Pear<>();
Watermelon<Long,String> watermelon = new Watermelon<>();
System.out.println(Arrays.toString(
apples.getClass().getTypeParameters()
));
System.out.println("______________________________________");
System.out.println(Arrays.toString(
map.getClass().getTypeParameters()
));
System.out.println("______________________________________");
System.out.println(Arrays.toString(
pear.getClass().getTypeParameters()
));
System.out.println("______________________________________");
System.out.println(Arrays.toString(
watermelon.getClass().getTypeParameters()
));
}
}
Class.getTypeParameters()将返回一个TypeVariable对象数组,表示有泛型声明所声明的类型参数。
但是最终的输出结果确实一堆用作参数占位符的标识符,不是我们所想的具体的参数类型的信息。
最终的事实就是,在Java中,在泛型代码内部,你无法获得任何有关泛型参数类型的信息
Java泛型是用擦除来实现的,意味着你使用泛型时,任何具体的类型信息都背擦除了,你唯一知道的就是你在使用一个对象。
因此List< String >和List< Integer >在运行时事实上是相同的类型,这两种形式都被擦除成它们的“原生”类型,即List。
继续深入
还是先上例子:
public class HasF {
public void f(){
System.out.println("HasF.f");
}
}
public class Manipulator<T> {
private T obj;
public Manipulator(T obj) {
this.obj = obj;
}
// ERROR
public void manipulator(){
// obj.f();
}
}
public class Manipulation {
public static void main(String[] args) {
HasF hasF = new HasF();
Manipulator<HasF> manipulator =
new Manipulator<>(hasF);
manipulator.manipulator();
}
}
因为擦除,Java编译器无法将manipulator()必须能够在obj上调用f()这一需求映射到HasF拥有f()这一事实。
为了解决问题,我们必须给定泛型类的边界,告知编译器只能接受遵循这个边界的类型。
public class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T obj) {
this.obj = obj;
}
public void manipulator2(){
obj.f();
}
}
边界< T extends HasF >声明T必须具有类型HasF或者从HasF导出的类型。
泛型类型参数将擦除到它的第一个边界(也许有有多个边界)。还有类型参数的擦除,编译器实际上会把类型参数替换为它的擦除,就像上面,T擦除到了HasF,就好像在类的声明中用HasF替换了T一样。
看第三个版本的Manipulator,我们可以自己去擦除:
public class Manipulator3 {
private HasF obj;
public Manipulator3(HasF obj) {
this.obj = obj;
}
public void manipulator3(){
obj.f();
}
}
擦除带来的问题
擦除的代价很明显,泛型不能用于显式的引用运行时类型的操作之中,比如转型,instanceof操作,new表达式,因为所有关于参数的类型信息都丢失了。
所以如果接下来你写了这段代码:
public class Car {
}
public class Fon<T> {
T var;
}
public class T1 {
public static void main(String[] args) {
Fon<Car> fon = new Fon<>();
}
}
class Fon的代码应该工作与Car之上,泛型语法也在提醒我们,但事实并非如此,你应该意识到,这只是一个Object、
边界
用泛型表示没有任何意义的事物
public class ArrayMake<T> {
private Class<T> kind;
public ArrayMake(Class<T> kind) {
this.kind = kind;
}
@SuppressWarnings("unchecked")
T[] create(int size){
return (T[]) Array.newInstance(kind,size);
}
public static void main(String[] args) {
ArrayMake<String> stringArrayMake =
new ArrayMake<>(String.class);
String[] strings = stringArrayMake.create(10);
System.out.println(Arrays.toString(
strings
));
}
}
kind即使被存储了Class< T >,擦除也意味着它实际被存储未Class,无任何参数,所以当你用Array.newInstance创建的时候,其实并未拥有任何kind的类型信息。
如果换成容器呢?
public class ListMake<T> {
List<T> create(){
return new ArrayList<T>();
}
public static void main(String[] args) {
ListMake<String> stringListMake = new ListMake<>();
List<String> stringList = stringListMake.create();
}
}
编译器没有给出警告。
再看这个例子:
public class FilledListMake<T> {
List<T> create(T t,int n){
List<T> res = new ArrayList<>();
for (int i = 0; i < n ; i++) {
res.add(t);
}
return res;
}
public static void main(String[] args) {
FilledListMake<String> stringFilledListMake =
new FilledListMake<>();
List<String> list = stringFilledListMake.create("World",20);
System.out.println(list);
}
}
即使擦除在方法或类内部移除了有关实际类型的信息,编译器依旧可以确保在方法或类中使用的类型的内部的一致性。
补偿擦除
因为擦除,任何在运行时需要知道的确切类型信息的操作都无法执行:
public class Erase<T> {
private final int siez = 100;
public static void f(Object o){
// ERROR
// if (o instanceof T) {
//
// }
// error
// T var = new T();
// error
// T[] array = new T[siez];
}
}
如果引入类别标签,那么就可以使用动态的isInstance():
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<Fruit> classTypeCapture =
new ClassTypeCapture<>(Fruit.class);
System.out.println(classTypeCapture.f(new Fruit()));
System.out.println("____________________________________");
System.out.println(classTypeCapture.f(new Apple()));
System.out.println("____________________________________");
ClassTypeCapture<Apple> classTypeCapture1 =
new ClassTypeCapture<>(Apple.class);
System.out.println(classTypeCapture1.f(new Fruit()));
System.out.println("____________________________________");
System.out.println(classTypeCapture1.f(new Apple()));
}
}
使用类型标签的话,你就可以使用newInstance()来创建这个类型的新对象。
public class ClassFactory<T> {
T x;
public ClassFactory(Class<T> kind) {
try {
x = kind.newInstance();
} catch (Exception e) {
throw new RuntimeException();
}
}
}
public class Apple {
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassFactory<Apple> classFactory =
new ClassFactory<>(Apple.class);
System.out.println("succeed");
// 报错
ClassFactory<Integer> classFactory1 =
new ClassFactory<>(Integer.class);
}
}
最后的语句会报错,因为Integer没有任何默认构造函数。
泛型数组
正如之前的例子可以看到,不能创建泛型数组,所以一般的思路就是用Arraylist
public class ListOfGenerics<T> {
private List<T> array = new ArrayList<>();
public void add(T item){
array.add(item);
}
public T get(int index) {
return array.get(index);
}
}
假如你还是想创建泛型类型数组,你可以按照编译器喜欢的方式定义一个引用。
public class Generic<T> {
}
public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}
编译器会接受这个程序,也不会给出任何警告,但是,永远都不能创建这个确切类型的数组(包括类型参数)。
比如接下来这个,可以编译,但不能运行。
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
gia = (Generic<Integer>[]) new Object[SIZE];
gia = new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<Integer>();
// gia[1] = new Object();
// gia[2] = new Generic<Double>();
}
}
来看一个泛型数组包装器:
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz){
array = (T[]) new Object[sz];
}
public void put(int index,T item){
array[index] = item;
}
public T get(int index){
return array[index];
}
public T[] rep(){
return array;
}
public static void main(String[] args) {
GenericArray<Integer> gai =
new GenericArray<>(10);
// error
// Integer[] integers = gai.rep();
Object[] objects = gai.rep();
}
}
我们不能声明T[] array = new T[sz],所以需要创建一个对象数组,然后将其转型
因为擦除,数组运行时类型就只能时Object[],假设我们立即将其转型为T[ ],那么编译期间数组的实际类型就会丢失。
看接下来的例子:
public class GenericArray2<T> {
private Object[] array;
public GenericArray2(int size){
array = new Object[size];
}
public void put(int index,T item){
array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index){
return (T) array[index];
}
@SuppressWarnings("unchecked")
public T[] rep(){
return (T[]) array;
}
public static void main(String[] args) {
GenericArray2<Integer> genericArray2 =
new GenericArray2<>(20);
for (int i = 0; i < 20; i++) {
genericArray2.put(i,i);
}
for (int i = 0; i < 20 ; i++) {
System.out.println(genericArray2.get(i) + "\n");
}
// error
// Integer[] integers = genericArray2.rep();
}
}
现在内部表示是Object[]而不是T[ ],方法get()会将对象转型为T,这是正确的类型。
当然传递一个类型标记也是可以尝试的:
public class GenericArrayWithTypeToken<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type,int siez){
array = (T[]) Array.newInstance(type,siez);
}
public void put(int index,T item){
array[index] = item;
}
public T get(int index){
return array[index];
}
public T[] rep(){
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken genericArrayWithTypeToken =
new GenericArrayWithTypeToken(Integer.class,30);
// error
// Integer[] ia = genericArrayWithTypeToken.rep();
}
}
类型标记Class< T >被传递到构造函数中,以便从擦除中恢复,使得我们可以创建需要的实际类型的数组。
深入边界
边界使得你可以在用于泛型的参数类型上设置限制条件。
因为擦除移除了类型信息,所以可以用无界泛型参数调用的方法只是那些可以用Object调用的方法,但是假设能将这个参数限制为某个类型的子集,那么就可以用这些类型子集来调用方法。
看下面这个例子,体现边界的基本要素:
public interface HasColor {
java.awt.Color getColor();
}
public class Colored<T extends HasColor> {
T item;
public Colored(T item) {
this.item = item;
}
public T getItem() {
return item;
}
java.awt.Color color(){
return item.getColor();
}
}
public class Dimension {
public int x,y,z;
}
public class ColoredDimension<T extends Dimension & HasColor> {
T item;
public ColoredDimension(T item) {
this.item = item;
}
public T getItem() {
return item;
}
java.awt.Color color(){
return item.getColor();
}
int getX(){
return item.x;
}
}
public interface Weight {
int weight();
}
public class Solid<T extends Dimension & HasColor & Weight> {
T item;
public Solid(T item) {
this.item = item;
}
public T getItem() {
return item;
}
java.awt.Color color(){
return item.getColor();
}
int getX(){
return item.x;
}
int weight(){
return item.weight();
}
}
public class Bounded extends Dimension implements HasColor,Weight{
@Override
public Color getColor() {
return null;
}
@Override
public int weight() {
return 0;
}
}
public class BasicBounds {
public static void main(String[] args) {
Solid<Bounded> solid =
new Solid<>(new Bounded());
solid.color();
solid.getX();
solid.weight();
}
}
接下来看看如何在继承的每个层次上添加边界限制:
public class HoldItem<T> {
T item;
public HoldItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
public class Color2<T extends HasColor> extends HoldItem<T> {
public Color2(T item) {
super(item);
}
java.awt.Color color(){
return item.getColor();
}
}
public class ColoredDimension2<T extends Dimension & HasColor> extends Color2<T>{
public ColoredDimension2(T item) {
super(item);
}
int getX(){
return item.x;
}
}
public class Solid2<T extends Dimension & HasColor & Weight> extends ColoredDimension2<T>{
public Solid2(T item) {
super(item);
}
int weight(){
return item.weight();
}
}
public class InheritBounds {
public static void main(String[] args) {
Solid2<Bounded> solid2 = new
Solid2<>(new Bounded());
solid2.color();
solid2.getX();
solid2.weight();
}
}
HoldItem直接持有一个对象,因此这种行为也继承到了Color2中,也要求其参数与HasColor一致。
通配符
来考虑数组的一种特殊行为,向导出类型的数组赋予基类型的数组引用。
public class Fruit {
}
public class Apple extends Fruit{
}
public class Jonathan extends Apple{
}
public class Orange extends Fruit{
}
public class CovarianArrays {
public static void main(String[] args) {
// 创建了一个Apple数组,并将其赋值给了一个Fruit数组引用,这是有意义的
// 因为Apple也是一种Fruit。所以Apple数组也是一个Fruit数组
Fruit[] fruits = new Apple[10];
fruits[0] = new Apple();
fruits[1] = new Jonathan();
// error
// fruits[0] = new Fruit();
// error
// fruits[0] = new Orange();
}
}
如果用泛型容器代替数组会怎样:
public class NonCovariantGenerics {
// error
// List<Fruit> fruits = new ArrayList<Apple>();
}
Apple的List在类型上不等价于Fruit的List,即使Apple是一种Fruit类型。
那如果我想要在两个类型之间建立某种类型的向上转型关系,这个时候就可以使用通配符了
public class GenericsAndCovariance {
public static void main(String[] args) {
List<? extends Fruit> fruits = new ArrayList<Apple>();
// error
// fruits.add(new Apple())
// error
// fruits.add(new Fruit())
fruits.add(null);
Fruit fruit = fruits.get(0);
System.out.println(fruit);
}
}
fruits现在是List<? extends Fruit>,你可以将其视作“具有任何从Fruit继承的类型的列表”。
逆变
超类型通配符
声明通配符是由某个特定类的任何基类来界定的,方法是指定<? super MyClass>,甚至或者使用类型参数<? super T>.
例如:
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> a){
a.add(new Apple());
a.add(new Jonathan());
// error
// a.add(new Fruit());
}
}
参数Apple是Apple的某种基类型的List,这样向其中添加Apple或Apple的子类型是安全的。
超类型边界放松了在可以向方法传递的参数上所做的限制:
public class GenericWriting {
// 使用了一个确切参数类型(无通配符)
static <T> void writeExact(List<T> list,T item){
list.add(item);
}
static List<Apple> apples = new ArrayList<Apple>();
static List<Fruit> fruits = new ArrayList<Fruit>();
static void f1(){
writeExact(apples,new Apple());
writeExact(fruits,new Apple());
}
// 使用了通配符,因此这个List将持有从T导出的某种具体类型
static <T> void
writrWithWildcard(List<? super T> list,T item){
list.add(item);
}
static void f2(){
writrWithWildcard(apples,new Apple());
writrWithWildcard(fruits,new Apple());
}
public static void main(String[] args) {
f1();
f2();
}
}
无界通配符
无界通配符<?>看起来意味着“任何事物”,因此使用起来就好像等价于使用原生类型。
public class UnboundedWildcard1 {
static List list1;
static List<?> list2;
static List<? extends Object> list3;
static void assign1(List list){
list1 = list;
list2 = list;
list3 = list;
}
static void assign2(List<?> list){
list1 = list;
list2 = list;
list3 = list;
}
static void assign3(List<? extends Object> list){
list1 = list;
list2 = list;
list3 = list;
}
public static void main(String[] args) {
assign1(new ArrayList());
assign2(new ArrayList<>());
assign3(new ArrayList<>());
assign1(new ArrayList<String>());
assign2(new ArrayList<String>());
assign3(new ArrayList<String>());
List<?> wildList = new ArrayList<>();
wildList = new ArrayList<String>();
assign1(wildList);
assign2(wildList);
assign3(wildList);
}
}
网友评论