Java 8 新特性之泛型的類型推導

来源:http://www.cnblogs.com/heimianshusheng/archive/2016/08/13/5766573.html
-Advertisement-
Play Games

1. 泛型究竟是什麼? 在討論類型推導(type inference)之前,必須回顧一下什麼是泛型(Generic).泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。通俗點將就是“類型的變數”。這種類型變數可以用在類、介面和方法的創建中。理解J ...


1. 泛型究竟是什麼?

  在討論類型推導(type inference)之前,必須回顧一下什麼是泛型(Generic).泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。通俗點將就是“類型的變數”。這種類型變數可以用在類、介面和方法的創建中。理解Java泛型最簡單的方法是把它看成一種便捷語法,能節省你某些Java類型轉換(casting)上的操作:

List<Apple> box = new ArrayList<Apple>();box.add(new Apple());
Apple apple =box.get(0);

上面的代碼自身已表達的很清楚:box是一個裝有Apple對象的List。get方法返回一個Apple對象實例,這個過程不需要進行類型轉換。沒有泛型,上面的代碼需要寫成這樣:

Apple apple = (Apple)box.get(0);

當然,泛型絕不像我在這裡描述的這麼簡單,但這不是我們今天的主角,對於泛型還不是很明白的同學需要補課了~當然,最好的參考資料還是官方文檔。

2. 泛型帶來的問題(Java 7之前)

泛型的最大優點是提供了程式的類型安全同時可以向後相容,但也有讓開發者不爽的地方,就是每次定義時都要寫明泛型的類型,這樣顯示指定不僅感覺有些冗長,最主要是很多程式員不熟悉泛型,因此很多時候不能夠給出正確的類型參數,現在通過編譯器自動推斷泛型的參數類型,能夠減少這樣的情況,並提高代碼可讀性。

3. Java 7中對於泛型的類型推導方面的改進

在Java 7以前的版本中使用泛型類型,需要在聲明並賦值的時候,兩側都加上泛型類型。比方說這樣:

Map<String,Integer> map = new HashMap<String,Integer>(); 

很多人當初肯定和我一樣,對此感到很不解:我在變數聲明中不是已經聲明瞭參數類型了嗎?為什麼在對象初始化的時候還要顯示的寫出來?這也是泛型在一開始出現的時候受到很多人吐槽的地方。不過,讓人欣慰的是,java在進步的同時,那些設計者們也在不斷的改進java的編譯器,讓它變的更加智能與人性化。這裡,就是我們今天的主角:類型推倒...額...不是推倒,是類型推導,即type inference,這哥們兒的出現,再寫上面這樣的代碼的時候,可以很開心地省略掉對象實例化時的參數類型,也就變成了這個樣子:

Map<String,Integer> map = new HashMap<>();

在這條語句中,編譯器會根據變數聲明時的泛型類型自動推斷出實例化HashMap時的泛型類型。再次提醒一定要註意new HashMap後面的“<>”,只有加上這個“<>”才表示是自動類型推斷,否則就是非泛型類型的HashMap,並且在使用編譯器編譯源代碼時會給出一個警告提示(unchecked conversion warning)。這一對尖括弧"<>"官方文檔中叫做"diamond"。

但是,這時候的類型推導做的並不完全(甚至算是一個半成品),因為在Java SE 7中創建泛型實例時的類型推斷是有限制的:只有構造器的參數化類型在上下文中被顯著的聲明瞭,才可以使用類型推斷,否則不行。例如:下麵的例子在java 7無法正確編譯(但現在在java8裡面可以編譯,因為根據方法參數來自動推斷泛型的類型):

List<String> list = new ArrayList<>();
list.add("A");// 由於addAll期望獲得Collection<? extends String>類型的參數,因此下麵的語句無法通過
list.addAll(new ArrayList<>());

4. 在Java8中的再進化

在最新的java官方文檔之中,我們可以看到對於類型推導的定義:

Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.

簡言之,類型推導也就是指編譯器能夠根據你調用的方法和相應的聲明來確定需要的參數類型的能力。並且官方文檔中還給出了一個例子加以詮釋:

static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());

在這裡,編譯器能夠推導出傳入pick方法中的第二個參數的類型是Serializable的。

在之前的java版本當中,上面的例子要能夠通過編譯的話需要這要寫:

Serializable s = this.<Serializable>pick("d", new ArrayList<String>());

這樣寫的詳細原因可以在Bruce Eckel的java編程思想(第四版)的泛型一章看得到,當然這本書是基於java6的,這個版本還沒有類型推導這個概念。看到這裡,很多人已經明顯能看得出來最新版本中類型推導的強力之處了。已經不僅僅局限於泛型類的聲明與實例化過程了,而是延伸到了具有泛型參數的方法當中了。

4.1 類型推導和泛型方法(Type Inference and Generic Methods)

關於新版本中的類型推導和泛型方法,文檔中還給了一個稍微複雜一點的例子,我在這裡貼出來,原理和上面的Serializable例子都是一樣就不再贅述,想鞏固的可以再看一下:

public class BoxDemo {

  public static <U> void addBox(U u, 
      java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
  }

  public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
    int counter = 0;
    for (Box<U> box: boxes) {
      U boxContents = box.get();
      System.out.println("Box #" + counter + " contains [" +
             boxContents.toString() + "]");
      counter++;
    }
  }

  public static void main(String[] args) {
    java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
      new java.util.ArrayList<>();
    BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
    BoxDemo.outputBoxes(listOfIntegerBoxes);
  }
}

上面這段代碼輸出為:

Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]

提一下,泛型方法addBox重點就在於在新java版本中你不需要再在方法調用中進行顯示的類型說明,像這樣:

BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);

編譯器能夠從傳入addBox中的參數自動推斷出參數類型是Integer.

4.2 類型推導與泛型類和非泛型類的泛型構造器(Type Inference and Generic Constructors of Generic and Non-Generic Classes)

額...這個也許英語的更好斷句一點:Type Inference and Generic Constructors of Generic and Non-Generic Classes

其實,泛型構造器並不是泛型類的專利品,非泛型類也完全可以有自己的泛型構造器,看一下這個例子:

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

假如對 MyClass類做出下麵這樣的實例化:

new MyClass<Integer>("")

OK,這裡我們顯示地指出了MyClass的泛參類型X是Integer,而對於構造器,編譯器根據傳入的String對象("")推導出形式參數T是String,這個在java7版本之中已經實現了,在Java8中有了什麼改進呢?在Java8之後,對於這種具有泛型構造器的泛型類的實例化我們可以這麼寫:

MyClass<Integer> myObject = new MyClass<>("");

對,還是這一對尖括弧(<>),江湖人稱diamond,這樣我們的編譯器就能夠自動推導出形式參數X是Integer,T是String了。這個其實和我們一開始Map<String,String>的例子很像,只是多了個構造器的泛型化。

需要註意的是:類型推導只能根據調用的參數類型、目標類型(這個馬上會講到)和返回類型(如果有返回的話)進行推導,而不能根據程式後面的一些需求來進行推導。

4.3 目標類型(Target Type)

前文已經提到過,編譯器能夠根據目標類型進行類型推導。一個表達式的目標類型指的是一種編譯器根據表達式出現的位置而需要的正確的數據類型。比如這個例子:

static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList();

在這裡,List<String>就是目標類型,因為這裡需要的是List<String>,而Collections.emptyList()返回的是List<T>,所以這裡編譯器就推斷T一定是String。這個在Java 7 和 8 中都OK。但是在java 7 中,在下麵這種情況中就不能正常編譯了:

void processStringList(List<String> stringList) {
    // process stringList
}

processStringList(Collections.emptyList());

這個時候,java7就會給出這種錯誤提示:

//List<Object> cannot be converted to List<String>

原因:Collections.emptyList()  返回的是List<T> ,這裡的T需要一個具體類型,但是因為不能從方法聲明中推斷出所需的是String,所以編譯器就給T了一個Object的值,很明顯,List<Object>不能轉型到List<String>.所以在java7版本中你需要這樣調用這個方法:

processStringList(Collections.<String>emptyList());

但是,在java8中,由於目標類型概念的引入,這裡,很明顯編譯器需要的是List<String>(也就是這裡的Target Type),所以編譯器推斷返回的List<T>中的T一定是String,所以processStringList(Collections.emptyList());這種描述是OK的。

 

好了,以上就是關於java中類型推導的一些跟人見解,總結來說,越來越完善的類型推導就是完成了一些本來就感覺很理所當然的類型轉換工作,只是這些工作滿滿地全交給了編譯器去自動推導而不是讓開發者顯示地去指定。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ELF文件格式是一個開發標準,各種UNIX系統的可執行文件都採用ELF格式,它有三種不同的類型: 可重定位的目標文件 可執行文件 共用庫 現在分析一下上一篇文章中經過彙編之後生成的目標文件max.o和鏈接之後生成的可執行文件max的格式,從而理解彙編、鏈接和載入執行的過程。 一、目標文件 ELF文件 ...
  • 一、簡單的彙編程式 以下麵這段簡單的彙編代碼為例 (註意是globl不是global;movl(MOVL)不是mov1(MOV一)) 將這段程式保存為demo.s,然後用彙編器as把彙編程式中的助記符翻譯成機器指令(彙編指令與機器指令是對應的)生成目標文件demo.o。然後用鏈接器ld把目標文件de ...
  • 對象,常見數據類型與序列的內部功能,collections模塊 ...
  • Hibernate的雙向多對多關聯有兩種配置方法:那我們就來看看兩種方案是如何配置的。 一、創建以各自類為類型的集合來關聯 1.首先我們要在兩個實體類(雇員<Emploee>、工程<Project>)中各自給對方添加一個對方的集合 1.1 雇員實體類 1.2 工程實體類 2.有了實體類之後呢,我們就 ...
  • 1.同步動態掃描 多個數位管的顯示採用的是同步動態掃描方法,同步動態掃描指的是:行信號和列信號同步掃描,是一種並行操作。 2.數位管驅動電路實現思路 如果要求數位管顯示我們想要的數字,首先需要寫一個數據接收模塊,這個模塊接收數據之後需要做什麼樣的處理呢?這時候我們會想到兩個數位管,其中一個顯示十位數 ...
  • 1、打開Eclipse,在.出現時進行代碼提示換成任意字母+.出現時的代碼提示了(.abcdefghijklmnopqrstuvwxyz): ...
  • 最近在使用QTableWidget時,因為結果數量比較多而且又有單元格控制項,為了改善效率要做分頁處理。在網上找了一番,最後在http://www.cppblog.com/biao/archive/2011/10/30/159350.html 基礎上修改而來,首先感謝原作者。 改後的版本:http:/ ...
  • ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...