Generika für den Einstieg in Kotlin

[Beginnen Sie mit dem Lernen aus dem Code] Generika in Kotlin

Bevor wir Kotlin-Generika lernen, werfen wir einen Blick auf die Grundlagen von Java-Generika.

Apropos Generika: Wir werden möglicherweise am häufigsten in den drei großen Sammlungen verwendet.

generisch

Verallgemeinern Sie den spezifischen Typ, verwenden Sie Symbole, um den Typ beim Codieren zu ersetzen, und bestimmen Sie dann seinen Typ, wenn Sie ihn verwenden.

Aufgrund der Existenz von Generika können wir die Besetzung retten.

Generika hängen mit Typen zusammen. Können wir also auch Polymorphismus mit Typen verwenden?

Szene eins:

//多态,因为Button是TextView的子类,向上转型
TextView textView=new Button(context);  

List<Button> buttons=new ArrayList<Button>();
//当我们将多态使用到这里时,就发生错误。
List<TextView> textViews=buttons;

Warum List<TextView> textViews=buttons;wird ein Fehler gemeldet? Dies liegt daran, dass die Generika von Java von Natur aus unveränderlich
sind . In Java gilt dies als inkonsistent mit dem Typ, dh der generische Typ ( ) der Unterklasse gehört nicht zur Unterklasse des generischen Typs ().List<TextView>List<Button>
List<Button>List<TextView>

Die generischen Typen von Java werden zur Kompilierungszeit einer Typlöschung unterzogen . Um die Typsicherheit zu gewährleisten, ist eine solche Zuweisung nicht zulässig.
Was Typlöschung ist, werden wir später besprechen.

Im tatsächlichen Gebrauch werden wir tatsächlich diese ähnliche Anforderung verwenden und müssen die obige Aufgabe implementieren.
Java hat auch daran gedacht und stellt uns daher generische Platzhalter ? exntends zur Verfügung , ? super
um dieses Problem zu lösen

Richtiges Verständnis der Java-Generika ? exntendsund? super

? exntends

    List<Button> buttons=new ArrayList<Button>();
    List<? extends TextView> textViews=buttons;
    

Dies ? extendsnennt man 上界通配符: Lassen Sie Java-Generika Kovarianz haben . Kovarianz soll ermöglichen, dass die obige
Zuweisung zulässig ist.

Es hat zwei Bedeutungen:

  1. Darunter ?befindet sich ein Platzhalter, der darauf hinweist, dass Listder generische Typ ein unbekannter Typ ist
  2. extendsSchränkt die Obergrenze dieses unbekannten Typs ein, d. h. der generische Typ muss extendsdiese
    Einschränkung erfüllen

Dies unterscheidet sich ein wenig vom definierten Schlüsselwort class:extends

  • Sein Geltungsbereich umfasst nicht nur alle direkten oder indirekten Unterklassen, sondern auch die übergeordnete Klasse selbst, die durch die Obergrenze definiert ist, d.
    TextView
  • Das implementsbedeutet auch, dass die Obergrenze hier auch sein kann interface.

Hier Buttonist TextVieweine Unterklasse von, sodass sie die Einschränkungen des generischen Typs erfüllt und erfolgreich
zugewiesen werden kann.

Auch die folgenden Situationen können erfolgreich zugeordnet werden.

List<? extends TextView> textViews=new ArrayList<TextView>(); //本身
List<? extends TextView> textViews=new ArrayList<Button>(); //直接子类
List<? extends TextView> textViews=new ArrayList<RadioButton>(); //间接子类

Die allgemeine Sammlungsklasse enthält zwei Operationen von getund , wie zum Beispiel in .addJavaList

public interface List<E> extends Collection<E>{
    
    
    E get(int index);
    boolean add(E e);
}

Die Symbole im obigen Code E, die generische Typen darstellen.

ListSehen wir uns an , ob es Probleme mit der Verwendung des Platzhalters für die Obergrenze gibt:

List<? extends TextView> textViews=new ArrayList<Button>();
TextView textView=textViews.get(0);//get方法可以使用
textViews.add(textView);//add会报错

Wie bereits erwähnt, ist der generische Typ von List<? Extens TextView> ein unbekannter Typ? Der Compiler ist sich nicht sicher, um
welchen Typ es sich handelt, es gibt jedoch eine Einschränkung.

? extends TextViewAufgrund der Einschränkungen , die es erfüllt , getmuss das ausgegebene Objekt TextView
eine Unterklasse von sein. Aufgrund der Merkmale des Polymorphismus kann er zugeordnet werden TextView.

Wenn es um die Bedienung geht add, können wir es so verstehen:

  • List<? extends TextView>Da der Typ unbekannt ist, könnte es sich um eine Liste oder eine
    List<TextView>, handeln List<RadioButton>.
  • Für Ersteres TextViewist es uns natürlich nicht möglich, etwas hinzuzufügen
  • Die Realität ist, dass der Compiler nicht bestimmen kann, zu welchem ​​es gehört. Wenn die Ausführung nicht fortgesetzt werden kann, wird ein Fehler gemeldet.

Sie fragen sich vielleicht, warum ich dann Platzhalter verwende ??

Eigentlich List<?>das Äquivalent List<? extends Object>der Abkürzung.

Aufgrund der Einschränkung von add können diejenigen, die ? extendsgenerische Platzhalter verwenden, Daten nur für den Verbrauch bereitstellen. Aus dieser Perspektive wird die Partei, die Daten bereitstellt, als „Produzent“ bezeichnet. ListDementsprechend gibt es ein anderes Konzept namens „Consumer“, das einem anderen generischen Platzhalter in Java entspricht? super.

? super

List<? super Button> buttons=new ArrayList<TextView>()

Dies ? superwird als Platzhalter für die untere Grenze bezeichnet und kann dazu führen, dass javaGenerika widersprüchlich sind

Es gibt zwei Bedeutungen:

  • Der durch den Platzhalter ?dargestellte generische Typ Listist ein unbekannter Typ
  • superSchränkt die Untergrenze dieses unbekannten Typs ein, d. h. der generische Typ muss diese super
    Einschränkung erfüllen
    • superWir verwenden es häufig in Klassenmethoden. Der Geltungsbereich umfasst hier nicht nur Buttondie direkten und indirekten
      übergeordneten Klassen, sondern auch Buttonsich selbst
    • superAuch unterstützt interface.

Gemäß dem obigen Beispiel kann die Zeile, TextViewwenn sie vom übergeordneten Typ ist, auch die Einschränkungsbedingungen Buttonerfüllen und der Wert kann erfolgreich zugewiesen werden.super

List<? super Button> buttons=new ArrayList<Button>(); //本身
List<? super Button> buttons=new ArrayList<TextView>(); //直接父类
List<? super Button> buttons=new ArrayList<Object>(); //间接父类

Für diejenigen, die Platzhalter mit unterer Grenze verwenden List, betrachten getund addmanipulieren wir Folgendes:

List<? super Button> buttons=new ArrayList<TextView>();
Object object=buttons.get(0); //此处get()可以获取,是因为Object是所有类的父类
Button button =new Button();
buttons.add(button)

Erklären Sie zunächst: Zeigt einen unbekannten Typ an und der Compiler ist sich seines Typs nicht sicher.

Obwohl ich seinen spezifischen Typ nicht kenne, ist jedes Objekt in Java eine Unterklasse von Object und kann daher hier Object zugewiesen werden.

Das Button-Objekt muss ein Untertyp dieses unbekannten Typs sein. Gemäß den Merkmalen des Polymorphismus ist es zulässig, das Button-Objekt über hier hinzufügen hinzuzufügen.

Verwenden Sie den Platzhalter für die untere Grenze? Die generische Liste von Super kann nur Objektobjekte lesen. Im Allgemeinen gibt es kein tatsächliches Verwendungsszenario. Normalerweise wird es nur
zum Hinzufügen von Daten verwendet, dh zum Verbrauchen der vorhandenen Liste<? super Button>, und geben Sie die Schaltfläche „Hinzufügen“ ein,
sodass diese generische Typdeklaration „Consumer Consumer“ heißt.

Zusammenfassend lässt sich sagen, dass die Generika von Java selbst keine Kovarianz und Kontravarianz unterstützen.

Generische Platzhalter können verwendet werden ? extends, um Generika die Unterstützung von Kovarianz zu ermöglichen, aber „kann nur gelesen und nicht geändert werden“,
die Änderung bezieht sich hier nur auf das Hinzufügen von Elementen zur generischen Sammlung, sofern dies der Fall ist remove(int index)und clearnatürlich möglich ist.

Generische Platzhalter können verwendet werden ? super, um Generika die Unterstützung der Inversion zu ermöglichen, aber „kann nur geändert und nicht gelesen werden“. Das Nichtlesbare
bedeutet hier, dass Sie nicht gemäß dem generischen Typ lesen können. Wenn Sie Objectes auslesen und es dann erzwingen, Natürlich kannst du das.

Nachdem wir über Generika in Java gesprochen haben, werfen wir einen Blick zurück auf Generika in Kotlin.

outund in Kotlinin

kotlinWie javaGenerika kotlinsind Generika selbst unveränderlich.
- Verwenden Sie Schlüsselwörter outzur Unterstützung der Kovarianz, entsprechend Javaden Platzhaltern für die Obergrenze in. ? extends
- Verwenden Sie Schlüsselwörter inzur Unterstützung der Kontravarianz, entsprechend den JavaPlatzhaltern für die Obergrenze in? super

var textViews:List<out TextView>
var textViews:List<in TextView>

outZeigt an, dass meine Variable oder mein Parameter nur für die Ausgabe und nicht für die Eingabe verwendet werden kann. Sie können mich nur lesen, nicht schreiben.

inBedeutet: Ich diene nur der Eingabe, nicht der Ausgabe, man kann mir nur schreiben, nicht lesen.

out

interface  Book

interface EduBook:Book

class BookStore<out T:Book>{
    
    
    //此处的返回类型,则为协变点
    fun getBook():T{
    
    
        TODO()
    }
}

fun main() {
    
    
    val eduBookStore:BookStore<EduBook> = BookStore<EduBook>()

    val bookStore:BookStore<Book> =eduBookStore
    //我需要书,不管什么类型的,只要是书即可。所以下列两种均可满足
    val book:Book=bookStore.getBook()
    val book1:Book=eduBookStore.getBook()

    var book2:EduBook = eduBookStore.getBook()
    //此处错误,因为已经指定了需要Edu类型的书,你却将book给我。引发错误
    var book4:EduBook = bookStore.getBook()
}
outUnterabschnitt:
  • Unterklassenkompatibles DerivedElternteilBase
  • Hersteller Producer<Derived>kompatibelProducer<Base>
  • Generische Parameter von Klassen mit kovarianten Punkten müssen als kovariante oder invariante deklariert werden
  • Verwenden Sie Kovarianz , wenn die generische Klasse als Produzent von Instanzen der generischen Parameterklasse fungiert

in

//垃圾
open class Waste
//干垃圾
class DryWaste : Waste(){
    
    }

//垃圾桶
class Dustbin <in T:Waste>{
    
    
    fun put(t:T){
    
    
        TODO()
    }
}

fun demo(){
    
    
    //创建一个垃圾桶
    val dustbin:Dustbin<Waste> =Dustbin<Waste>()
    //创建一个干垃圾桶
    val dryWasteDustbin:Dustbin<DryWaste> = dustbin

    //垃圾对象
    val waste=Waste()
    //干垃圾对象
    val dryWaste=DryWaste()

    //我们的垃圾桶,可以装我们的垃圾,以及干垃圾
    dustbin.put(waste)
    dustbin.put(dryWaste)

    //而干垃圾桶,只能装干垃圾,所以下面这句话,是错误的。
    dryWasteDustbin.put(waste)
    dryWasteDustbin.put(dryWaste)
}

inUnterabschnitt:
  • Unterklassenkompatibles DerivedElternteilBase
  • verbraucherkompatibel Producer<Derived>_Producer<Base>
  • Generische Parameter von Klassen mit kontravarianten Punkten müssen als kovariante oder invariante deklariert werden
  • Verwenden Sie Kovarianz , wenn generische Klassen als Konsumenten von Instanzen generischer Parameterklassen fungieren

*Nummer

*Zahl
Wie bereits erwähnt, kann eine einzelne ?Zahl in Java auch als generischer Platzhalter verwendet werden, was äquivalent ist ? extends Object.
Es gibt ein Äquivalent in Kotlin: *number, equal out Any.

var list: List<*>

Der Unterschied zu Java besteht darin , dass diese Einschränkung bei der Deklaration der Variablen weiterhin besteht und nicht durch die Zahl aufgehoben wird, wenn Sie bereits outein „or“ in Ihrer Typdefinition haben.in
*

Wenn es sich beispielsweise in Ihrer Typdefinition befindet out T : Number, <*>ist der Effekt nach dem Hinzufügen nicht out Any,
sondern out Number.

Beispiel:
Beispiel für einen Kovariationspunkt

class QueryMap<out K:CharSequence,out V:Any> {
    
    
    fun getKey():K =TODO()
    fun getValue():V =TODO()
}
val queryMap:QuerMap<*,*>= QueryMap<String,Int>()
queryMap.getKey()//类型为CharSequence
queryMap.getValue()//类型为Any

Beispiel für einen Umkehrpunkt

class Function<in P1,in P2>{
    
    
    fun invoke(p1: P1,p2: P2)=TODO()
}
val f:Function<*,*>=Function<Number,Any>()
f.invoke()//参数为下限,但是我们的kotlin中下限为`Nothing`,无法实例化。所以该方法的参数是传入不了的
*Regel
  • Wenn es outim generischen Typ der geänderten Klasse verwendet wird, wird dessen Obergrenze verwendet
  • Wenn es inim generischen Typ der geänderten Klasse verwendet wird, wird dessen Untergrenze verwendetNothing
*Umfang der Nutzung
  • *Kann nicht direkt oder indirekt auf Eigenschaften oder Funktionen angewendet werden

    • Falscher Weg:
    • QueryMap<String,*>()
    • maxOf<*>(1,3)
  • *Geeignet für den Einsatz in den als Typ beschriebenen Szenarien

    • val querMap:QueryMap<*,*>
    • if(f is Function<*,*>){...}
    • HashMap<String,List<*>>(), Hinweis: List<*> ist hier tatsächlich valueein generischer Parameter

    Generisches Konzept

1. Generics sind eine Abstraktion auf Typebene

2. Generika erkennen die Fähigkeit, allgemeinere Typen durch generische Parameter zu konstruieren

3. Generics ermöglichen Typen, die der Vererbungsbeziehung entsprechen, die Implementierung bestimmter Funktionen in Stapeln

generische Klasse

    class List<T> {
    
    }

generische Methode

    fun <T> maxOf(a:T,b:T):T

Allgemeine Einschränkungen

//表示 T 是Comparable的实现类
fun <T : Comparable<T>> maxOf(a:T,b:T):T{
    
    
    return if(a>b) a else b
}

mehrere generische Einschränkungen ( where)

//表示 T 是Comparable的实现类,并且是一个返回值为Unit的方法
 fun <T> callMax(a:T,b:T) where T:Comparable<T>,T:() ->Unit{
    
    
    if (a>b) a() else b()
 }

mehrere generische Parameter

    //该函数返回类型R必须继承Number, T 必须实现Comparable 接口,并且是一个返回类型为R的方法
    fun <T,R> callMax(a:T,b:T):R 
            where T:Comparable<T>,T:() -> R,R:Number{
    
    
        return if (a>b) a() else b()
    }

Inline-Spezialisierung

Wir müssen das Prinzip der Generika kennen, wenn wir die Inline-Spezialisierung erklären wollen.

  • Pseudogenerisch: Typlöschung zur Kompilierzeit, keine tatsächliche Typgenerierung zur Laufzeit
    • Zum Beispiel: javakotlin
  • Echte Generika: Echte Typen werden zur Kompilierungszeit generiert und existieren zur Laufzeit
    • Zum Beispiel: C#,C++

Wir wissen, dass Generika in der JVM im Allgemeinen durch Typlöschung implementiert werden, daher werden sie auch Pseudo-Generika genannt, was bedeutet, dass Typargumente zur Laufzeit nicht gespeichert werden.

Tatsächlich können wir eine Inline-Funktion so deklarieren, dass ihre Typargumente nicht gelöscht werden, aber das ist in Java nicht möglich.

 inline fun <reified T> getericMethod(t:T){
    
    
    //此处编译器报错,因为我们即使知道了这个T是什么类型,但不清楚,T有没有无参构造器
    val t=T()
    val ts=Array<T>(3){
    
     TODO()}
    val jclass=T::class.java
    val list=ArrayList<T>()
 }

praktische Anwendung

Gson中的
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    Object object = fromJson(json, (Type) classOfT);
    return Primitives.wrap(classOfT).cast(object);
  }
  
//我们可以简写为
inline fun <reified T> Gson.fromJson(json:String):T =fromJson(json,T::class.java)

//使用时
val person:Person=gson.fromJson(""" {...}""") //通过类型推导
val person=gson.fromJson<Person>(""" {...}""") //泛型参数

Ich denke du magst

Origin blog.csdn.net/weixin_44710164/article/details/108632281
Empfohlen
Rangfolge