java-基礎-泛型

java泛型通配符問題。
 
java中的泛型基本用法參考《java編程思想》第四版 p.353
java泛型中比較難理解的主要是類型擦除和通配符相關。
 
1.類型擦除
在編譯期間,類型信息會被擦除,能夠認爲類型的檢測是在編譯期間進行的(見例1)
List<String> list = new ArrayList<>();
list.add("123");
list.add(new Object());//編譯器在編譯的時候會檢測到這個類型不匹配問題
因此在生成的class文件中不包含泛型中了類型信息。
所以下面的代碼會報錯
class Test{
public void method1(List<String> list){
System.out.println("String list");
}
public void method1(List<Integer> list){
System.out.println("Integer list");
}
}
由於類型擦除的緣故,因此method1中的參數List<String> 和List<Integer> 會被擦除爲 List 所以 method1方法簽名相同,因此報錯。
另外,類型擦除將擦除到他的第一個邊界,能夠經過下面的例子來理解
class F{
public void f(){}
}
class Test{
public static <T> void method1(T t){
t.f(); //錯誤,沒法肯定傳進來的參數就是類F的實例
}
public static <T extends F> void method2(T t){
t.f();//正確,由於編譯器在編譯期間就檢查了參數t是否爲類F或者類F的子類的實例 所以能夠調用f() 方法
}
}
2.類型擦除與多態
考慮下面這個例子:
public class Main1 {
public static void main(String[] args) {
F z = new Z();
z.setValue("233");
}
}
class F<T>{
private T t;
public void setValue(T t){
System.out.println("F's setValue method called");
this.t = t;
}
}
class Z extends F<String>{
@Override
public void setValue(String s) {
System.out.println("Z's setValue method called");
super.setValue(s);
}
}
從類型擦除的方面考慮 類F的setValue方法通過類型擦除後實際的簽名爲 public void setValue(Object obj)。
因此預測的打印結果應該是:
F's setValue method called
實際結果爲
Z's setValue method called
F's setValue method called,也就是說實現了子類方法覆蓋父類方法。
這其中的緣由是編譯器替咱們生成了橋方法(bridgemethod)
能夠經過反射查看類Z中有哪些方法:
Method[] methods = Z.class.getDeclaredMethods();
for( Method method : methods ){
System.out.print( method.getReturnType().getSimpleName() + " " + method.getName() + "( " );
Parameter[] parameters = method.getParameters();
for( Parameter p : parameters ){
System.out.print( p.getType().getSimpleName() + " " );
}
System.out.println( ")" );
}
打印結果:
void setValue( String )
void setValue( Object )//橋方法
其實調用的是橋方法setValue( Object )覆蓋了父類的方法,而後在該方法中調用void setValue( String )。
橋方法內容:
void setValue(Object str){
this.setValue( (String)str );
}
這樣就實現了覆蓋。
3.通配符
3.1上界
 
List<? extends Animal> list;//表示此list引用能夠指向泛型參數爲Animal或者其子類的List實現的實例。並非說這個list容器能夠添加Animal或者Animal子類的實例。(以前一直理解錯誤)
在看java泛型時,最讓我難以理解的就是通配符的問題,不太好理解。經過一個例子解釋一下。
前提:
class Animal{
public void walk(){}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Alaska extends Dog{}
 
public class Main2 {
public static void main(String[] args) {
List<Cat> catList = new ArrayList<Cat>();
List<Dog> dogList = new ArrayList<Dog>();
testList(catList);
testList(dogList);
}
public static void testList(List<? extends Animal> list){
list.add( new Cat() );//錯誤
list.add( new Dog() );//錯誤
list.add( new Animal() )//錯誤
//正確
for( Animal animal : list){
animal.walk();
}
}
}
看上面這段代碼,應該能有所理解。
假設聲明爲List<? extends Animal> 的 list 能夠添加Animal及其子類,那麼調用testList方法傳入catList時,能夠向catList中添 加Animal及其子類, 然而catList被聲明爲只能存放Cat類的實例,二者矛盾,所以 聲明爲List<? extends Animal> 的 list 沒法添加Animal及其子類(由於沒法肯定接收的list參數的泛型參數是什麼)。 可是能夠肯定傳遞給testList方法的list中存放的對象一定爲Animal或者其子類,所以迭代改list並用Animal接收。
3.2下界
 
List<? super Dog> list //表示此list引用能夠指向泛型參數爲Dog或者其父類的List實現的實例。並非說這個list容器能夠添加Dog或者Dog父類的實例。
前提:
class Animal{
public void walk(){}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Alaska extends Dog{}
 
public class Main2 {
public static void main(String[] args) {
testList1( new ArrayList<Dog>() );
testList1( new ArrayList<Animal>());
}
public static void testList1( List<? super Dog> list ){
list.add(new Dog());
list.add( new Alaska() );
list.add( new Animal() );//錯誤
for( Dog dog: list ){}//錯誤
}
}
testList1方法能夠接受泛型參數爲Dog或者Dog父類的List實現的實例(List<Dog>或者List<Animal>),那麼在方法testList1中往list添加Dog類或者Dog類的子類的實例是可行的,由於能夠肯定傳遞給testList1方法的list容器能夠存放Dog或者Dog的父類。
可是沒法肯定肯定具體爲哪一個父類。所以對list迭代並用Dog引用接收是錯誤的。
 
文章中的結論均爲我的思考總結,並不是權威,若有錯誤,遺漏,望指正。
 
參考資料:
相關文章
相關標籤/搜索