java 擦除_Java——擦除

直接代码分析一波:

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;

}

}

这样的使用就是没错的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值