java 公雞五元錢一隻,母雞三元錢一隻,小雞一元錢三隻,現有一百元錢欲買百雞,共有多少種買法
1. 泛型
泛型提供了編譯時型別安全檢測機制,該機制允許程式設計師在編譯時檢測到非法的型別。泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。比如我們要寫一個排序方法,能夠對整型陣列、字串陣列甚至其他任何型別的陣列進行排序,我們就可以使用 Java 泛型。
1.1 為什麼需要泛型
泛型可以使得型別引數化, 泛型有如下的好處
- 型別引數化, 實現程式碼的複用
- 強制型別檢查, 保證了型別安全,可以在編譯時就發現程式碼問題, 而不是到在執行時才發現錯誤
- 不需要進行強制轉換。
1.2 型別引數命名規約
按照慣例,型別引數名稱是單個大寫字母。 通過規約, 我們可以容易區分出型別變數和普通類、介面。
- E - 元素
- T - 型別
- N - 數字
- K - 鍵
- V - 值
- S,U,V - 第2種類型, 第3種類型, 第4種類型
2. 泛型的使用
泛型有三種使用方式,分別為:泛型類、泛型介面、泛型方法
2.1 泛型類
泛型型別用於類的定義中,被稱為泛型類。通過泛型可以完成對一組類的操作對外開放相同的介面。最典型的就是各種容器類,如:List、Set、Map。
泛型類的最基本寫法
class 類名稱 <泛型標識:可以隨便寫任意標識號,標識指定的泛型的型別>{ private 泛型標識 /*(成員變數型別)*/ var; ..... } }
舉個簡單例子:
publicclass Test { public static void main(String[] args) { //泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別 //傳入的實參型別需與泛型的型別引數型別相同,即為Integer. Generic<Integer> genericInteger = new Generic<Integer>(123456); //傳入的實參型別需與泛型的型別引數型別相同,即為String. Generic<String> genericString = newGeneric<String>("key_vlaue"); System.out.printf("key id :%d\n", genericInteger.getKey()); System.out.printf("key id :%s\n", genericString.getKey()); } } //此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型 //在例項化泛型類時,必須指定T的具體型別 class Generic<T>{ //key這個成員變數的型別為T,T的型別由外部指定 private T key; public Generic(T key) { //泛型構造方法形參key的型別也為T,T的型別由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值型別為T,T的型別由外部指定 return key; } }
---------------
key id :123456
key id :key_vlaue
定義的泛型類,就一定要傳入泛型型別實參麼?並不是這樣,在使用泛型的時候如果傳入泛型實參,則會根據傳入的泛型實參做相應的限制,此時泛型才會起到本應起到的限制作用。如果不傳入泛型型別實參的話,在泛型類中使用泛型的方法或成員變數定義的型別可以為任何的型別。
看下邊的例子:
Generic generic = new Generic("111111"); Generic generic1 = new Generic(4444); Generic generic2 = new Generic(55.55); Generic generic3 = new Generic(false); System.out.printf("key id :%s\n", generic.getKey()); System.out.printf("key id :%s\n", generic1.getKey()); System.out.printf("key id :%s\n", generic2.getKey()); System.out.printf("key id :%s\n", generic3.getKey());
------------------
key id :111111
key id :4444
key id :55.55
key id :false
注意:
- 泛型的型別引數只能是類型別,不能是簡單型別,比如,可以為Integer不能為int。
- 不能對確切的泛型型別使用instanceof操作。如下面的操作是非法的,編譯時會出錯。
if(ex_num instanceof Generic<Number>){ }
2.2 泛型介面
泛型介面與泛型類的定義及使用基本相同。泛型介面常被用在各種類的生產器中,可以看一個例子:
//定義一個泛型介面 public interface Generator<T> { public T next(); }
當實現泛型介面的類,未傳入泛型實參時:
/** * 未傳入泛型實參時,與泛型類的定義相同,在宣告類的時候,需將泛型的宣告也一起加到類中 * 即:class FruitGenerator<T> implements Generator<T>{ * 如果不宣告泛型,如:class FruitGenerator implements Generator<T>,編譯器會報錯:"Unknown class" */ class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; } }
當實現泛型介面的類,傳入泛型實參時:
/** * 傳入泛型實參時: * 定義一個生產器實現這個介面,雖然我們只建立了一個泛型介面Generator<T> * 但是我們可以為T傳入無數個實參,形成無數種類型的Generator介面。 * 在實現類實現泛型介面時,如已將泛型型別傳入實參型別,則所有使用泛型的地方都要替換成傳入的實參型別 * 即:Generator<T>,public T next();中的的T都要替換成傳入的String型別。 */ public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
2.3 泛型方法
泛型方法的宣告, 型別變數放在修飾符之後,在返回值之前
public class EqualMethodClass { public static <T> boolean equals(T t1, T t2){ return t1.equals(t2); } }
如上所示, 其中<T>
是不能省略的。 而且可以是多種型別, 如<K, V>
public class Util { public static <K, V> boolean sameType(K k, V v) { return k.getClass().equals(v.getClass()); } }
泛型方法的呼叫,呼叫時, 在方法之前指定引數的型別
@Test public void equalsMethod(){ boolean same = EqualMethodClass.<Integer>equals(1,1); System.out.println(same); }
泛型類,是在例項化類的時候指明泛型的具體型別;泛型方法,是在呼叫方法的時候指明泛型的具體型別。
舉個簡單例子:
public class GenericMethodTest {
//泛型方法 public static <E> void printArray(E[] inputArray) { for (E element:inputArray) { System.out.printf("%s ", element); } System.out.println(); } public static void main(String[] args) { Integer[] intArrays = {1, 2, 3, 4, 5}; Double[] doubleArrays = {1.1, 2.2, 3.3, 4.4, 5.5}; Character[] characterArrays = {'H', 'E', 'L', 'L', 'O'}; System.out.print( "整型陣列元素為:" ); printArray(intArrays); System.out.print( "\n雙精度型陣列元素為:" ); printArray(doubleArrays); System.out.print( "\n字元型陣列元素為:" ); printArray(characterArrays); } }
-------------
整型陣列元素為:1 2 3 4 5
雙精度型陣列元素為:1.1 2.2 3.3 4.4 5.5
字元型陣列元素為:H E L L O
3. 泛型的子型別規則
子型別規則,即任何引數化的型別是原生態型別的一個子型別,比如List<String>是原生態型別List的一個子型別,而不是引數化List<Object>的子型別。
由於子型別規則的存在,我們可以將List<String>傳遞給List型別的引數
public static void main(String[] args) { List<String> strings = new ArrayList<>(); unsafeAdd(strings, new Integer(1)); String s = strings.get(0); } private static void unsafeAdd(List list, Object o){ list.add(o); }
雖然編譯器是沒有報錯的,但是編譯過程會出現以下提示,表明編寫了某種不安全的未受檢的操作
但是我們不能將List<String>傳遞給List<Object>型別引數
public static void main(String[] args) { List<String> strings = new ArrayList<>(); unsafeAdd(strings, new Integer(1)); String s = strings.get(0); } private static void unsafeAdd(List<Object> list, Object o){ list.add(o); }
編譯後就直接報錯,事實上編譯器就會自動提示有錯誤
4.無限制的萬用字元型別
使用原生態型別是很危險的,但是如果不確定或不關心實際的型別引數,那麼在Java 1.5之後Java有一種安全的替換方法,稱之為無限制的萬用字元型別(unbounded wildcard type),可以用一個“?”代替,比如Set<?>表示某個型別的集合,可以持有任何集合。
那麼無限制通配型別與原生態型別有啥區別呢?原生態型別是可以插入任何型別的元素,但是無限制通配型別的話,不能新增任何元素(null除外)。
問:那麼這樣的萬用字元型別有意義嗎?因為你並不知道它到底能加入啥樣的元素,但是又美其名曰“無限制”。
不能說沒有意義,因為它的出現歸根結底是為了防止破壞集合型別約束條件,並且可以根據需要使用泛型方法或者有限制的萬用字元型別(boundwildcardtype)介面某些限制,提高安全性。
5. 泛型的可擦除性
我們先看一下程式碼,看看結果:
public static void main(String[] args) { List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); // 輸出為true,擦除後的型別為List System.out.println(l1.getClass() == l2.getClass()); }
結果為true,這是因為:泛型資訊可以在執行時被擦除,泛型在編譯期有效,在執行期被刪除,也就是說所有泛型引數型別在編譯後都會被清除掉。歸根結底不管泛型被引數具體化成什麼型別,其class都是RawType.class,比如List.class,而不是List<String>.class或List<Integer>.class
事實上,在類文字中必須使用原生態型別,不準使用引數化型別(雖然允許使用陣列型別和基本型別),也就是List.class、String[].class和int.class都是合法的,而List<String>.class和List<?>.class不合法
6.有限制的萬用字元型別
之前提到過的無限制的萬用字元型別就提到過,無限制的萬用字元單純只使用"?"(如Set<?>),而有限制的萬用字元往往有如下形式,通過有限制的萬用字元型別可以大大提升API的靈活性。
(1)E的某種超類集合(介面):Collection<? super E>、Interface<? super E>、
(2)E的某個子類集合(介面):Collection<? extends E>、Interface<? extends E>
問1:那麼什麼時候使用extends關鍵字,什麼使用super關鍵字呢?
有這樣一個PECS(producer-extends, consumer-super)原則:如果引數化型別表示一個T生產者,就使用<? extends T>,如果表示消費者就是<? super T>。可以這樣助記
問2:什麼是生產者,什麼是消費者
1)生產者:產生T不能消費T,針對collection,對每一項元素操作時,此時這個集合時生產者(生產元素),使用Collection<? extends T>。只能讀取,不能寫入
2)消費者:不能生產T,只消費使用T,針對collection,新增元素collection中,此時集合消費元素,使用Collection<? super T>,只能新增T的子類及自身,用Object接收讀取到的元素
舉例說明:生產者
1)你不能在List<? extends Number>中add操作,因為你增加Integer可能會指向List<Double>,你增加Double可能會指向Integer。根本不能確保列表中最終儲存的是什麼型別。換句話說Number的所有子類從類關係上來說都是平級的,毫無聯絡的。並不能依賴型別推導(型別轉換),編譯器是無法確實的實際型別的!
2)但是你可以讀取其中的元素,並保證讀取出來的一定是Number的子類(包括Number),編譯並不會報錯,換句話說編譯器知道里面的元素都是Number的子類,不管是Integer還是Double,編譯器都可以向下轉型
舉例說明:消費者
1)編譯器不知道存入列表中的Number的超類具體是哪一個,只能使用Object去接收
2)但是只可以新增Interger及其子類(因為Integer子類也是Integer,向上轉型),不能新增Object、Number。因為插入Number物件可以指向List<Integer>物件,你插入Object,因為可能會指向List<Ineger>物件
注意:Comparable/Comparator都是消費者,通常使用Comparator<? Super T>),可以將上述的max方法進行改造:public static <T extends Comparable<? super T>> T max(List<? extends T> list) { Iterator<? extends T> iterator = list.iterator(); T result = iterator.next(); while (iterator.hasNext()) { T t = iterator.next(); if (t.compareTo(result) > 0) { result = t; } } return result; }