Kotlin細節文章筆記整理更新進度:
Kotlin系列 - 基礎類型結構細節小結(一)
Kotlin系列 - 函數與類相關細節小結(二)
Kotlin系列 - 高階函數與標準庫中的經常使用函數(三)java
本篇文章從java
開始講泛型,後面再切換到kotlin
,重點java
的泛型掌握住,koltin
的泛型就會很快掌握。(可自行選取節段食用,碼字不易看完以爲還能夠的,麻煩給贊,本人能力有限,有錯誤或者有問題在評論區留言,感激~~)json
總結數組
- 虛擬機沒有泛型,只有普通方法和類。
- 全部的類型參數都用它們的限定類型替換。
- 橋方法被合成用於保持多態。
- 爲保持類型安全性,必要時插入強制類型轉換。
泛型,也稱參數化類型。可使代碼應用多種類型。使用類型參數,用尖括號括住,放在類名後面。在使用該類的時候用實際的類型替換該類型參數。 示例:安全
//這裏的參數T能夠自由命名
ClassA<T>{}
複製代碼
若是在程序中只能使用具體的類型、具體的基本類型,或者自定義的類,在編寫多種類型的代碼,這種限制會對代碼有很大的約束。咱們須要一種在可在運行是才肯定類型的一種方法來實現代碼更加通用。 示例:bash
public class PrintClass {
static public void printInt(Integer a, Integer b) {
System.out.println("參數a=" + a + "參數b=" + b);
}
static public void printFloat(Float a, Float b) {
System.out.println("參數a=" + a + "參數b=" + b);
}
static public void printDouble(Double a, Double b) {
System.out.println("參數a=" + a + "參數b=" + b);
}
}
複製代碼
改爲泛型函數:ide
public class PrintClass1 {
static public <T> void printMultiply(T a, T b) {
System.out.println("參數a=" + a + "參數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---------------------------
參數a=10.0參數b=10.0
參數a=10參數b=10
參數a=10.0參數b=10.0
參數a=10.0參數b=10.0
參數a=10參數b=10
參數a=10.0參數b=10.0
參數a=100參數b=100
複製代碼
經過上面的展現,你們對泛型有個最基本的瞭解。oop
interface Animal<T> {
void name();
void cry();
void mysteryData(T t);
}
複製代碼
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("假設它擁有一種數據類型"+ s.getClass().getName());
}
}
複製代碼
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("假設它擁有一種數據類型"+t.getClass().getName());
}
}
複製代碼
使用:post
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日誌
狗
汪汪汪
假設它擁有一種數據類型java.lang.Integer
狗
喵喵
假設它擁有一種數據類型java.lang.String
複製代碼
上面接口泛型實現二
中就是用了類泛型的實現了。ui
public class PrintClass1 {
static public <T> void printMultiply(T a, T b) {
System.out.println("參數a=" + a + "參數b=" + b);
}
}
複製代碼
有時候類、方法須要對類型變量進行約束。
例如:增長一個類,用於專門打印各類動物的叫聲 (這裏泛型雖然能夠替換爲Animal
,可是這個演示案例我就使用泛型替代,這個不是重點)
class AnimalCry{
public static <T> void cry(T a){
a.cry();
}
}
複製代碼
這裏你單純這樣子寫,它不會有識別到
cry
這個方法,由於這個方法是Animal
接口的Cat
、Dog
等持有的方法,因此咱們要給它個類型變量的限定。
class AnimalCry{
public static <T extends Animal> void cry(T a){
a.cry();
}
}
--------------------調用
public static void main(String[] args) {
AnimalCry.cry(new Dog());
}
--------------------打印的Log
汪汪汪
複製代碼
格式
<T extends BoundingType> extends
後面能夠跟接口
或者類名
。T
:表示爲綁定類型的子類型。 多個限定類型用&
進行多個限定,例如<T extends BoundingType & BoundingType1 & BoundingType2 & ... >
java
的虛擬機中沒有泛型類型對象,全部的對象都是普通類,不管什麼時候定義一個泛型類型,都會自動體用一個對應的原始類型。原始類型的名字就是擦拭後的類型參數的類型名。若是沒有限定的類型變量則用Object
代替,若是有限定則以限定的爲代替。
public class PrintClass1 {
public static <T> void printMultiply(T a, T b) {
System.out.println("參數a=" + a + "參數b=" + b);
}
}
--------------編譯後爲
public class PrintClass1 {
public static void printMultiply(Object a, Object b) {
System.out.println("參數a=" + a + "參數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();
}
}
複製代碼
大多數的限制都是由類型擦拭帶來的。
Pair<double>
只有Pair<Double>
,由於泛型擦拭後,Pair
類含有Object
類型的域,而Object
不能存儲double
值。Pair p = new Pair("str","str1");
Pair i = new Pair(10,20);
// illegal generic type for instanceof 沒法使用instanceof關鍵字判斷泛型類的類型
if (p instanceof Pair<String,String>)
//比較結果都是true,由於兩次調用都是返回Pair.class
if (p.getClass() == i.getClass()){}
複製代碼
Pair<String,String>[] pairs = new Pair[10];
pairs[0] = new Pair(10,20);
//雖然能賦值可是會致使類型錯誤。
複製代碼
new T(...)
、new T[]
、T.class
等表達式的變量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;
}
}
----------------------------------------------調用的時候
錯誤,返回Object類型
AType a = Singleton.getSingleInstance();
錯誤,這種用法是不容許的,只能在調用方法或構造方法時傳遞泛型參數
AType a = Singleton<AType>.getSingleInstance();
複製代碼
public class Pair<T,Q> extends Exception{} // 報錯,不能繼承Exception或者Throwable
public static <T extends Throwable> void doWork(Class<T> t){
try{
....
}catch(T e){ //報錯 這裏不能拋出泛型類型
}
}
//正確。
public static <T extends Throwable> void doWork(T t) throws T{
try{
....
}catch(Throwable t){
}
}
複製代碼
interface Animal{}
public class Cat extends Animal{}
public class Dog extends Animal{}
public class Cry<T>{}
------------------------------------------------
Cry<Animal> 與 Cry<Cat> 不是繼承關係,也沒有什麼關係。
複製代碼
public class ArrayList<E> extends AbstractList<E>
複製代碼
小結:
協變:
<? extends Class>
指定泛型類型的上限,只能讀取不能修改(修改是指對泛型集合添加元素,若是是remove(int index)
以及clear
固然是能夠的)逆變:
<? super Class>
指定泛型類型的下線,只能修改不能讀取,(不能讀取是指不能按照泛型類型讀取,你若是按照Object
讀出來再強轉也能夠)
<?>
至關於< ? extends Object>
指定沒有限制的泛型類型
以上面的圖爲例子:
<? extends Class>
指定泛型類型的上限它的限定範圍爲上限自己(上限能夠是接口)以及全部直接跟間接的子類。
Animal animal = new Dog();// java的多態
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; //這裏會報錯 incompatible types: List<Dog> cannot be converted to List<Animal>
複製代碼
上面的例子由於發生了類型擦拭,爲了保證類型安全因此不容許這樣子賦值。 這個時候就可使用協變的寫法<? extends Class>
限制參數類型的上界,也就是泛型類型必須知足這個 extends
的限制條件。
List<? extends Animal> animals = new ArrayList<Animal>(); // 自己
List<? extends Animal> animals = new ArrayList<Cat>(); // 直接子類
List<? extends Animal> animals = new ArrayList<ShamoDog>(); // 間接子類
複製代碼
只可以向外提供數據被消費,相似
生產者
。
List<? extends Animal> animals = new ArrayList<Dog>();
Animal animal= animals.get(0); //get 出來的是 Animal 類型的
animals.add(textView);//報錯,no suitable method found for add(TextView)
複製代碼
它的限定範圍爲下限自己(下限能夠是接口)以及全部直接跟間接的父類。
List<? super ShamoDog> shamoDogs= new ArrayList<shamoDogs>(); // 自己
List<? super ShamoDog> shamoDogs= new ArrayList<WailaiDog>();//直接接父類
List<? super ShamoDog> shamoDogs= new ArrayList<Animal>();//間接父類
複製代碼
只能讀取到
Object
對象,一般也只拿它來添加數據,也就是消費已有的List<? super ShamoDog>
,往裏面添加Dog
,所以這種泛型類型聲明相對協變
能夠稱爲消費者
List<? super ShamoDog> shamoDogs = new ArrayList<Animal>();
Object object = shamoDogs.get(0); // get 出來的是 Object 類型
Dog dog = ...
shamoDogs.add(dog); // add 操做是能夠的
複製代碼
與java
泛型同樣的格式
interface AnimalKot<T> { } //接口泛型
class DogKot<Q> { } //類泛型
fun <T>TestLooperManager(t:T): Unit { }//方法泛型
複製代碼
out
、in
、*
、where
out
:協變、與java
的上限通配符<? extends BoundType>
對應in
:逆變,與java
的下限通配符<? super BoundType>
對應*
: 與 java
的 <?>
,不過java
的是<? extends Object>
,kotlin
的是<out Any>
where
: 與java
的<T extends Animal & Person >
的&
符號對應//where的示例
//java中多個限定泛型定義
public class Cry <T extends Animal & Person>{ }
//kotlin的對應寫法
class Cry<T> where T:Animal,T:Person{ }
複製代碼
重點:
kotlin
提供另一種附加功能,在聲明類的時候,給泛型類型加上in
關鍵字,代表泛型參數T
只會用來輸入,在使用的時候就不用額外加in
。對應out
,則是代表泛型參數T
只會用來輸出,使用時不須要額外加out
。
例子
//koltin的 List
public interface List<out E> : Collection<E> {
}
var animalKot:List<Animal<String>> = ArrayList<Dog<String>>()// 不報錯
var animalKot:List<out Animal<String>> = ArrayList<Dog<String>>()//寫了out,不報錯
var animalKot:List<in Dog<String>> = ArrayList<Animal<String>>()//報錯,不能寫in
//定義一個 in泛型類型
class All<in T>{
fun p(t:T){
}
}
var all:All<Dog<String>> = All()// 不報錯
var all:All<in Dog<String>> = All()//寫了in,不報錯
var all:All<out Dog<String>> = All()//報錯,不能寫in
複製代碼
reified
關於java
泛型存在擦拭的狀況下,在上面5、約束性與侷限性
中第二點中提到的 運行時類型查詢只適用於原始類型
<T> void println(Object obj) {
if (obj instanceof T) { // IDE提示錯誤,illegal generic type for instanceof
}
}
kotlin也是如此---------------------------------------------------------
fun <T> println(any: Any) {
if (any is T) { // IDE提示錯誤,Cannot check for instance of erased type: T
}
}
複製代碼
java
的解決方法:額外傳遞一個 Class<T>
類型的參數,而後經過 Class#isInstance
方法來檢查
<T> void println(Object obj, Class<T> type) {
if (type.isInstance(obj )) {
}
}
複製代碼
Kotlin
的解決方法,就是reified
關鍵字,可是 reified
只能在inline
表示的方法中使用,因此,要使用inline
方法。
inline fun <reified T> println(any: Any) {
if (any is T) {
println(item)
}
}
複製代碼
inline
內聯函數,當方法在編譯時會拆解方法的調用爲語句的調用,進而減小建立沒必要要的對象。在kotlin中一個inline
能夠被具體化reified
,這意味着咱們能夠獲得使用泛型類型的Class
。
//定義`Gson`的擴展函數
inline fun <reified T> Gson.fromJson(json:String):T = fromJson(json,T::class.java)
複製代碼
@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
源碼,能夠看到定義類泛型限定的時候爲<out E>
爲協變,只用來輸出,可是這裏面的方法override fun contains(element: @UnsafeVariance E): Boolean
爲了支持輸入,則使用@UnsafeVariance
避免IDE的檢查
Java
裏的數組是支持協變的,而 Kotlin
中的數組 Array
不支持協變。 Kotlin
中數組是用 Array
類來表示的,這個 Array
類使用泛型就和集合類同樣,因此不支持協變。
Java
中的 List
接口不支持協變,而 Kotlin
中的 List
接口支持協變。 在 Kotlin
中,實際上 MutableList
接口才至關於 Java
的 List
。Kotlin
中的 List
接口實現了只讀操做,沒有寫操做,因此不會有類型安全上的問題,天然能夠支持協變。
Kotlin 的泛型 Java核心技術 卷一