kotlin的in和out對(duì)應(yīng)的是
java中帶上界和下界的通配符蝗砾?號(hào)阐污。
【in】 等價(jià)于java中的【? super】
【out】 等價(jià)于java中的【 ? extends】
List<TextView> textviews = new ArrayList<Button>(); // ERROR
以上會(huì)報(bào)錯(cuò)雹顺!不能把子類(lèi)的List對(duì)象賦值給父類(lèi)List的引用避诽。
這是java泛型的一種性質(zhì):covariance(協(xié)變)呐籽。
covariance(協(xié)變)
源自數(shù)學(xué)中的一種概念盏浇。
子類(lèi)的泛型類(lèi)型也屬于泛型類(lèi)型的子類(lèi)变丧。
你聲明一個(gè)父類(lèi)的List,我給你賦值一個(gè)子類(lèi)的List對(duì)象也是可以的绢掰。
但是java的泛型不具備這種性質(zhì)痒蓬。
原因:java泛型在編譯時(shí)的類(lèi)型擦除童擎,由于有類(lèi)型擦除的存在,為了保證類(lèi)型安全攻晒,java給泛型設(shè)置了這種限制顾复。
TextView[] textviews = new Button[10];
在java中用數(shù)組中做類(lèi)似的事情是不會(huì)報(bào)錯(cuò)的。
這是因?yàn)閿?shù)組并沒(méi)有在編譯時(shí)擦除類(lèi)型鲁捏。
但是有這種需求怎么辦芯砸?
使用通配符【? extends】即可。
List<? extends TextView> textViews = new ArrayList<Button>();
這種寫(xiě)法雖然解除了賦值的限制给梅,但是會(huì)增加另一個(gè)限制:
在使用這個(gè)引用的時(shí)候假丧,不能調(diào)用它的參數(shù)包含①類(lèi)型參數(shù)的方法。
[注①:類(lèi)型參數(shù)就是<>里面的東西]
也不能給它的包含類(lèi)型參數(shù)的字段賦值动羽,除了空值包帚。
只能用它不能修改它。
使用場(chǎng)景:
當(dāng)你遇到【只想使用曹质,不需要修改】的情況婴噩,來(lái)讓本來(lái)不具covariance性質(zhì)的java支持covariance,以此來(lái)擴(kuò)大變量或參數(shù)的接收范圍羽德。
public void printTexts(List<? extends TextView> textViews){
for(TextView textView: textViews){
Log.e(TAG,textView.getText());
}
}
List<Button> buttons = ...
printTexts(buttons);
contravariant(逆變)
與之相對(duì)的還有【? super】几莽,它可以讓你把父類(lèi)的泛型類(lèi)型對(duì)象賦值給子類(lèi)的泛型類(lèi)型聲明,叫逆變或者反變宅静。
使用它是附加的限制和【? extends】相反章蚣,在使用這種變量的時(shí)候,不能調(diào)用返回值包含類(lèi)型參數(shù)的方法姨夹。
List<? super Button> textViews = new ArrayList<TextView>();
...
Button button = textViews.get(0); //報(bào)錯(cuò)O舜埂!磷账!
錯(cuò)誤:
public void addTextView(List<TextView> textViews){
TextView textView = ...;
textViews.add(textView);
}
...
List<View> views = new ArrayList<>();
addTextView(views);//報(bào)錯(cuò)G吐佟!逃糟!
正確:只希望有一個(gè)能承接這個(gè)TextView的List吼鱼。
public void addTextView(List<? super TextView> textViews){
TextView textView = ...;
textViews.add(textView);
}
...
List<View> views = new ArrayList<>();
addTextView(views);
PECS法則
Producer extends, Consumer super
Kotlin中的泛型
//java:
List<? extends TextView> textViews;
List<? super Button> textViews;
//kotlin
var textViews : List<out TextView>
var textViews : List<in TextView>
out:這個(gè)變量只用來(lái)輸出,不用來(lái)輸入绰咽,只能讀菇肃,不能寫(xiě)。
in:只能用來(lái)輸入取募,不能用來(lái)輸出琐谤,只能寫(xiě),不能讀玩敏。
kotlin的out和in不只可以用在變量和參數(shù)的聲明里斗忌,還可以直接用在泛型類(lèi)型聲明時(shí)的類(lèi)型參數(shù)上质礼。它表示我的這個(gè)類(lèi)型就只能用來(lái)輸出或只能用來(lái)輸入。什么意思织阳?說(shuō)白就是几苍,它的作者根據(jù)它的功能判斷出它所有的使用場(chǎng)景都是只能用來(lái)輸出的或者只能用來(lái)輸入的。
interface Producer<out T>{
fun producer(): T
}
interface Consumer<in T>{
fun consume(product: T)
}
為了避免我在每個(gè)使用的位置都給變量或者參數(shù)寫(xiě)上in或out這么麻煩(以下寫(xiě)法)陈哑,那么就直接在類(lèi)型創(chuàng)建的地方寫(xiě)上就一勞永逸了(以上寫(xiě)法),這其實(shí)是個(gè)簡(jiǎn)便寫(xiě)法伸眶。
val aProducer: Porducer<out A> = ...
val a = aProducer.produce();
...
val bProducer: Porducer<out B> = ...
val b = bProducer.produce();
在類(lèi)型聲明的時(shí)候就加上out或in惊窖,就表示你對(duì)這個(gè)類(lèi)型的定位就是只用來(lái)產(chǎn)出或者消費(fèi)。什么時(shí)候用厘贼!取決你對(duì)這個(gè)類(lèi)的定位界酒!
kotlin還可以在類(lèi)型聲明的時(shí)候用【*】來(lái)填寫(xiě)類(lèi)型參數(shù)。術(shù)語(yǔ)叫Unbounded Wildcard嘴秸,即沒(méi)有上界和下界的意思毁欣。
//kotlin:
var textViews:List<*>
|| 等價(jià)于
var textViews:List<out Any>
//java:
List<?> textViews;
|| 等價(jià)于
List<? extends Object> textViews;
如果你的類(lèi)型聲明里已經(jīng)有了out或者in,那么這個(gè)限制在變量聲明時(shí)也依然存在岳掐。
interface Counter<out T : Number>{
fun count():T
}
...
var counter : Counter<*> = ...
|| 實(shí)際效果是??
var counter : Counter<out Number> = ...
多重上界
java里泛型聲明的時(shí)候可以通過(guò)extends設(shè)置上界凭疮,注意,這個(gè)是類(lèi)型聲明的上界串述,不是【? extends】执解,這個(gè)上界可以設(shè)置成多重的,用【&】連接纲酗,而在kotlin里衰腌,上界的設(shè)置從extends變成了【:】,多重上界寫(xiě)法要把類(lèi)型從尖括號(hào)里拿出來(lái)寫(xiě)觅赊,并在前面加where關(guān)鍵字右蕊。只是把java的多重上界換了個(gè)寫(xiě)法而已。
//java
class Monster<T extends Animal & Food>{
...
}
//kotlin
class Monster<T> where T : Animal , T : Food{
...
}
reified
java泛型里的類(lèi)型參數(shù)(就是那個(gè)T)吮螺,它并不是真正的類(lèi)型饶囚,而是一個(gè)代號(hào),所以不能把它當(dāng)做一個(gè)普通的類(lèi)型來(lái)用规脸,比如不能在方法里檢查一個(gè)對(duì)象是不是T的實(shí)例坯约。但在kotlin里可以加reified解除這種限制。不過(guò)reified自身有個(gè)限制莫鸭,只能用來(lái)inline函數(shù)上
java:
<T> void printIfTypeMatch(Object item){
if(item instanceof T){//報(bào)錯(cuò)D重ぁ!被因!
TLog.e(item)
}
}
kotlin:
inline fun <reified T> printIfTypeMatchI(item: Any){
if(item is T){
println(item)
}
}