Java語言進階篇:泛型原理與Android網絡應用

引入

看一段常見的代碼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類型

嵌套涉及的一些知識

嵌套後如何進行類型擦除?又會產生什麼新的問題呢?

類型擦除的規則:

  • 擦除後變爲Obecjt
  • 擦除後變爲A
  • <? super A>擦除後變爲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)相互之間沒有繼承關係。

泛型如何變化

令f(A)=ArrayList,那麼f(⋅)時逆變、協變仍是不變的呢?若是是逆變,則ArrayList是ArrayList的父類型;若是是協變,則ArrayList是ArrayList的子類型;若是是不變,兩者沒有相互繼承關係。前面咱們用ArrayList實例化list的對象錯誤,則說明泛型是不變的。

因此這個時候咱們須要經過嵌套來實現,即

<? extends>實現了泛型的協變,好比:List<? extends Number> list = new ArrayList();

<? super>實現了泛型的逆變,好比:List<? super Number> list = new ArrayList();

這兩個是徹底沒有問題的,再回到咱們的栗子,咱們只需修改第二種super來實現添加,首先若是咱們使用extends,如上所示,咱們能夠添加Number的子類,但具體添加哪種?沒法識別,而後咱們使用super,如上所示,咱們添加Number的父類,咱們能夠直接使用Object,就能直接添加了。

這個例子結合咱們使用通配符的例子,總結一下就是

要從泛型類取數據時,用extends

要往泛型類寫數據時,用super

Android上泛型的應用

1.泛型類:網絡請求返回的結果類封裝、適配器的基類封裝等

2.泛型方法:網絡請求的json轉存對象的方法等

3.泛型接口:這個和方法相似,用的少

最後

若是你看到了這裏,以爲文章寫得不錯就給個小讚唄?若是你以爲那裏值得改進的,請給我留言。必定會認真查詢,修正不足。謝謝。

最後針對Android程序員,小編這邊給你們整理了一些資料,其中分享內容包括不限於**【高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術】**但願能幫助到你們,也節省你們在網上搜索資料的時間來學習,也能夠分享動態給身邊好友一塊兒學習!

爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!

轉發分享+關注,歡迎加入Android開發交流羣(820198451)獲取更多資料

Android架構師之路很漫長,一塊兒共勉吧!

相關文章
相關標籤/搜索