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,歡迎交流指正!
總結
以上是生活随笔為你收集整理的java 泛型 擦除_Java泛型和类型擦除的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何测量代码执行时间
- 下一篇: java创建子类对象的步骤_一通Spri