直接代码分析一波:
import java.util.*;public classEx12 {public static voidmain(String[] args) {
Class c1= new Double(0).getClass();
Class c2= new Integer(0).getClass();
Class c3= new ArrayList().getClass();
Class c4= new ArrayList().getClass();
System.out.println(c1==c2);
System.out.println(c3==c4);
}
}
这里的c1 c2 c3 c4将分别得到Double、Integer、ArrayList、ArrayList的类,输出是:
false
true
c1 == c2很明显是不一样的,这里的ArrayList和ArrayList很容易认为是不同的类型,但是其实他两是相同类型的,
import java.util.*;class test1{}class test2{}public classEx12 {public static voidmain(String[] args) {
test1 t1 = new test1();
test2 t2 = new test2();
System.out.println(Arrays.toString(t1.getClass().getTypeParameters()));
System.out.println(Arrays.toString(t2.getClass().getTypeParameters()));
}
}
Class.getTypeParameters()将返回一个泛型声明所声明的类型参数数组,但是只能看到T,Q,得不到Double,这是因为在java泛型是使用擦除来实现的,这就意味这当你在使用泛型的时候,任何具体点的类型信息都被擦除了,唯一知道的就是你在使用一个对象,因此List和List是相同的类型。
1、边界
classHasF{public void f() {System.out.println("f()");}
}class Ma{
T obj;
Ma(T t){ obj=t;}public void maf(){obj.f();}//这里是错的
}
假如你有一个HasF类,这个类中有一个f()方法,然后你想创建一个泛型类,并且想将obj的f()方法,这是java编译器无法实现的,因为擦除。为了调用f(),我们必须协助泛型类,给定泛型的边界,以此告知编译器只能接受遵循这个边界的类型。
用extends关键字,就能设置边界。
import java.util.*;classHasF{public void f() {System.out.println("f()");}
}class Ma{
T obj;
Ma(T t){ obj=t;}public void maf(){obj.f();}//这里是错的
}public classEx12 {public static voidmain(String[] args) {
HasF hasf= newHasF();
Ma ma = new Ma(hasf);
ma.maf();
}
}
边界声明T必须具有类型HasF或者从HasF导出的类型。
泛型类型参数将擦除到它的第一个边界,实际上编译器会把类型参数替换为它的擦除,上面的示例就是T擦除到HasF。
2、擦除的问题
泛型不能显式的引用运行时类型的操作中,例如转型、instanceof操作和new表达式,因为所有关于参数的类型信息都丢失了。例如:
class Foo{
T var;
}class Cat{}
定义这样两个类,Foo是一个泛型类,Cat是一个普通类
Foo foo = new Foo();
这里看起来class Foo中的所有操作都像是使用Cat来操作,泛型好像也在告诉我们所有的类型T都将会被替换,但事实上我们必须提醒自己,被擦除了,它只是一个Object,也就是说这里没有边界,应该说边界就是Object。
importjava.lang.reflect.Array;importjava.util.Arrays;class ArrayMaker{private Classkind;
ArrayMaker(Class kind){ this.kind =kind;}
T[] create(intsize) {return (T[])Array.newInstance(kind, size);//这里会有警告
}
}public classEx13 {public static void main(String[] args) throwsException{
ArrayMaker stringMaker =
new ArrayMaker(String.class);
String[] stringArray= stringMaker.create(9);
System.out.println(Arrays.toString(stringArray));
}
}
输出:
[null, null, null, null, null, null, null, null, null]
即使这里的泛型被指定为Class擦除就意味这kind实际被储存的是Class,由此在使用的时候,Array.newInstance(kind, size)并为拥有kind的类型信息,就是不知道kind是String.class,所以需要转型。
但是在创建一个容器情况稍有不同
public class Ex13{
List create(){return new ArrayList();}public static void main(String[] args) throwsException{
Ex13 ex = new Ex13();
List stringList =ex.create();
}
}
这里的编译器 不会给出任何的警告,尽管我们知道create中的被移除了。这样就能这样操作:
public class Ex13{
List create(T t , intn){
List result = new ArrayList();for(int i = 0; i < n; i++)
result.add(t);returnresult;
}public static void main(String[] args) throwsException{
Ex13 ex = new Ex13();
List stringList = ex.create("hello", 4);
System.out.println(stringList);
}
}
输出:
[hello, hello, hello, hello]
但是数组就不能这样做:
public class Ex13{
T[] create(T t ,intn){
T[] result= new T[n];//这里编译器无法编译
for(int i = 0; i < n; i++)
result[i]=t;returnresult;
}public static void main(String[] args) throwsException{
Ex13 ex = new Ex13();
List stringList = ex.create("hello", 4);
System.out.println(stringList);
}
}
因为T的类型被擦除了。一般都使用Array.newInstance()方法或者使用List。
3、擦除的补偿
public class Ex13{public voidf(Object o) {if(o instanceof T) {}//编译出错
T var = new T();//编译出错
T[] ts = new T[10];//编译出错
T[] tss = (T)new Object[10];//编译出错
}
}
因为擦除使泛型代码失去某些执行某些操作的能力,想要绕过这些问题进行编程需要引入类型标签对擦除进行补偿。
instanceof的使用失败 可以使用动态的ISinstance)
public class Ex13{
Classkind;
Ex13(Classkind){this.kind =kind;
}public booleanf(Object o) {returnkind.isInstance(o);
}public static voidmain(String[] args) {
Ex13 ex = new Ex13(String.class);
System.out.println(ex.f("233"));
System.out.println(ex.f(233));
}
}
输出:
true
false
T var = new T();创建类型实例无法实现,一部分原因是因为擦除了,另一部分原因是因为编译器不能验证T具有默认(无参)的构造器,解决方法还是同样的使用Class对象。
public class Ex13{
Classkind;
Ex13(Classkind){this.kind =kind;
}publicT f() {try{returnkind.newInstance();
}catch(Exception e) {throw newRuntimeException(e);
}
}public static voidmain(String[] args) {
String a= new Ex13(String.class).f();
}
}
调用用Class的newInstance()方法即可,但是如果在main中这样使用
public static voidmain(String[] args) {
Integer b= new Ex13(Integer.class).f();
}
就会失败,因为Integer是没有任何的默认的构造器。
可以使用显式的工厂,并限制其类型,使得只能接受实现这个类。
interface Factoryi{
T create();
}
先创建一个泛型接口,create()方法将返回一个T类对象。
可以
class IntegerFactory implements Factoryi{publicInteger create() {return new Integer(0);
}
}
可以直接实现这个接口。
classWidest{public static class Factory implements Factoryi{publicWidest create() {return newWidest();
}
}
}
可以在这个类中创建一个内部类去实现这个接口,
class Foo2{privateT x;public >Foo2(F factory){
x=factory.create();
}
}
这就是实际上的泛型类了,因为Integer和Widest都没有任何默认的构造器,所以就创建一个类实现Factoryi接口,实现其中的create()方法然后在泛型类从使用这个Factoryi作为边界,使用其中的create()方法,从而实现在泛型类从创建泛型类型实例。
泛型数组
不能创建泛型数组,一般的解决方案是使用Araayist:
public class Ex13{public List array = new ArrayList();public voidadd(T item) {
array.add(item);
}public T get(intindex) {returnarray.get(index);
}
}
这样和数组其实也能实现和数组差不多的功能。
还能通过传入Class类型对象,然后使用Array.newInstance()方法创建一个数组
class ArrayMaker{private Classkind;
ArrayMaker(Class kind){ this.kind =kind;}
T[] create(intsize) {return (T[])Array.newInstance(kind, size);//这里会有警告
}
}public classEx13 {public static voidmain(String[] args) {
String[] s= new ArrayMaker(String.class).create(10);
}
}
5、通配符
先看一个数组的特殊行为:
classFruit{}class Apple extendsFruit{}class Orange extendsFruit{}class Jon extendsApple{}public classEx14 {public static voidmain(String[] args) {
Fruit[] fruit= new Apple[10];
fruit[0] = newApple();
fruit[1] = newJon();try{
fruit[2] = newFruit();
}catch(Exception e) {
System.out.println(e.toString());
}try{
fruit[3] = newOrange();
}catch(Exception e) {
System.out.println(e.toString());
}
}
}
这里的输出是:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
这里创建了一个Apple数组,并将其赋值给一个Fruit数组。你可以将一个Apple或者Apple的子类对象放入数组,并不会有警告,和错误。
但是当你想要放入一个Fruit或是Orange对象的时候,编译器不会有警告但是运行的时候却能捕获到java.lang.ArrayStoreException异常,编译器不会给出警告是因为数组有一个Fruit[]引用,但是运行的时候数组机制知道这是一个Apple[]数组,所以就会抛出异常 。
类似的当我们试图使用泛型容器来代替数组的时候:
public classEx14 {public static voidmain(String[] args) {
List flist = new ArrayList();
}
}
这时编译器会直接给出警告,讲道理Apple是Fruit的子类,这应该属于向上转型,然而实际上这里并不是根本不是向上转型这里是两个List,Apple可以算是Fruit类型的,但是Apple的List并不是Fruit的List,这时如果想要建立某种类型的向上转型关系,就需要使用通配符了。
public classEx14 {public static voidmain(String[] args) {
List extends Fruit> flist = new ArrayList();
flist.add(newOrange());
flist.add(newApple());
flist.add(newJon());
}
}
然后创建List就不会报错了,但是后面的调用add()方法还是会给出警告。
flist的类型现在是List extends Fruit>,可以将其读为“具有任何从Fruit继承的类型的列表”,但是并不就意味着这个List就可以持有任何类型的Fruit。通配符指定的是没有指定具体类型,其实就是只能接受不能修改。
public classEx14 {public static voidmain(String[] args) {
List list = new ArrayList();
list.add(newApple());
list.add(newJon());
List extends Fruit> flist =list;
}
}
这样的使用就是没错的。