java 泛型 擦除_Java泛型和类型擦除

一 前言:初识泛型

废话不说,先来看一段代码:

public class Holder {

private Object data;

public Holder(Object data ){

this.data = data;

}

public void setData(Object data) {

this.data = data;

}

public Object getData() {

return data;

}

public static void main(String[] args){

Holder holder = new Holder(new SomeNode());

SomeNode someNode = holder.getData();

}

}

class SomeNode{}

Holder类是一个容器,它的作用是用来保存其他类的,这里我们用它来保存SomeNode类,随后把它取出来,编译运行,结果如下:

Error:(21, 43) java: incompatible types

required: SomeNode

found: java.lang.Object

意思是,需要的是SomeNode,取出来的却是Object,如此看来,如果我想保存SomeNode类,就只能把data声明为SomeNode:

private SomeNode data;

这就意味着我们需要为每一个类创造一个Holder,这肯定是不行的,于是泛型的作用来了,泛型,可以理解为任何类型,意思是我可以声明一个可以容纳任何类型的容器:

public class Holder {

private T data;

public Holder(T data ){

this.data = data;

}

public void setData(T data) {

this.data = data;

}

public T getData() {

return data;

}

public static void main(String[] args){

Holder holder = new Holder(new SomeNode());

SomeNode someNode = holder.getData();

}

}

class SomeNode{}

注意写法,在类声明后面加个就行了,你也可以加,只是一个占位符,形参而已。然后我们再把它取出来:

Process finished with exit code 0

程序没有报错,如果这时候我们使用holder的set()方法去插入设置一些非SomeNode类型的值,代码如下:

public static void main(String[] args){

Holder holder = new Holder(new SomeNode());

SomeNode someNode = holder.getData();

holder.setData("AAAA");

}

看结果:

Error:(22, 15) java: method setData in class Holder cannot be applied to given types;

required: SomeNode

found: java.lang.String

reason: actual argument java.lang.String cannot be converted to SomeNode by method invocation conversion

泛型机制就自动为我们报错,很方便。

二 泛型类:元组(Tuple),返回多个对象

熟悉python的同学都知道元组的概念,它是一个只读列表,在返回多个结果时是很有用的,我们利用泛型特性来创造一个包含两个对象的元组:

public class Tuple {

public static void main(String[] args){

TwoTuple t = new TwoTuple("Monkey",12);

System.out.println(t.toString());

}

}

class TwoTuple{

final A first;

final B second;

public TwoTuple(A a,B b){

first = a;

second = b;

}

public String toString(){

return "("+first+","+second+")";

}

}

来看结果:

(Monkey,12)

是不是很方便:)如果想要一个长度为3的元组可以这么写:

public class Tuple {

public static void main(String[] args){

ThreeTuple t = new ThreeTuple("Dog",12,true);

System.out.println(t.toString());

}

}

class TwoTuple{

final A first;

final B second;

public TwoTuple(A a,B b){

first = a;

second = b;

}

public String toString(){

return "("+first+","+second+")";

}

}

class ThreeTuple extends TwoTuple{

final C three;

public ThreeTuple(A a,B b,C c){

super(a,b);

three = c;

}

public String toString(){

return "("+first+","+second+","+three+")";

}

}

结果如下:

(Dog,12,true)

三 泛型接口

泛型接口的定义和泛型类的定义类似,我们来定义一个生成器接口:

public interface Generator {

T next();

}

接着我们实现这个接口,来生成斐波拉契数:

public class Fib implements Generator {

private int count = 0;

@Override

public Integer next() {

return fib(count++);

}

private int fib(int n){

if (n<2)

return 1;

else

return fib(n-2) + fib(n-1);

}

public static void main(String[] args){

Fib f = new Fib();

for (int i=0;i<100;i++){

System.out.println(f.next());

}

}

}

四 泛型方法

比起泛型类,我们更推荐去使用泛型方法,泛型方法定义起来也很简单,我们只需将泛型参数放在返回类型前面即可:

public class GenericMethods {

public void f(T x){

System.out.println(x.getClass().getName());

}

public static void main(String[] args){

GenericMethods g = new GenericMethods();

g.f("Hello");

g.f(100);

g.f(true);

}

}

这里我们定义了一个泛型方法f(),并使用getClass获取类的相关信息(关于Class对象的知识点这里),来看结果:

java.lang.String

java.lang.Integer

java.lang.Boolean

这里还要注意一下Varargs(变长参数)机制和泛型的结合:

public class GenericVarargs {

public static List makeList(T...args){

List list = new ArrayList();

for (T item : args){

list.add(item);

}

return list;

}

public static void main(String[] args){

List list = makeList("A","B","C","D");

System.out.println(list);

}

}

结果如下:

[A, B, C, D]

六 类型擦除

在认识类型擦除之前,我们首先要明白编译器对泛型的处理有两种方式:

1.Code specialization

在实例化一个泛型类或者泛型方法是都生成一份新的字节码,比如对于List,List,List产生三份不同的字节码。

2.Code sharing

对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。参考文章

C++的模板是典型的Code specialization实现,而Java泛型则是Code sharing实现,将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。对擦除更通俗的理解就是:编译器生成的bytecode是不包涵泛型信息的。我们看下面的代码:

public class ErasedType {

public static void main(String[] args){

Class c1 = new ArrayList().getClass();

Class c2 = new ArrayList().getClass();

System.out.println(c1 == c2);

}

}

结果如下:

true

也就是说我们在实例化ArrayList和实例化ArrayList时是共享一份目标代码的,泛型类类型信息在编译的过程中被擦除了。对于JVM来说,它只看到一份ArrayList(原始类型)而已。我们还可以从反射的角度来理解类型擦除:

public class ErasedType {

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

List list = new ArrayList();

list.add("ABC");

list.getClass().getMethod("add",Object.class).invoke(list,123);

System.out.println(list);

}

}

看结果:

[ABC, 123]

我们很顺利的把Integer型的123插入到了String的List里:)

七 后记

由于类型擦除的存在,我们往往会在使用泛型特性的时候遇到一些诡异的问题,由于篇幅原因,这里不展开了:)我将在另外一篇文章中集中的总结一下这方面的问题。

我的微信号是aristark,欢迎交流指正!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值