[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 ? exntends
und? super
? exntends
List<Button> buttons=new ArrayList<Button>();
List<? extends TextView> textViews=buttons;
Dies ? extends
nennt man 上界通配符
: Lassen Sie Java-Generika Kovarianz haben . Kovarianz soll ermöglichen, dass die obige
Zuweisung zulässig ist.
Es hat zwei Bedeutungen:
- Darunter
?
befindet sich ein Platzhalter, der darauf hinweist, dassList
der generische Typ ein unbekannter Typ ist extends
Schränkt die Obergrenze dieses unbekannten Typs ein, d. h. der generische Typ mussextends
diese
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
implements
bedeutet auch, dass die Obergrenze hier auch sein kanninterface
.
Hier Button
ist TextView
eine 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 get
und , wie zum Beispiel in .add
Java
List
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.
List
Sehen 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 TextView
Aufgrund der Einschränkungen , die es erfüllt , get
muss 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>
, handelnList<RadioButton>
.- Für Ersteres
TextView
ist 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 ? extends
generische Platzhalter verwenden, Daten nur für den Verbrauch bereitstellen. Aus dieser Perspektive wird die Partei, die Daten bereitstellt, als „Produzent“ bezeichnet. List
Dementsprechend 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 ? super
wird als Platzhalter für die untere Grenze bezeichnet und kann dazu führen, dass java
Generika widersprüchlich sind
Es gibt zwei Bedeutungen:
- Der durch den Platzhalter
?
dargestellte generische TypList
ist ein unbekannter Typ super
Schränkt die Untergrenze dieses unbekannten Typs ein, d. h. der generische Typ muss diesesuper
Einschränkung erfüllensuper
Wir verwenden es häufig in Klassenmethoden. Der Geltungsbereich umfasst hier nicht nurButton
die direkten und indirekten
übergeordneten Klassen, sondern auchButton
sich selbstsuper
Auch unterstütztinterface
.
Gemäß dem obigen Beispiel kann die Zeile, TextView
wenn sie vom übergeordneten Typ ist, auch die Einschränkungsbedingungen Button
erfü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 get
und add
manipulieren 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 clear
natü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 Object
es 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.
out
und in Kotlinin
kotlin
Wie java
Generika kotlin
sind Generika selbst unveränderlich.
- Verwenden Sie Schlüsselwörter out
zur Unterstützung der Kovarianz, entsprechend Java
den Platzhaltern für die Obergrenze in. ? extends
- Verwenden Sie Schlüsselwörter in
zur Unterstützung der Kontravarianz, entsprechend den Java
Platzhaltern für die Obergrenze in? super
var textViews:List<out TextView>
var textViews:List<in TextView>
out
Zeigt 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.
in
Bedeutet: 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()
}
out
Unterabschnitt:
- Unterklassenkompatibles
Derived
ElternteilBase
- 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)
}
in
Unterabschnitt:
- Unterklassenkompatibles
Derived
ElternteilBase
- 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 out
ein „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
out
im generischen Typ der geänderten Klasse verwendet wird, wird dessen Obergrenze verwendet - Wenn es
in
im 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 Szenarienval querMap:QueryMap<*,*>
if(f is Function<*,*>){...}
HashMap<String,List<*>>()
, Hinweis: List<*> ist hier tatsächlichvalue
ein 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:
java
、kotlin
- Zum Beispiel:
- Echte Generika: Echte Typen werden zur Kompilierungszeit generiert und existieren zur Laufzeit
- Zum Beispiel:
C#
,C++
- Zum Beispiel:
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>(""" {...}""") //泛型参数