java 泛型 擦除_Java泛型和类型擦除
一 前言:初識(shí)泛型
廢話不說,先來看一段代碼:
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類是一個(gè)容器,它的作用是用來保存其他類的,這里我們用它來保存SomeNode類,隨后把它取出來,編譯運(yùn)行,結(jié)果如下:
Error:(21, 43) java: incompatible types
required: SomeNode
found: java.lang.Object
意思是,需要的是SomeNode,取出來的卻是Object,如此看來,如果我想保存SomeNode類,就只能把data聲明為SomeNode:
private SomeNode data;
這就意味著我們需要為每一個(gè)類創(chuàng)造一個(gè)Holder,這肯定是不行的,于是泛型的作用來了,泛型,可以理解為任何類型,意思是我可以聲明一個(gè)可以容納任何類型的容器:
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{}
注意寫法,在類聲明后面加個(gè)就行了,你也可以加,只是一個(gè)占位符,形參而已。然后我們?cè)侔阉〕鰜?#xff1a;
Process finished with exit code 0
程序沒有報(bào)錯(cuò),如果這時(shí)候我們使用holder的set()方法去插入設(shè)置一些非SomeNode類型的值,代碼如下:
public static void main(String[] args){
Holder holder = new Holder(new SomeNode());
SomeNode someNode = holder.getData();
holder.setData("AAAA");
}
看結(jié)果:
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
泛型機(jī)制就自動(dòng)為我們報(bào)錯(cuò),很方便。
二 泛型類:元組(Tuple),返回多個(gè)對(duì)象
熟悉python的同學(xué)都知道元組的概念,它是一個(gè)只讀列表,在返回多個(gè)結(jié)果時(shí)是很有用的,我們利用泛型特性來創(chuàng)造一個(gè)包含兩個(gè)對(duì)象的元組:
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+")";
}
}
來看結(jié)果:
(Monkey,12)
是不是很方便:)如果想要一個(gè)長(zhǎng)度為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+")";
}
}
結(jié)果如下:
(Dog,12,true)
三 泛型接口
泛型接口的定義和泛型類的定義類似,我們來定義一個(gè)生成器接口:
public interface Generator {
T next();
}
接著我們實(shí)現(xiàn)這個(gè)接口,來生成斐波拉契數(shù):
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());
}
}
}
四 泛型方法
比起泛型類,我們更推薦去使用泛型方法,泛型方法定義起來也很簡(jiǎn)單,我們只需將泛型參數(shù)放在返回類型前面即可:
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);
}
}
這里我們定義了一個(gè)泛型方法f(),并使用getClass獲取類的相關(guān)信息(關(guān)于Class對(duì)象的知識(shí)點(diǎn)這里),來看結(jié)果:
java.lang.String
java.lang.Integer
java.lang.Boolean
這里還要注意一下Varargs(變長(zhǎng)參數(shù))機(jī)制和泛型的結(jié)合:
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);
}
}
結(jié)果如下:
[A, B, C, D]
六 類型擦除
在認(rèn)識(shí)類型擦除之前,我們首先要明白編譯器對(duì)泛型的處理有兩種方式:
1.Code specialization
在實(shí)例化一個(gè)泛型類或者泛型方法是都生成一份新的字節(jié)碼,比如對(duì)于List,List,List產(chǎn)生三份不同的字節(jié)碼。
2.Code sharing
對(duì)每個(gè)泛型類只生成唯一的一份目標(biāo)代碼;該泛型類的所有實(shí)例都映射到這份目標(biāo)代碼上,在需要的時(shí)候執(zhí)行類型檢查和類型轉(zhuǎn)換。參考文章
C++的模板是典型的Code specialization實(shí)現(xiàn),而Java泛型則是Code sharing實(shí)現(xiàn),將多種泛型類形實(shí)例映射到唯一的字節(jié)碼表示是通過類型擦除(type erasue)實(shí)現(xiàn)的。對(duì)擦除更通俗的理解就是:編譯器生成的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);
}
}
結(jié)果如下:
true
也就是說我們?cè)趯?shí)例化ArrayList和實(shí)例化ArrayList時(shí)是共享一份目標(biāo)代碼的,泛型類類型信息在編譯的過程中被擦除了。對(duì)于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);
}
}
看結(jié)果:
[ABC, 123]
我們很順利的把Integer型的123插入到了String的List里:)
七 后記
由于類型擦除的存在,我們往往會(huì)在使用泛型特性的時(shí)候遇到一些詭異的問題,由于篇幅原因,這里不展開了:)我將在另外一篇文章中集中的總結(jié)一下這方面的問題。
我的微信號(hào)是aristark,歡迎交流指正!
總結(jié)
以上是生活随笔為你收集整理的java 泛型 擦除_Java泛型和类型擦除的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何测量代码执行时间
- 下一篇: 网页设计的价格标准