一、引言 在學習集合的時候我們會發現一個問題,將一個對象丟到集合中後,集合併不記住對象的類型,統統都當做Object處理,這樣我們取出來再使用時就得強制轉換類型,導致代碼臃腫,而且加入集合時都是以Object,沒做類型檢查,那麼強制轉換就容易出錯,泛型的誕生就是為解決這些問題。 二、使用泛型 泛型是 ...
一、引言
在學習集合的時候我們會發現一個問題,將一個對象丟到集合中後,集合併不記住對象的類型,統統都當做Object處理,這樣我們取出來再使用時就得強制轉換類型,導致代碼臃腫,而且加入集合時都是以Object,沒做類型檢查,那麼強制轉換就容易出錯,泛型的誕生就是為解決這些問題。
二、
ArrayList<Integer> list = new ArrayList<Integer>();
這是Java 7之前的寫法,很明顯構造器上面的泛型沒有必要,現在推薦以下寫法:
ArrayList<Integer> list = new ArrayList<>();
既然我已經指定了類型,那麼添加時只能添加Integer,並且使用時可以直接當做Integer使用
System.out.println(list.get(2)+3);
這種參數化類型就是泛型,泛型就是允許在定義類、介面、方法時使用類型形參,這個參數形參將在申明變數、創建對象、調用方法時動態指定。
三、
public interface List<E> extends Collection<E> public interface Map<K,V>
List介面定義時指定了一個類型形參,Map介面定義了兩個類型形參。介面定義了形參之後,在介面中形參就可以當做一種類型來使用,那麼其中的方法就可以使用類型形參
boolean add(E e);
這種方式其實也是一種代碼復用,我們通過類型形參,高度的將參數抽象,而不需要每種類型都去重新定義類,只要在使用時確定類型即可。我們也可以自定義泛型類
public class WebResult<T> { //使用T類型形參定義實例變數 private T data; public WebResult() { } //使用T類型形參構造對象 public WebResult(T data) { this.data = data; } public void setData(T data) { this.data = data; } public T getData() { return this.data; } public static void main(String[] args) { WebResult<String> webResult = new WebResult<>("返回一個String對象"); System.out.println(webResult.getData()); WebResult<Integer> webResult1 = new WebResult<>(10); System.out.println(webResult1.getData()); } }
四、
public static void main(String[] str){ ArrayList<String> arrayList=new ArrayList(); test(arrayList); } public static void test(List<Object> test){ for (int i = 0; i <test.size() ; i++) { System.out.println(test.get(i)); } }
這段代碼會出現編譯錯誤,因為List<String>對象不能作為List<Object>使用,這說明泛型不是協變的,因為之前數組的設計是協變的,導致存在安全性問題,而泛型的設計原則是編譯時不出現警告就不會出現類型轉換錯誤,那為了表示各種泛型的父類,就引入了通配符:?這個問號代表可以是匹配任何類型。
將方法修改:
public static void test(List<?> test){ for (int i = 0; i <test.size() ; i++) { System.out.println(test.get(i)); } }
這樣便可以順利編譯,我們再加上這段代碼:
public static void main(String[] str){ ArrayList<String> arrayList=new ArrayList(); test(arrayList); List<?> strings = arrayList; strings.add("abc"); }
這裡我們可以將arrayList給strings,按說這個時候是不能賦值的,因為List不知道類型參數的值,這是編譯器作用,可以進行類型推理,但是後面的strings.add("abc")是不能通過編譯的,編譯器不能對 List 的類型參數作出足夠嚴密的推理,以確定將 String 傳遞給 List.add() 是類型安全的。所以編譯器將不允許這麼做。
4.1 設置通配符上限
List<?>這種方式,通配的是所有的類型,很多時候我們可以確定是哪一類對象可以添加進去,我們只希望它代表某一類泛型的父類,這個時候我們可以設置通配符的上限。
//動物類 public abstract class Animal { public abstract void say(); } public class Cat extends Animal { @Override public void say() { System.out.println("喵喵"); } } public class Dog extends Animal { @Override public void say() { System.out.println("旺旺"); } }
這個時候我們就限定了上限
public static void test1(List<? extends Animal> animals) { for (int i = 0; i < animals.size(); i++) { animals.get(i).say(); } }
我們也可以直接在定義類型形參的時候設置上限
public class WebResult<T extends Animal> {
//將src中的集合複製到dest,並返回最後一個值 public static <T> T copy(Collection<T> dest, Collection<? extends T> src) { T last = null; for (T ele : src) { last = ele; dest.add(ele); } return last; }
功能比較很簡單,將src中的集合複製到dest,並返回src最後一個值
List<Number> ln = new ArrayList<>(); List<Integer> li = new ArrayList<>(); //編譯出錯,類型不確定 Integer last = copy(ln, li);
這個時候出錯,因為雖然我們知道返回的值一定是Integer,但是由於copy方法的返回值並不是,所有相當於我們在複製的過程中丟失了src的類型,如果我們想定義約束關係使得返回值明確即:dest集合元素類型與src的關係要麼相同要麼是其父類,為了表示這種約束關係,引入了<? super T> 這個通配符表示它必須是T本身或者T的父類。
//將src中的集合複製到dest,並返回最後一個值 public static <T> T copy(Collection<? super T> dest, Collection<? extends T> src) { T last = null; for (T ele : src) { last = ele; dest.add(ele); } return last; }
五、
static <T> void arrayToList(T[] a, List<T> list) { for (T o : a) { list.add(o); } }
調用如下:
Object[] objects = new Object[10]; List<Object> list = new ArrayList<>(); arrayToList(objects, list); Integer[] integers = new Integer[10]; List<Integer> integerList = new ArrayList(); arrayToList(integers, integerList); String[] strings = new String[10]; List<String> stringList = new ArrayList<>(); arrayToList(strings, stringList); //編譯錯誤,類型不正確 arrayToList(strings, integerList);
這裡可以看出泛型方法跟類型通配符的功能有點類似,其實在大部分情況下我們可以用泛型方法代替類型通配符。
泛型方法允許類型形參被用來表示方法的一個或者多個參數之間的依賴關係,或者說與返回值之間的關係,如果沒有這種關係,我們就不使用泛型方法。
六、擦除與轉換
當把一個具有泛型信息的對象賦給一個沒有泛型信息的變數時,所有的類型信息就都丟掉了,比如List<String>類型被轉換成List,則對該List的類型檢查也變成了Object。
public class WebResult<T extends Number> { //使用T類型形參定義實例變數 private T data; public WebResult() { } //使用T類型形參構造對象 public WebResult(T data) { this.data = data; } public void setData(T data) { this.data = data; } public T getData() { return this.data; } public static void main(String[] args) { WebResult<Integer> webResult1 = new WebResult<>(10); System.out.println(webResult1.getData()); WebResult<Integer> a = new WebResult<>(20); WebResult b = a; //已經擦除了泛型,只能按最高類型Object //Integer bData = b.getData(); Object object=b.getData(); } }
原本的泛型類上限是Number,而當把a賦給擦除泛型的b對象時,編譯器失去了推斷能力,只能把其當做Objec來處理。
而當一個List轉成泛型對象是java是允許的
List<Integer> integerList = new ArrayList<>(); List stringList = integerList; //允許直接將list對象轉換給 List<String> strings = stringList; //直接獲取數據會出現錯誤,因為轉換不成功 System.out.println(stringList.get(0));