Kotlin細(xì)節(jié)文章筆記整理更新進(jìn)度:
Kotlin系列 - 基礎(chǔ)類型結(jié)構(gòu)細(xì)節(jié)小結(jié)(一)
Kotlin系列 - 函數(shù)與類相關(guān)細(xì)節(jié)小結(jié)(二)
Kotlin系列 - 高階函數(shù)與標(biāo)準(zhǔn)庫(kù)中的常用函數(shù)(三)
前言
本篇文章從java
開始講泛型,后面再切換到kotlin
,重點(diǎn)java
的泛型掌握住,koltin
的泛型就會(huì)很快掌握。(可自行選取節(jié)段食用,碼字不易看完覺得還可以的季二,麻煩給贊,本人能力有限,有錯(cuò)誤或者有問(wèn)題在評(píng)論區(qū)留言灵汪,感激~~)
總結(jié)
- 虛擬機(jī)沒有泛型檀训,只有普通方法和類。
- 所有的類型參數(shù)都用它們的限定類型替換享言。
- 橋方法被合成用于保持多態(tài)峻凫。
- 為保持類型安全性,必要時(shí)插入強(qiáng)制類型轉(zhuǎn)換览露。
一荧琼、泛型基礎(chǔ)
1. 定義
泛型,也稱參數(shù)化類型差牛∶可以使代碼應(yīng)用多種類型。使用類型參數(shù)偏化,用尖括號(hào)括住脐恩,放在類名后面。在使用該類的時(shí)候用實(shí)際的類型替換該類型參數(shù)侦讨。
示例:
//這里的參數(shù)T可以自由命名
ClassA<T>{}
2. 存在的意義
如果在程序中只能使用具體的類型驶冒、具體的基本類型,或者自定義的類韵卤,在編寫多種類型的代碼骗污,這種限制會(huì)對(duì)代碼有很大的約束。我們需要一種在可在運(yùn)行是才確定類型的一種方法來(lái)實(shí)現(xiàn)代碼更加通用沈条。
示例:
public class PrintClass {
static public void printInt(Integer a, Integer b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
static public void printFloat(Float a, Float b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
static public void printDouble(Double a, Double b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
}
改成泛型函數(shù):
public class PrintClass1 {
static public <T> void printMultiply(T a, T b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
}
使用:
public static void main(String[] args) {
PrintClass.printDouble(10.0,10.0);
PrintClass.printInt(10,10);
PrintClass.printFloat(10f,10f);
PrintClass1.printMultiply(10.0,10.0);
PrintClass1.printMultiply(10,10);
PrintClass1.printMultiply(10f,10f);
PrintClass1.printMultiply("100","100");
}
-----------------------打印的Log---------------------------
參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=10參數(shù)b=10
參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=10參數(shù)b=10
參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=100參數(shù)b=100
通過(guò)上面的展示需忿,大家對(duì)泛型有個(gè)最基本的了解。
二拍鲤、java的泛型使用
1. 接口泛型
- 接口泛型定義:
interface Animal<T> {
void name();
void cry();
void mysteryData(T t);
}
- 接口泛型實(shí)現(xiàn)一
public class Cat implements Animal<String> {
@Override
public void name() {
System.out.println("貓");
}
@Override
public void cry() {
System.out.println("喵喵");
}
@Override
public void mysteryData(String s) {
System.out.println("假設(shè)它擁有一種數(shù)據(jù)類型"+ s.getClass().getName());
}
}
- 接口泛型實(shí)現(xiàn)二
public class Dog<T> implements Animal<T> {
@Override
public void name() {
System.out.println("狗");
}
@Override
public void cry() {
System.out.println("汪汪汪");
}
@Override
public void mysteryData(T t) {
System.out.println("假設(shè)它擁有一種數(shù)據(jù)類型"+t.getClass().getName());
}
}
使用:
public static void main(String[] args) {
Dog<Integer> dog = new Dog();
dog.name();
dog.cry();
dog.mysteryData(10);
Cat cat =new Cat();
dog.name();
cat.cry();
cat.mysteryData("String");
}
------------------------------log日志
狗
汪汪汪
假設(shè)它擁有一種數(shù)據(jù)類型java.lang.Integer
狗
喵喵
假設(shè)它擁有一種數(shù)據(jù)類型java.lang.String
2. 類泛型
上面接口泛型實(shí)現(xiàn)二
中就是用了類泛型的實(shí)現(xiàn)了贴谎。
3. 方法泛型
public class PrintClass1 {
static public <T> void printMultiply(T a, T b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
}
三、泛型類型變量的限定
有時(shí)候類季稳、方法需要對(duì)類型變量進(jìn)行約束擅这。
例如:增加一個(gè)類,用于專門打印各種動(dòng)物的叫聲
(這里泛型雖然可以替換為Animal
,但是這個(gè)演示案例我就使用泛型替代景鼠,這個(gè)不是重點(diǎn))
class AnimalCry{
public static <T> void cry(T a){
a.cry();
}
}
這里你單純這樣子寫仲翎,它不會(huì)有識(shí)別到
cry
這個(gè)方法,因?yàn)檫@個(gè)方法是Animal
接口的Cat
铛漓、Dog
等持有的方法溯香,所以我們要給它個(gè)類型變量的限定。
class AnimalCry{
public static <T extends Animal> void cry(T a){
a.cry();
}
}
--------------------調(diào)用
public static void main(String[] args) {
AnimalCry.cry(new Dog());
}
--------------------打印的Log
汪汪汪
格式
<T extends BoundingType> extends
后面可以跟接口
或者類名
浓恶。
T
:表示為綁定類型的子類型玫坛。
多個(gè)限定類型用&
進(jìn)行多個(gè)限定,例如
<T extends BoundingType & BoundingType1 & BoundingType2 & ... >
四包晰、泛型類型擦拭
java
的虛擬機(jī)中沒有泛型類型對(duì)象湿镀,所有的對(duì)象都是普通類炕吸,無(wú)論何時(shí)定義一個(gè)泛型類型,都會(huì)自動(dòng)體用一個(gè)對(duì)應(yīng)的原始類型勉痴。原始類型的名字就是擦拭后的類型參數(shù)的類型名赫模。如果沒有限定的類型變量則用Object
代替,如果有限定則以限定的為代替蒸矛。
public class PrintClass1 {
public static <T> void printMultiply(T a, T b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
}
--------------編譯后為
public class PrintClass1 {
public static void printMultiply(Object a, Object b) {
System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
}
}
class AnimalCry{
public static <T extends Animal> void cry(T a){
a.cry();
}
}
--------------編譯后為
class AnimalCry{
public static void cry(Animal a){
a.cry();
}
}
五瀑罗、約束與局限性
大多數(shù)的限制都是由類型擦拭帶來(lái)的。
-
不能用基本類型實(shí)例化類型參數(shù)雏掠。
沒有Pair<double>
只有Pair<Double>
,因?yàn)榉盒筒潦煤螅?code>Pair類含有Object
類型的域斩祭,而Object
不能存儲(chǔ)double
值。 -
運(yùn)行時(shí)類型查詢只適用于原始類型
虛擬機(jī)的對(duì)象總是一個(gè)非泛型類型磁玉。所以停忿,所有的類型查詢只產(chǎn)生原始類型。
Pair p = new Pair("str","str1");
Pair i = new Pair(10,20);
// illegal generic type for instanceof 無(wú)法使用instanceof關(guān)鍵字判斷泛型類的類型
if (p instanceof Pair<String,String>)
//比較結(jié)果都是true蚊伞,因?yàn)閮纱握{(diào)用都是返回Pair.class
if (p.getClass() == i.getClass()){}
- 不能創(chuàng)建參數(shù)化類型的數(shù)組
Pair<String,String>[] pairs = new Pair[10];
pairs[0] = new Pair(10,20);
//雖然能賦值但是會(huì)導(dǎo)致類型錯(cuò)誤席赂。
-
不能實(shí)例化類型變量或者實(shí)例化泛型數(shù)組
不能使用類似new T(...)
、new T[]
时迫、T.class
等表達(dá)式的變量 - 泛型類的靜態(tài)上下文類型變量無(wú)效
public class Singleton<T>{
private static T singleInstance; //ERROR
public static T getSingleInstance(){ //ERROR
if(singleInstance == null)
return singleInstance;
}
}
------------------------------------類型擦除后被替換成Object具體類
public class Singleton{
private static Object singleInstance;
public static Obejct getSingleInstance(){
if(singleInstance == null)
return singleInstance;
}
}
----------------------------------------------調(diào)用的時(shí)候
錯(cuò)誤颅停,返回Object類型
AType a = Singleton.getSingleInstance();
錯(cuò)誤,這種用法是不允許的掠拳,只能在調(diào)用方法或構(gòu)造方法時(shí)傳遞泛型參數(shù)
AType a = Singleton<AType>.getSingleInstance();
- 不能繼承Exception或者Throwable癞揉,不能拋出或捕獲泛型類的實(shí)例,但可以將泛型限定的異常拋出
public class Pair<T,Q> extends Exception{} // 報(bào)錯(cuò)溺欧,不能繼承Exception或者Throwable
public static <T extends Throwable> void doWork(Class<T> t){
try{
....
}catch(T e){ //報(bào)錯(cuò) 這里不能拋出泛型類型
}
}
//正確喊熟。
public static <T extends Throwable> void doWork(T t) throws T{
try{
....
}catch(Throwable t){
}
}
六、泛型類型繼承規(guī)則
- 兩個(gè)泛型參數(shù)是繼承關(guān)系姐刁,但是對(duì)應(yīng)的兩個(gè)泛型沒有一點(diǎn)關(guān)系芥牌!
interface Animal{}
public class Cat extends Animal{}
public class Dog extends Animal{}
public class Cry<T>{}
------------------------------------------------
Cry<Animal> 與 Cry<Cat> 不是繼承關(guān)系,也沒有什么關(guān)系聂使。
- 泛型類可以擴(kuò)展或?qū)崿F(xiàn)其他的泛型類
public class ArrayList<E> extends AbstractList<E>
七壁拉、通配符類型(重點(diǎn)!0匕小F怼)
小結(jié):
協(xié)變:
<? extends Class>
指定泛型類型的上限,只能讀取不能修改(修改是指對(duì)泛型集合添加元素,如果是remove(int index)
以及clear
當(dāng)然是可以的)逆變:
<? super Class>
指定泛型類型的下線,只能修改不能讀取,(不能讀取是指不能按照泛型類型讀取屎蜓,你如果按照Object
讀出來(lái)再?gòu)?qiáng)轉(zhuǎn)也可以)
<?>
相當(dāng)于< ? extends Object>
指定沒有限制的泛型類型
以上面的圖為例子:
1. 協(xié)變: <? extends Class>
指定泛型類型的上限
它的限定范圍為上限本身(上限可以是接口)以及所有直接跟間接的子類痘昌。
- 應(yīng)用場(chǎng)景
Animal animal = new Dog();// java的多態(tài)
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; //這里會(huì)報(bào)錯(cuò) incompatible types: List<Dog> cannot be converted to List<Animal>
上面的例子因?yàn)榘l(fā)生了類型擦拭,為了保證類型安全所以不允許這樣子賦值。
這個(gè)時(shí)候就可以使用協(xié)變的寫法<? extends Class>
限制參數(shù)類型的上界控汉,也就是泛型類型必須滿足這個(gè) extends
的限制條件笔诵。
List<? extends Animal> animals = new ArrayList<Animal>(); // 本身
List<? extends Animal> animals = new ArrayList<Cat>(); // 直接子類
List<? extends Animal> animals = new ArrayList<ShamoDog>(); // 間接子類
- 限制
只能夠向外提供數(shù)據(jù)被消費(fèi),類似
生產(chǎn)者
姑子。
List<? extends Animal> animals = new ArrayList<Dog>();
Animal animal= animals.get(0); //get 出來(lái)的是 Animal 類型的
animals.add(textView);//報(bào)錯(cuò),no suitable method found for add(TextView)
2. 逆變: <? super Class> 指定泛型類型的下限
它的限定范圍為下限本身(下限可以是接口)以及所有直接跟間接的父類测僵。
- 應(yīng)用場(chǎng)景
List<? super ShamoDog> shamoDogs= new ArrayList<shamoDogs>(); // 本身
List<? super ShamoDog> shamoDogs= new ArrayList<WailaiDog>();//直接接父類
List<? super ShamoDog> shamoDogs= new ArrayList<Animal>();//間接父類
- 限制
只能讀取到
Object
對(duì)象街佑,通常也只拿它來(lái)添加數(shù)據(jù),也就是消費(fèi)已有的List<? super ShamoDog>
捍靠,往里面添加Dog
沐旨,因此這種泛型類型聲明相對(duì)協(xié)變
可以稱為消費(fèi)者
List<? super ShamoDog> shamoDogs = new ArrayList<Animal>();
Object object = shamoDogs.get(0); // get 出來(lái)的是 Object 類型
Dog dog = ...
shamoDogs.add(dog); // add 操作是可以的
八、Kotlin的泛型
1. 格式
與java
泛型一樣的格式
interface AnimalKot<T> { } //接口泛型
class DogKot<Q> { } //類泛型
fun <T>TestLooperManager(t:T): Unit { }//方法泛型
2. 關(guān)鍵字out
榨婆、in
磁携、*
、where
-
out
:協(xié)變良风、與java
的上限通配符<? extends BoundType>
對(duì)應(yīng) -
in
:逆變谊迄,與java
的下限通配符<? super BoundType>
對(duì)應(yīng) -
*
: 與java
的<?>
,不過(guò)java
的是<? extends Object>
,kotlin
的是<out Any>
-
where
: 與java
的<T extends Animal & Person >
的&
符號(hào)對(duì)應(yīng)
//where的示例
//java中多個(gè)限定泛型定義
public class Cry <T extends Animal & Person>{ }
//kotlin的對(duì)應(yīng)寫法
class Cry<T> where T:Animal,T:Person{ }
重點(diǎn):
kotlin
提供另外一種附加功能烟央,在聲明類的時(shí)候统诺,給泛型類型加上in
關(guān)鍵字,表明泛型參數(shù)T
只會(huì)用來(lái)輸入疑俭,在使用的時(shí)候就不用額外加in
粮呢。對(duì)應(yīng)out
,則是表明泛型參數(shù)T
只會(huì)用來(lái)輸出,使用時(shí)不需要額外加out
钞艇。
例子
//koltin的 List
public interface List<out E> : Collection<E> {
}
var animalKot:List<Animal<String>> = ArrayList<Dog<String>>()// 不報(bào)錯(cuò)
var animalKot:List<out Animal<String>> = ArrayList<Dog<String>>()//寫了out,不報(bào)錯(cuò)
var animalKot:List<in Dog<String>> = ArrayList<Animal<String>>()//報(bào)錯(cuò)啄寡,不能寫in
//定義一個(gè) in泛型類型
class All<in T>{
fun p(t:T){
}
}
var all:All<Dog<String>> = All()// 不報(bào)錯(cuò)
var all:All<in Dog<String>> = All()//寫了in,不報(bào)錯(cuò)
var all:All<out Dog<String>> = All()//報(bào)錯(cuò),不能寫in
3. 關(guān)鍵字reified
關(guān)于java
泛型存在擦拭的情況下哩照,在上面五挺物、約束性與局限性
中第二點(diǎn)中提到的
運(yùn)行時(shí)類型查詢只適用于原始類型
<T> void println(Object obj) {
if (obj instanceof T) { // IDE提示錯(cuò)誤,illegal generic type for instanceof
}
}
kotlin也是如此---------------------------------------------------------
fun <T> println(any: Any) {
if (any is T) { // IDE提示錯(cuò)誤葡秒,Cannot check for instance of erased type: T
}
}
java
的解決方法:額外傳遞一個(gè) Class<T>
類型的參數(shù)姻乓,然后通過(guò) Class#isInstance
方法來(lái)檢查
<T> void println(Object obj, Class<T> type) {
if (type.isInstance(obj )) {
}
}
Kotlin
的解決方法,就是reified
關(guān)鍵字,但是 reified
只能在inline
表示的方法中使用眯牧,所以蹋岩,要使用inline
方法。
inline fun <reified T> println(any: Any) {
if (any is T) {
println(item)
}
}
inline
內(nèi)聯(lián)函數(shù)学少,當(dāng)方法在編譯時(shí)會(huì)拆解方法的調(diào)用為語(yǔ)句的調(diào)用剪个,進(jìn)而減少創(chuàng)建不必要的對(duì)象。在kotlin中一個(gè)inline
可以被具體化reified
版确,這意味著我們可以得到使用泛型類型的Class
扣囊。
- 項(xiàng)目中使用的自定義擴(kuò)展Gson
//定義`Gson`的擴(kuò)展函數(shù)
inline fun <reified T> Gson.fromJson(json:String):T = fromJson(json,T::class.java)
4. 注解 @UnsafeVariance
public interface List<out E> : Collection<E> {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
// Bulk Operations
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
....
上面是kotlin
的List
源碼乎折,可以看到定義類泛型限定的時(shí)候?yàn)?code><out E>為協(xié)變,只用來(lái)輸出侵歇,但是這里面的方法override fun contains(element: @UnsafeVariance E): Boolean
為了支持輸入骂澄,則使用@UnsafeVariance
避免IDE的檢查
5. Kotlin 泛型與 Java 泛型不一致的地方
Java
里的數(shù)組是支持協(xié)變的,而Kotlin
中的數(shù)組Array
不支持協(xié)變惕虑。
Kotlin
中數(shù)組是用Array
類來(lái)表示的坟冲,這個(gè)Array
類使用泛型就和集合類一樣,所以不支持協(xié)變溃蔫。Java
中的List
接口不支持協(xié)變健提,而Kotlin
中的List
接口支持協(xié)變。
在Kotlin
中伟叛,實(shí)際上MutableList
接口才相當(dāng)于Java
的List
私痹。Kotlin
中的List
接口實(shí)現(xiàn)了只讀操作,沒有寫操作统刮,所以不會(huì)有類型安全上的問(wèn)題紊遵,自然可以支持協(xié)變。
感謝
Kotlin 的泛型
Java核心技術(shù) 卷一