閱讀本文前,如果對Java泛型不夠了解,不妨先閱讀我之前寫的兩篇說Java泛型的文章
語法比較
泛型方法
Java
public static<T extends Comparable<? super T>> T max(List<? extends T> list){
Iterator<? extends T> iterator = list.iterator();
T result = iterator.next();
while (iterator.hasNext()){
T t = iterator.next();
if(t.compareTo(result) > 0){
result = t;
}
}
return result;
}
Kotlin
fun <T : Comparable<in T>> max(list: List<out T>): T {
val iterator = list.iterator()
var result: T = iterator.next()
while (iterator.hasNext()) {
val t = iterator.next()
if (t.compareTo(result) > 0) {
result = t
}
}
return result
}
查看Kotlin裏的Comparable類和List類源碼,Comparable類定義,List類定義,所以可以不必重複表明Comparable的逆變能力和List的協變能力。
fun <T : Comparable<T>> max(list: List<T>): T {
val iterator = list.iterator()
var result: T = iterator.next()
while (iterator.hasNext()) {
val t = iterator.next()
if (t.compareTo(result) > 0) {
result = t
}
}
return result
}
不管是泛型類還是泛型方法,泛型的使用分爲兩個步驟
- 第一步,聲明泛型參數
- 第二步,纔是使用泛型參數
上面的泛型方法,以Java泛型方法爲例,泛型聲明部分是:
<T extends Comparable<? super T>>
引入泛型類型名稱爲T,這個數據類型的關係是實現Comparable的類。
使用泛型的部分如下:
List<? extends T>
入參的集合具有對類型T的協變能力。
所以,有些書或者網上資料,會出現兩個概念:聲明點變型,使用點變型。這個概念說的就是這件事
再來看下in位置,和out位置的概念
所以,類型參數T上的關鍵字out有兩方面含義:
- 具有協變的能力
- T只能用在out位置
我們再看一個有趣的例子
我有一個方法,入參的一個集合,我希望入參集合元素是EditText的子類的集合,如何構建滿足這個關係的方法呢?至少有下面的兩種方式:
fun test2(list: MutableList<out EditText>){
}
fun <T: EditText>test3(list: MutableList<T>){
}
上面test2, test3方法都能實現預期的效果。不同的是,test2使用協變來實現,也意味着只能讀不能寫操作,test2不是泛型方法。test3方法通過在聲明表明泛型參數的類型關係來實現,可讀可寫,它是泛型方法。
對應的Java代碼如下
private void test2(List<? extends EditText> list){
}
private <T extends EditText> void test3(List<T> list){
}
實現泛型參數多約束
Java
private <T extends Serializable & CharSequence> void ensureTrailingPeriod(T seq){
}
kotlin
fun <T> ensureTrailingPeriod(seq: T) where T: Serializable, T: CharSequence{
}
這是Java語法上很大的不同,Kotlin要實現多約束,使用到where關鍵詞,這是全新的表示法。
實化類型參數
來看一段Java代碼:
private <T> void test(T t){
if(t instanceof String){
String s = (String)t;
}else if(t instanceof Integer){
Integer i = (Integer)t;
}
}
入參是泛型的,通過instanceof來判斷泛型具體是哪個類型。再看一段代碼
private <T> void test2(Object o){
//編譯不通過
if(o instanceof T){
}
}
泛型作爲參數類型時,編譯不通過。如果要完成上述的邏輯,要怎麼實現?可以通過class近似等效實現
class ClassJudge<T>{
Class<T> kind;
public ClassJudge(Class<T> kind) {
this.kind = kind;
}
public boolean isInstance(Object o){
return kind.isInstance(o);
}
}
調用
Integer a = 12;
boolean instance = new ClassJudge<String>(String.class).isInstance(a);
System.out.println("instance = " + instance);
Kotlin在語言層面作出了支持,對上述Java代碼test2方法,kotlin代碼如下:
inline fun<reified T> test2(any: Any){
if(any is T){
}
}
因此需要具備兩個條件:
- 是內聯函數 inline
- 關鍵詞 reified
集合協變
在Kotlin裏,非空類型是可空類型的子類型。
Java實現的協變
List<? extends Number> list = new ArrayList<Integer>();
Kotlin實現的協變
val list: MutableList<out Number> = arrayListOf<Int>()
集合逆變
Java實現的逆變
List<? super Integer> list = new ArrayList<Number>();
對應的Kotlin逆變的實現:
val list: MutableList<in Int> = arrayListOf<Number>()
變型(協變和逆變)涉及到集合元素,集合類。協變講的是兩個集合的元素是子類關係,這兩個集合也是子類關係,有了子類關係,就可以用多態表示。逆變的關係是反過來的,逆變說得是,兩個集合的元素是父類關係,這兩個集合卻能成爲子類關係。
由此可見,在泛型裏,extends 不全等於 :。extends代表子類型關係和協變,而Kotlin的 :只代表子類型關係。
<out T> = <? extends T>
<in T> = <? super T>
星投影和Java無限制通配符?差異
星投影如何使用
<*> = <out Any?>
<*> = <in nothing>
投影一詞,顧名思義,是對Any?的投影,獲得了Any?部分的能力—Any?協變的能力,即失去寫操作,只能讀操作。
fun printFirst(list: List<*>){
if(list.isNotEmpty()){
println(list.first())
}
}
場景二:泛型類存儲
所有不安全的邏輯都被隱藏在類的主體中,通過把這些邏輯局部化到一個分開的位置,預防了誤用,保證了它不會被錯誤地使用。
其他
非空性與泛型
Java和Kotlin默認,泛型參數都是可空的,以Kotlin爲例
class Processor<T> {
fun process(value: T){
value?.hashCode()
}
}
泛型參數是可空類型,可以理解爲默認實現
class Processor2<T: Any> {
fun process(value: T){
value.hashCode()
}
}
所以,泛型 和是有區別的,前者是可空類型,後者是非空類型,確切說,和
小結
Kotlin和Java的差異,體現在語法和功能兩個方面
語法上
- Kotlin使用:替代 extends;
- 用星投影 * 替代?
- 用out, in來體現協變和逆變;
- 泛型類型多約束條件,Kotlin使用where關鍵詞,而不用Java裏&表示
功能上:
1. 新增了實化參數類型