看一段常見的代碼java
記得之前咱們使用的時候都須要強轉類型,如今這裏竟然提示這是沒必要要的 why?發生了什麼?何時發生的?程序員
咱們打開這個方法,以下編程
@SuppressWarnings("TypeParameterUnusedInFormals")
@Override
public <T extends View> T findViewById(@IdRes int id) {
return getDelegate().findViewById(id);
}
複製代碼
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
複製代碼
以上兩段代碼分別來自於API25,即對應Android8.0源碼中的v7包的AppCompatActivity、Activityjson
咱們再看兩段代碼,以下小程序
@Override
public View findViewById(@IdRes int id) {
return getDelegate().findViewById(id);
}
複製代碼
@Nullable
public View findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
複製代碼
以上兩段代碼分別來自於API24,即對應Android7.0源碼中的v7包的AppCompatActivity、Activity微信小程序
Ps:你能夠試着把module的依賴的SDK版本和AppCompatActivity的版本下降到24及如下,就會出現須要以下狀況安全
對比兩個版本的代碼,出現了重點,就是< T extends View > T 代替了原來的View。這種代碼就是使用泛型的栗子。性能優化
PS:末尾有驚喜bash
做用微信
1.代碼寫起來比較方便,直接fbi一鼓作氣(如上慄),不用擔憂控件的類型轉換問題。
再舉個栗子,
List list = new ArrayList();
list.add(123);
list.add("123");
int i = (Integer) list.get(1);
System.out.println(i);
複製代碼
可在main方法裏執行以上代碼,編譯器並不會報錯,但執行的時候會出現類的強制轉換錯誤,這裏的建立了ArrayList的實例,即默認爲Object,因此不管是123仍是「123」都被當初object添加,可是,取出的時候會被自動強轉成添加進去的類型,即list.get(1)取出的是String類型,而String類型是不能強轉成Integer類型的。
若是咱們使用泛型呢
2.代碼的運行更加安全,能有效的避免運行時才知道類型轉換錯誤,能夠提早發現錯誤,進行修正。
定義
泛型,即參數化類型,是在JDK1.5以後纔開始引入的。所謂參數化類型,是指所操做的數據類型在定義是被指定爲一個參數,而後在使用時傳入具體的類型。這種參數類型能夠用在類、接口和方法的建立中,分別稱爲泛型類、泛型接口和泛型方法。
使用
上述例子就已有使用,咱們能夠發現List是個泛型接口,即List 而後看到它的get方法,返回值即傳入的類型,E只是個形參,Integer纔是實參,因此當咱們把泛型設置成Integer的時候,此時的list數據集合的類型被肯定,get出來的即爲Integer,因此再次添加String類型即報錯。
下面列出每一個用例的標準類型參數:
• E:元素
• K:鍵
• N:數字
• T:類型
• V:值
• S、U、V 等:多參數狀況中的第 二、三、4 個類型
使用的注意事項:
① 全部泛型聲明都有一個類型參數聲明部分(由尖括號分隔),泛型方法的該類型參數聲明部分在方法返回類型以前,泛型類和接口在直接在名稱後面添加了類型參數聲明部分(例如)
② 每個類型參數聲明部分包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數,也被稱爲一個類型變量,是用於指定一個泛型類型名稱的標識符。
③ 類型參數能被用來聲明返回值類型,而且能做爲泛型方法獲得的實際參數類型的佔位符。 泛型方法體的聲明和其餘方法同樣。注意類型參數只能表明引用型類型,不能是原始類型(像int,double,char的等)。
④ 泛型的參數類型可使用extends語句,例如。習慣上稱爲「有界類型」。 這裏的限定使用關鍵字extends,後面能夠是類也能夠是接口。但這裏的extends已經不是繼承的含義了,應該理解爲T類型是實現XX接口的類型,或者T是繼承了XX類的類型。 <T extends SomeClass & interface1 & interface2 & interface3>
⑤ 泛型的參數類型還能夠是通配符類型。例如Class<?> classType = Class.forName("java.lang.String");
一、若是隻指定了<?>,而沒有extends,則默認是容許Object及其下的任何Java類了。也就是任意類。
二、通配符泛型不單能夠向上限制,如<? extends Collection>,還能夠向下限制,如<? super Double>,表示類型只能接受Double及其上層父類類型,如Number、Object類型的實例。
泛型類的使用
/泛型類的使用
ClassName<String, String> a = new ClassName<String, String>();
a.doSomething("hello world");
//**************************分割線***********************
//泛型類的定義
static class ClassName<T1, T2> { // 能夠任意多個類型變量
public void doSomething(T1 t1) {
System.out.println(t1);
}
}
複製代碼
泛型方法的使用
//泛型方法的使用
System.out.println(getMax("hello world","hello world !!!"));
//**********************分割線****************************
//泛型方法的定義
static <T extends Comparable<T>> T getMax(T t1, T t2) {
if (t1.compareTo(t2) > 1) {
return t1;
} else {
return t2;
}
}
複製代碼
泛型接口的使用
//泛型接口的使用
InterfaceName<String, String> b = new ConcreteName<String>();
b.doSomething("hello world !!!");
//*********************分割線****************************
//泛型接口的定義
interface InterfaceName<T1, T2> { // 能夠任意多個類型變量
public void doSomething(T1 t1);
}
static class ConcreteName<T2> implements InterfaceName<String, T2> {
public void doSomething(String t1) {
System.out.println(t1);
}
}
複製代碼
原理(重點)
Java中的泛型是經過類型擦除來實現的。所謂類型擦除,是指經過類型參數合併,將泛型類型實例關聯到同一份字節碼上。編譯器只爲泛型類型生成一份字節碼,並將其實例關聯到這份字節碼上。類型擦除的關鍵在於從泛型類型中清除類型參數的相關信息,而且再必要的時候添加類型檢查和類型轉換的方法。
舉個栗子
package upupup.fanxing;
import java.util.ArrayList;
/**
* @version: v1.0
* @description: 泛型原理的探究
* @package: upupup.fanxing
* @author: 小小黑
* @date :2018/12/28
*/
public class test01 {
public static void main(String[] args) {
/**
* 證實只生成了一個類,兩個實例共享
*/
// 聲明一個具體類型爲String的ArrayList
ArrayList<String> arrayList1 = new ArrayList<String>();
arrayList1.add("abc");
// 聲明一個具體類型爲Integer的ArrayList
ArrayList<Integer> arrayList2 = new ArrayList<Integer>();
arrayList2.add(123);
// 結果爲true
System.out.println(arrayList1.getClass() == arrayList2.getClass());
/**
* 證實了在編譯後,擦除了Integer這個泛型信息,只保留了原始類型
*/
ArrayList<Integer> arrayList3 = new ArrayList<Integer>();
arrayList3.add(1);
try {
arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
for (int i = 0; i < arrayList3.size(); i++) {
System.out.println(arrayList3.get(i)); // 輸出1,asd
}
// NoSuchMethodException:java.util.ArrayList.add(java.lang.Integer
arrayList3.getClass().getMethod("add", Integer.class).invoke(arrayList3, 2);
}catch (Exception e){
e.printStackTrace();
}
}
}
複製代碼
這裏暴露了一個問題,既然擦除了,那麼返回給咱們收到的應該是一個object對象,爲何咱們能直接獲得咱們須要的對象?不須要進行強轉?緣由是Java的泛型除了類型擦除以外,還會自動生成checkcast指令進行強制類型轉換
看個例子
public static void main(String[] args) {
List<Integer> a = new ArrayList<Integer>();
a.add(1);
Integer ai = a.get(0);
System.out.println(ai);
}
複製代碼
咱們來編譯一下,看他的字節碼,即.class文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package upupup.fanxing;
import java.util.ArrayList;
public class test02 {
public test02() {
}
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add(1);
Integer var2 = (Integer)var1.get(0);
System.out.println(var2);
}
}
複製代碼
咱們幹了啥?這和咱們直接強轉有區別嗎?泛型一樣須要強轉,並不會提升運行效率,可是會下降編程時的錯誤率,即咱們剛開始所說的泛型的好處。
舉個栗子
package upupup.fanxing;
import java.util.ArrayList;
import java.util.List;
/**
* @version: v1.0
* @description: 通配符泛型方法和嵌套
* @package: upupup.fanxing
* @author: 小小黑
* @date :2018/12/28
*/
public class test03 {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getData(name);
getData(age);
getData(number);
System.out.println("***************");
// getUperNumber(name);//1
getUperNumber(age);//2
getUperNumber(number);//3
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
}
複製代碼
經過栗子咱們很明顯知道,這個通配符是和泛型搭配使用的,由於咱們須要獲得不一樣類型的data,若是咱們不使用泛型取出的時候就須要逐個進行強轉,而使用?通配符就把這個問題解決了,但注意這並非通配符泛型方法,但咱們能夠修改一下
//通配符泛型方法的使用
System.out.println("?data :" +getData1(name).get(0));
//***********************分割線****************
//定義通配符泛型方法
public static List<?> getData1(List<?> data) {
return data;
}
複製代碼
這裏應該很容易就能明白,通配符即適配任一類型,表示未知(單一)的Object類型
嵌套涉及的一些知識
嵌套後如何進行類型擦除?又會產生什麼新的問題呢?
類型擦除的規則:
這個規則叫作保留上界,很容易想到這個,但咱們未給List設置泛型的時候,即默認爲Object就是這個道理
給個栗子看一哈
Number num = new Integer(1);
//type mismatch
ArrayList<Number> list = new ArrayList<Integer>();
List<? extends Number> list = new ArrayList<Number>();
list.add(new Integer(1)); //error
list.add(new Float(1.2f)); //error
複製代碼
爲何Number的對象能夠由Integer實例化,而ArrayList的對象卻不能由ArrayList實例化?list中的<? extends Number>聲明其元素是Number或Number的派生類,爲何不能add Integer和Float?
要弄明白上述問題,咱們先得了解一哈
1.里氏替換原則
2.逆變與協變用來描述類型轉換(type transformation)後的繼承關係
3.泛型經歷了類型擦除事後的繼承關係
里氏替換原則
全部引用基類(父類)的地方必須能透明地使用其子類的對象。
LSP包含如下四層含義:
子類徹底擁有父類的方法,且具體子類必須實現父類的抽象方法。
子類中能夠增長本身的方法。
當子類覆蓋或實現父類的方法時,方法的形參要比父類方法的更爲寬鬆。
當子類覆蓋或實現父類的方法時,方法的返回值要比父類更嚴格。
這裏能夠想到咱們使用的向上造型是否是就是這個原理
逆變和協變
逆變與協變用來描述類型轉換(type transformation)後的繼承關係,其定義:
若是A、B表示類型,f(⋅)表示類型轉換,≤表示繼承關係(好比,A≤B表示A是由B派生出來的子類);
f(⋅)是逆變(contravariant)的,當A≤B時有f(B)≤f(A)成立;
f(⋅)是協變(covariant)的,當A≤B時f(A)≤f(B)成立;
f(⋅)是不變(invariant)的,當A≤B時上述兩個式子均不成立,即f(A)與f(B)相互之間沒有繼承關係。
泛型如何變化
因此這個時候咱們須要經過嵌套來實現,即
<? extends>實現了泛型的協變,好比:List<? extends Number> list = new ArrayList();
<? super>實現了泛型的逆變,好比:List<? super Number> list = new ArrayList();
這兩個是徹底沒有問題的,再回到咱們的栗子,咱們只需修改第二種super來實現添加,首先若是咱們使用extends,如上所示,咱們能夠添加Number的子類,但具體添加哪種?沒法識別,而後咱們使用super,如上所示,咱們添加Number的父類,咱們能夠直接使用Object,就能直接添加了。
這個例子結合咱們使用通配符的例子,總結一下就是
要從泛型類取數據時,用extends
要往泛型類寫數據時,用super
1.泛型類:網絡請求返回的結果類封裝、適配器的基類封裝等
2.泛型方法:網絡請求的json轉存對象的方法等
3.泛型接口:這個和方法相似,用的少
若是你看到了這裏,以爲文章寫得不錯就給個小讚唄?若是你以爲那裏值得改進的,請給我留言。必定會認真查詢,修正不足。謝謝。
最後針對Android程序員,小編這邊給你們整理了一些資料,其中分享內容包括不限於**【高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術】**但願能幫助到你們,也節省你們在網上搜索資料的時間來學習,也能夠分享動態給身邊好友一塊兒學習!
爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!
轉發分享+關注,歡迎加入Android開發交流羣(820198451)獲取更多資料
Android架構師之路很漫長,一塊兒共勉吧!