淺談 Java 範型
作者:hubery, 時間:2018.11.30-12.01
說起範型,算是java中逼格較高的技能了吧,每每看到某個開源框架或者技術架構最頂層的介面實現,全是P/Q/R/T等的型別引數,瞬間跪著看。其實很多時候都是一葉障目,打算靜下心來研究下,寫幾個簡單的類,稍微一執行感受下。
期間查閱了多次 Java程式設計思想
這本聖經,本人理解的還是太淺,暫且一看。
推薦個專案: ofollow,noindex">android-architecture
googlesample中的android-architecture專案,裡面基本都是Google的各風格的專案,值得一看,其中大量用到了範型。
簡單範型類
public class Main { static class Automobile {} public static class Holder1 { private Automobile a; public Holder1(Automobile a) { this.a = a; } Automobile get() { return a; } } public static class Holder2<T> { private T a; public Holder2(T a) {this.a = a;} public void set(T a) { this.a = a; } public T get() {return a;} } public static void main(String[] args) { Holder1 h1 = new Holder1(new Automobile()); Holder2<Automobile> h2 = new Holder2<Automobile>(new Automobile()); } } 複製程式碼
該測試程式碼中,Holder1是普通類使用,Holder2是用了範型T。 範型的主要目的是: 用來指定T是什麼型別,由編譯器來保證T的正確性
。 我們要的是暫時不指定型別,等用到的時候再決定是什麼型別。要達到這個效果,需要用型別引數T。 用尖括號括住,放在類名後面:Holder2<T>
,後續使用該類的時候用實際型別來替換此型別引數T。
範型核心: 告訴編譯器想用什麼型別,然後編譯器會幫你處理一切細節
。 一般來說,範型和其他型別差不多,只不過範型有型別引數而已。 使用範型時只需要 指定範型的名稱和型別引數列表
即可。
範型介面
範型可應用於介面。設計模式中工廠模式的應用:物件生成器。 如:
public interface Generator<T> { T next(); } 複製程式碼
寫個生成器,next方法返回T。介面與類在使用範型上,沒區別。
public class Fibonacci implements Generator<Integer> { private int count = 0; @Override public Integer next() { return fib(count++); } private int fib(int n) { if(n < 2) return 1; return fib(n - 2) + fib(n - 1); } public static void main(String[] args) { Fibonacci gen = new Fibonacci(); for (int i = 0; i < 10; i++) { System.out.println(gen.next() + " "); } } } 複製程式碼
斐波那契數列數列的應用,實現範型介面,指定T為Integer型,相應next回撥方法返回的T也替換成Integer型。感受一下,完美。 至於演算法方面不做多深究,這裡主要用來感受範型介面,找找感覺。
範型方法
範型也可以用在方法上。範型方法所在的類,可以是範型類,也可以是普通類,沒相關性。 範型方法指導原則: 無論何時,只要你能做到,儘量使用範型方
法。 範型方法的定義: 只需將範型引數列表放到返回值之前即可
。 看程式碼:
public class GenericsMethods { // 範型方法:將引數列表寫到返回值之前就可以了 public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericsMethods gm = new GenericsMethods(); gm.f(""); gm.f(20); gm.f(1.1); gm.f(1.0F); gm.f(gm); } } 複製程式碼
列印結果:
java.lang.String java.lang.Integer java.lang.Double java.lang.Float cn.hubery.generic.GenericsMethods 複製程式碼
程式碼中,f()擁有型別引數,由該方法的返回型別前面的型別引數列表指明的。
注:
使用範型類時,建立物件的時候必須指定型別引數的具體型別;</p> 使用範型方法時,不必指明引數型別,編譯器會自行推斷出所需的具體型別; 複製程式碼
寫個通用的Generator 範型類+範型方法
public class BasicGenerator<T> implements Generator<T> { private Class<T> type; public BasicGenerator(Class<T> type) { this.type = type; } @Override public T next() { try { return type.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } public static <T> Generator<T> create(Class<T> type) { return new BasicGenerator<T>(type); } } 複製程式碼
寫個輔助物件類:
public class CountedObject { private static long counter = 0; private final long id = counter++; public long id() { return id; } public String toString() { return "CountedObject" + id; } } 複製程式碼
寫個測試類:
public class BasicGeneratorTest { public static void main(String[] args) { Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); for (int i = 0; i < 5; i++) { System.out.println(gen.next()); } } } 複製程式碼
列印情況:
CountedObject0 CountedObject1 CountedObject2 CountedObject3 CountedObject4 複製程式碼
通過測試看到,用範型方法建立Generator物件,大大減少程式碼量。範型方法的入參要求傳入Class物件,故可以推斷出確切型別。還是得多揣摩下。
匿名內部類
待研究。
邊界
可以在範型的引數型別上設定限制條件。 由於擦除動作會移除型別資訊,故用無界範型引數呼叫的方法只是那些Object可用的方法。 如果將這個引數限制在某個型別的子集中,那我們就可以呼叫這些型別子集的方法。 為了設定這種限制,java的範型用了extends關鍵字。 此處的extends與傳統意義上的類繼承不是一回事兒。 寫法是: TestClass<T extends LineClass>
package cn.hubery.generic.line; import java.util.List; public class EpicBattle { interface SuperPower{} interface XRayVision extends SuperPower { void seeThroughWalls(); } interface SuperHearing extends SuperPower { void hearSubtleNoises(); } interface SuperSmell extends SuperPower { void trackBySmell(); } static class SuperHero<POWER extends SuperPower> { POWER power; SuperHero(POWER power) { this.power = power; } POWER getPower() { return power; } } class SuperSleuth<POWER extends XRayVision> extends SuperHero<POWER> { SuperSleuth(POWER power) { super(power); } void see() { power.seeThroughWalls(); } } static class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> { CanineHero(POWER power) { super(power); } void hear() { power.hearSubtleNoises(); } void smell() { power.trackBySmell(); } } static class SuperHearSmell implements SuperHearing, SuperSmell { @Override public void hearSubtleNoises() { } @Override public void trackBySmell() { } } static class DogBoy extends CanineHero<SuperHearSmell> { DogBoy() { super(new SuperHearSmell()); } } static <POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero) { hero.getPower().hearSubtleNoises(); } static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) { hero.getPower().hearSubtleNoises(); hero.getPower().trackBySmell(); } public static void main(String[] args) { DogBoy dogBoy = new DogBoy(); useSuperHearing(dogBoy); superFind(dogBoy); List<? extends SuperHearing> audioBoys; //List<? extends SuperHearing & SuperSmell> dogBoy;// error } } 複製程式碼
講真,有時候 意會不能言傳,容我回頭組織下語言再慢慢道來。
萬用字元
範型引數表示式中的問號 ?
public class TPF { static class Fruit{} static class Apple extends Fruit{} public static void main(String[] args) { List<? extends Fruit> flist = Arrays.asList(new Apple()); //flist.add(new Apple());// 不能隨意向flist中新增物件 //flist.add(new Fruit()); Apple a = (Apple) flist.get(0); flist.contains(new Apple()); flist.indexOf(new Apple()); } } 複製程式碼
flist型別是List<? extends Fruit>,可以理解問:具有任何從Fruit繼承的型別的列表。但,這實際上不代表該List可以持有任何型別的Fruit。 萬用字元?引用的是明確的型別,因此意味著:某種flist飲用沒有指定的具體型別。 例子還沒想清楚怎麼寫,回頭補上。
注意事項
基本型別不能作為型別引數T
如:不能建立ArrayList之類的。如果需要,jdk會啟用自動包裝機制,int->Integer。
實現引數化介面
因為編譯器會擦除範型引數,所以要注意,使用範型類時儘量型別不一致。
過載
由於存在擦除,過載方法會產生相同的型別簽名,故:保險起見,方法名儘量不一致。
注: 剛通了個宵,白天睡了個回籠覺,平復心情中,現在整理思緒,關於範型的理解會慢慢在本文中補充,歡迎拍磚。
天星技術團QQ: 557247785
。
