1. 程式人生 > >由Kotlin 中關鍵字out和in和Java中的泛型的比較學習

由Kotlin 中關鍵字out和in和Java中的泛型的比較學習

由Kotlin 中關鍵字out和in聯想到Java中的泛型

最近在學習kotlin語法,發現了kotlin中的關鍵字out和in,感覺甚是新穎,就細細琢磨了一下,發現這兩個關鍵字和Java中的泛型邊界有著千絲萬縷的聯絡。那麼接下來我們就先談談Java中的泛型,順便複習一下泛型知識,在逐步談談kotlin中的out和in關鍵字。

  • Java中的泛型
  • Java中的泛型擦除
  • Java中的泛型邊界
  • kotlin中關鍵字out和in

Java中的泛型

1、泛型的由來

Java是單繼承,這會使程式受限太多。如果方法的引數是一個介面,而不是一個類,這種限制就放鬆了許多。因此,介面允許我們快捷實現類繼承,也使我們有機會建立一個新類來做到這一點。在Java 5中出現了“泛型”概念。泛型實現了引數化型別的概念,使程式碼可以應用於多種型別。“泛型”顧名思義是:適用於許多許多的型別。泛型在程式語言總出現時,其最初的目的希望類或方法具備最廣泛的表達能力。但Java泛型對比與其他語言(c++)侷限就非常大,這不是本篇的重點,可以自行查詢。

2、泛型分類

泛型可以分為:簡單泛型、泛型介面、泛型方法
2.1 簡單泛型
就是為了創造容器類,就是存放使用的物件的地方。陣列也是如此,不過與簡單的陣列相比,容器類更加靈活,具備更多不同的功能。事實上,所有的程式,在執行時都要求你持有一大堆物件,所以容器類算得上是最具有重用功能的類庫之一。

public class Holder<T>{
    private T t;
    public Holder(T t){this.t = t;}
    public T get(){return t;}
}

2.2 泛型介面
泛型可以應用於介面,介面使用泛型與類使用泛型沒有區別,例如:

public interface Generator<T> { T next();}

2.3 泛型方法
在類中可以包含引數化方法,而這個方法所在的類可以是泛型類,也可以不是泛型類。也就是說,是否擁有泛型方法,與其所在的類是否是泛型沒有關係。泛型方法使得該方法能夠獨立於類而產生變化。一個基本原則是:無論何時,只要你能夠做到,你就應該儘量使用泛型方法。也就是說,如果泛型方法可以取代將整個類泛型化,那麼就應該只使用泛型方法,因為它可以使事情更清楚明白。

public class GenericMethods{
    public <T> void f(T x){
        System.out.println(x.getClass().getName());
    }
}

Java中的泛型擦除

Java中的泛型是不型變的,這就意味著 List<String> 並不是List<Object>的子型別。為什麼會這樣?如果 List 不是不型變的,它就沒比Java陣列好到哪去,如下程式碼會通過編譯然後導致執行時異常:

List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!!即將來臨的問題的原因就在這裡。Java 禁止這樣!
objs.add(1); // 這裡我們把一個整數放入一個字串列表
String s = strs.get(0); // !!! ClassCastException:無法將整數轉換為字串

因此,殘酷的現實是:
在泛型程式碼內部,無法獲得任何有關泛型引數型別的資訊。
Java泛型是使用擦除來實現的,意味著當你在使用泛型時,任何具體的型別資訊都被擦除了,你唯一知道的就是你在使用一個物件。因此List<String>List<Object>在執行上事實上是相同的型別。這兩種形式都被擦除成它們的“原生”型別,即List

Java中的泛型邊界

即使擦除在方法或類內部移除了有關實際型別的資訊,編譯器仍舊可以確保在方法或類中使用的型別的內部一致性。因為擦除在方法中移除了型別資訊,所以在執行時的問題就是邊界:即物件進入和離開方法的地點。這些正是編譯器在編譯期執行型別檢查並插入轉型程式碼的地點。
正如我們看到的,擦除丟失了在泛型程式碼中執行某些操作的能力。任何在執行時需要知道確切型別資訊的操作都將無法工作。那麼,為彌補這些,就出現了邊界技術。邊界使得你可以在用於泛型的引數型別上設定限制條件。儘管使得你可以強制規定泛型可以應用的型別,但是其潛在的一個更重要的效果是你可以按照自己的邊界型別來呼叫方法。
因為擦除移除了型別資訊,所以,可以用無界泛型引數呼叫的方法只是那些可以用Object呼叫的方法。但是,如果能夠將這個引數限制為某個型別子集,那麼你就可以用這些型別子集來呼叫方法。為了執行這種限制,Java泛型重用了extends關鍵字。對你來說有一點很重要,即來理解extends 關鍵字在泛型邊界上下文和普通情況下所具有的意義是完全不同的。
例如,考慮Collection 介面中addAll() 方法。該方法的簽名應該是什麼?估計大多數人會認為:

interface Collection<E> …… {
  void addAll(Collection<E> items);
}

但隨後,我們將無法做到以下簡單的事情(這是完全安全):

void copyAll(Collection<Object> to, Collection<String> from) {
  to.addAll(from); // !!!對於這種簡單宣告的 addAll 將不能編譯:
                   //       Collection<String> 不是 Collection<Object> 的子型別
}

這就是為什麼 addAll() 的實際簽名是以下這樣:

interface Collection<E> …… {
  void addAll(Collection<? extends E> items);
}

萬用字元型別引數? extends E 表示此方法接受E 或者E的一些子型別 物件的集合,而不只是E 自身。這意味著我們可以安全地從其中(該集合中的元素是E的子型別的例項)讀取E,但是不能寫入,因為我們不知道什麼物件符合那個未知的E 的子型別。反過來,該限制可以讓Collection<String> 表示Collection<? extends Object> 的子型別。簡而言之,帶 extends限定(上界)的萬用字元型別使得型別是協變的(covariant)

理解為什麼這個技巧能夠工作的關鍵相當簡單:如果只能從集合中獲取專案,那麼使用 String 集合,並且從其中讀取Object 也沒問題。反過來,如果只能向集合中寫入 ,就可以用Object 集合並向其中放入String:在Java中有List<? super String>List<Object> 的一個超類。

後者稱為逆變性(contravariance) ,並且對於 List<? super String> 你只能呼叫接受String作為引數的方法(例如,你可以呼叫add(String) 或者 set(int,String)),當然如果呼叫函式返回List<T> 中的T ,你得到的並非一個String 而是一個Object

kotlin中關鍵字out和in

那麼在Java中,在 Source 型別的變數中儲存 Source 例項的引用是極為安全的——沒有消費者-方法可以呼叫。但是 Java 並不知道這一點,並且仍然禁止這樣操作:

void demo(Source<String> strs) {
  Source<Object> objects = strs; // !!!在 Java 中不允許
  // ……
}

為了修正這一點,我們必須宣告物件的型別為 Source<? extends Object>,這是毫無意義的,因為我們可以像以前一樣在該物件上呼叫所有相同的方法,所以更復雜的型別並沒有帶來價值。但編譯器並不知道。

所以在Kotlin中,有一種方法向編譯器解釋這種情況。這稱為宣告處型變:我們可以標註Source引數型別T 來確保它僅從Source<T> 成員中返回(只讀取,相當於Java中? extends T)。為此,kotlin提供out 修飾符。

//kotlin
interface Source<out T> {
    fun nextT(): T
}
fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 這個沒問題,因為 T 是一個 out-引數
    // ……
}

一般原則是:當一個類C 的型別引數T 被宣告為out 時,它就只能出現在C 的成員的輸出位置,但回報是C<Base> 可以安全的作為C<Derived> 的超類。
另外除了 out,Kotlin 又補充了一個型變註釋:in。它使得一個型別引數逆變:只可以被寫入而不可以被讀取(相當於Java中 ? super T)。逆變型別的一個很好的例子是 Comparable

interface Comparable<in T> {
    operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 擁有型別 Double,它是 Number 的子型別
    // 因此,我們可以將 x 賦給型別為 Comparable <Double> 的變數
    val y: Comparable<Double> = x // OK!
}

簡單的總結,方便以後查閱。

相關推薦

Kotlin 關鍵字outinJava比較學習

由Kotlin 中關鍵字out和in聯想到Java中的泛型 最近在學習kotlin語法,發現了kotlin中的關鍵字out和in,感覺甚是新穎,就細細琢磨了一下,發現這兩個關鍵字和Java中的泛型邊界有著千絲萬縷的聯絡。那麼接下來我們就先談談Java中的泛型,

sqlexistsnot exists用法 容易in not in 混淆

看專案程式碼時遇到,記錄下 select * from A where id in(select id from B) 以上查詢使用了in語句,in()只執行一次,它查出B表中的所有id欄位並快取起來. 然後,檢查A表的id是否與B表中的id相等, 如果相等則將A表的記

C語言關鍵字作用(conststaticextern)

儲存型別關鍵字(4個): auto: 宣告自動變數,現在一般不用(auto int a;和int a;一樣) register: 宣告暫存器變數 static: 宣告靜態變數。該變數宣告時系統所分配的

C語言關鍵字static、externauto的作用總結

1、首先說一下auto自動儲存型別,一般我們很少在程式中顯示申明變數為auto型別。因為程式碼塊中的變數預設情況下就是這種型別,這種型別的變數存放於堆疊中,也就是說只有程式執行這些程式碼塊(一對{}之間的語句)時這種自動變數才會被建立,程式碼塊執行結束後自動變數便被釋放。

Spring裡的aop實現方式原始碼分析 java代理,靜態代理,動態代理以及spring aop代理方式,實現原理統一彙總

使用"橫切"技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如許可權認證、日誌、事務。AOP的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。

Mybatis(四):MyBatis核心元件介紹原理解析原始碼解讀 java代理,靜態代理,動態代理以及spring aop代理方式,實現原理統一彙總

Mybatis核心成員 Configuration        MyBatis所有的配置資訊都儲存在Configuration物件之中,配置檔案中的大部分配置都會儲存到該類中 SqlSession         &

Java的instanceof運算子的執行原理使用參考 Java的instanceof運算子的執行原理使用參考

原 Java中的instanceof運算子的執行原理和使用參考 2017年11月13日 09:28:34 boker_han 閱讀數:894

python資料結構容器(list、dict、tuple、set)C++、JAVA的匯出資料型別, 陣列

list(列表):語法:列表形如 [1, 2, 3, 4] [‘小明’,‘小紅’,] ,用中括號括住,裡面是字串、布林,每一項逗號分開。 建立 宣告變數時 中括號、項,建立一個非空的列表。 num_list = [1,2,3,4] 建立一個空列表,之後再修改

redisredis在java的使用

Redis 簡介 REmote DIctionary Server(Redis) 是一個由Salvatore Sanfilippo寫的key-value儲存系統。Redis是一個開源的使用ANSI C語言編寫、遵守BSD協議、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資

二、C++迭代器的兩種實現方式 (Range forC#、Java的foreach)

一、迭代器概述   這個標題其實有點“標題黨”的含義,因為C++在標準庫中的實現迭代器的方式只有一種,也就是為類定義begin()和end()函式,C++11增加了range for語句,可以用來遍歷迭代器中的元素。實現迭代器的第二種方式,就是用C++模擬C#和Java中的

java基本型別的讀入方式關閉方式 javanext()nextLine()

1、一般讀入形式和關閉形式 import java.util.*; Scanner scan=new Scanner(System.in); float l=scan.nextFloat(); double l1=scan.nextDouble();//這裡不能是double l,因為在一

Oracle 關鍵字 ‘exists‘ 與 ‘in’ 詳解

IN(list) 和 NOT IN(list) 等於列表其一和不等於列表其一,IN(list) 還常用於判斷一個子查詢的結果集; EXISTS(): 用在where中作為過濾條件,其後跟一個子查詢,只

Java物件,如何定義Java的類,如何使用Java的物件,變數

1.物件的概念 :萬物皆物件,客觀存在的事物皆為物件 2.什麼是面向物件:人關注一個物件,實際上是關注該物件的事務資訊 3.類:類是模子,確定物件將會擁有的特徵(屬性)和行為(方法)        

使用DelayQueue FutureTask 實現java的快取

使用DelayQueue、ConcurrentHashMap、FutureTask實現的快取工具類。 DelayQueue 簡介 DelayQueue是一個支援延時獲取元素的無界阻塞佇列。DelayQueue內部佇列使用PriorityQueue來實現。

javasuperextends的區別

環境 java:1.7+ 前言 主要講的是<? super T> 和 <? extends T>的區別! 這個是我在打算封裝一段通用程式碼時,發現經常用的<? extends T>,網上搜索時,發現其總是要和<? sup

使用svn的過程check out的文件路徑的文件圖標全都加上了“藍色問號”的解決方案

問號 keyword bat 過程 解決 out 使用 方案 word (1)你在對同一層目錄下創建一個記事本文件,然後把下面這句話復制進去 for /r . %%a in (.) do @if exist "%%a\.svn" rd /s /q "%%a\.svn" (

Java約束限制

實例 == -h 不同 java異常 轉換 component 參數 測試 不能用基本類型實例化類型參數 不能用類型參數代替基本類型:例如,沒有Pair<double>,只有Pair<Double>,其原因是類型擦除。擦除之後,Pair類含有O

Action<T>Func<T>委託,委託,,匿名函式,Lambda表示式的綜合使用

前言 在上篇文章C#中委託(delegate)和多播委託的理解中已經基本瞭解了委託的用法。 由於委託封裝的方法我們不一定知道其傳入的引數和返回值,例如之前說的的排序方法—————在物件的排序演算法中,需要對物件進行比較,而不同物件比較的方法不同,所以比較兩個物件的方法的引用可以作為引數傳

零基礎學java的最佳學習方法最全java知識大綱(含100G學習資料)

相信有很多學習java的道友,在這裡我給大家說說我的群哦,分享一套系統的java教程哦,872603705,絕對的 java乾貨,首先你是學習java的,不管是大神還是小白,我們一同從入門到精通吧! 本文作者:【java進階架構師】 做一個最有態度的java自媒體人。

#乾貨分享:Java擦除執行時資訊獲取

Java 的泛型擦除 程式設計師界有句流行的話,叫 talk is cheap, show me the code,所以話不多說,看程式碼。 如果有想學習java的程式設計師,可來我們的java學習扣qun:79979,2590免費送java的視訊教程噢!我整理了一份適合18年學習的java