Lassen Sie uns heute über die Leistung von Kotlin auf Android in der Produktion sprechen. Lassen Sie uns unter die Haube schauen, knifflige Optimierungen implementieren und den Bytecode vergleichen. Schließlich werden wir uns dem Vergleich ernsthaft nähern und die Benchmarks messen.
Dieser Artikel basiert auf einem Bericht von
Alexander Smirnov auf der AppsConf 2017 und hilft herauszufinden, ob es möglich ist, Code in Kotlin zu schreiben, der in seiner Geschwindigkeit Java nicht unterlegen ist.
Über den Sprecher: Alexander Smirnov CTO bei PapaJobs, betreibt den Videoblog
Android in Faces und ist auch einer der Organisatoren der Mosdroid-Community.
Beginnen wir mit Ihren Erwartungen.
Denken Sie, dass Kotlin zur Laufzeit langsamer ist als Java? Oder schneller? Oder gibt es keinen großen Unterschied? Schließlich arbeiten beide an dem Bytecode, den uns die virtuelle Maschine zur Verfügung stellt.
Lass es uns richtig machen. Wenn sich die Frage nach dem Leistungsvergleich stellt, möchte traditionell jeder Benchmarks und bestimmte Zahlen sehen. Leider gibt es für Android kein JMH (
Java Microbenchmark Harness ), sodass wir nicht einfach messen können, wie cool es in Java sein kann. Was können wir also tun, um die Messung wie unten beschrieben durchzuführen?
fun measure() : Long { val startTime = System.nanoTime() work() return System.nanoTime() - startTime } adb shell dumpsys gfxinfo %package_name%
Wenn Sie jemals versuchen, Ihren Code auf diese Weise zu messen, wird einer der JMH-Entwickler traurig sein, weinen und in einem Traum zu Ihnen kommen - tun Sie das niemals.
Auf Android können Sie Benchmarks erstellen, insbesondere hat Google dies auf der I / O im letzten Jahr demonstriert. Sie sagten, dass sie die virtuelle Maschine, in diesem Fall ART, erheblich verbessert hätten. Wenn unter Android 4.1 eine Zuordnung eines Objekts etwa 600 bis 700 Nanosekunden dauerte, würde dies in der achten Version etwa 60 Nanosekunden dauern. Das heißt, Sie konnten es in einer virtuellen Maschine mit solcher Genauigkeit messen. Warum wir das auch nicht können - wir haben keine solchen Tools.
Wenn wir uns die gesamte Dokumentation ansehen, können wir nur die obige Empfehlung finden, wie die Benutzeroberfläche gemessen werden kann:
adb shell dumpsys gfxinfo% paketname%Eigentlich machen wir es so und sehen am Ende, was es geben wird. Aber zuerst werden wir bestimmen, was wir messen und was wir sonst noch tun können.
Die nächste Frage. Wo ist Ihrer Meinung nach Leistung wichtig, wenn Sie eine erstklassige Anwendung erstellen?
- Auf jeden Fall überall.
- UI-Thread.
- Benutzerdefinierte Ansicht + Animationen.

Ich mag die erste Option am meisten, aber höchstwahrscheinlich wird angenommen, dass es unmöglich ist, den gesamten Code sehr, sehr schnell zum Laufen zu bringen, und es ist wichtig, dass zumindest kein UiThread oder keine benutzerdefinierte Ansicht vorhanden ist. Dem stimme ich auch zu - es ist sehr, sehr wichtig. Die Tatsache, dass in Ihrem separaten JSON-Stream 10 Millisekunden länger deserialisiert wird, wird niemand bemerken.
Die Gestaltpsychologie sagt, dass das menschliche Auge unscharf ist und nicht sieht, was dort tatsächlich passiert, wenn wir etwa 150 bis 300 Millisekunden lang blinken. Und dann diese 10 Millisekunden Wetter nicht. Aber wenn wir zur Gestaltpsychologie zurückkehren, ist es nicht wichtig, was ich wirklich sehe und was wirklich passiert, sondern was ich als Benutzer verstehe.
Das heißt, Wenn wir den Benutzer glauben lassen, dass er alles sehr, sehr schnell hat, aber tatsächlich einfach schön geschlagen wird, zum Beispiel mit Hilfe einer schönen Animation, dann wird er zufrieden sein, auch wenn dies tatsächlich nicht der Fall ist.
Gestaltpsychologische Motive in iOS bewegen sich seit geraumer Zeit. Wenn Sie also zwei Anwendungen mit derselben Verarbeitungszeit, jedoch auf unterschiedlichen Plattformen, nebeneinander stellen, scheint unter iOS alles schneller zu sein. Die Animation in iOS wird etwas schneller verarbeitet, frühere Animationen beginnen beim Start und viele andere Animationen, sodass sie schön sind.
Die
erste Regel ist also, an den Benutzer zu denken.Und für die zweite Regel müssen Sie in Hardcore eintauchen.
KOTLIN STYLE
Um die Leistung von Kotlin ehrlich zu bewerten, werden wir sie mit Java vergleichen. Daher stellt sich heraus, dass es unmöglich ist, einige Dinge zu messen, die nur in Kotlin sind, zum Beispiel:
- Sammlung Api.
- Standardparameter der Methode.
- Datenklassen.
- Reified Typen.
- Coroutinen.
Die Sammlungs-API , die Kotlin uns zur Verfügung stellt, ist sehr cool, sehr schnell. In Java existiert dies einfach nicht, es gibt nur unterschiedliche Implementierungen. Beispielsweise ist die Liteweight Stream-API-Bibliothek langsamer, da sie alles wie Kotlin ausführt, jedoch mit ein oder zwei zusätzlichen Zuordnungen für den Vorgang, da alles zu einem zusätzlichen Objekt wird.
Wenn wir die Stream-API von Java 8 übernehmen, funktioniert sie langsamer als die Kotlin Collection-API, jedoch unter einer Bedingung: Es gibt keine solche Lähmung in der Collection-API, wenn wir sie parallel für große Mengen von Stream-API-Daten einbeziehen. Java umgeht die Kotlin Collection API. Daher können wir solche Dinge nicht vergleichen, da wir den Vergleich genau aus der Sicht von Android durchführen.
Die zweite Sache, die meines Erachtens nicht verglichen werden kann, sind die
Standardparameter der Methode - eine sehr coole Funktion, die übrigens in Dart enthalten ist. Wenn Sie eine Methode aufrufen, enthält sie möglicherweise einige Parameter, die einen bestimmten Wert annehmen, jedoch NULL sein können. Daher erstellen Sie nicht 10 verschiedene Methoden, sondern eine Methode und sagen, dass einer der Parameter NULL sein kann, und verwenden Sie ihn in Zukunft ohne Parameter. Das heißt, er wird schauen, der Parameter ist gekommen oder er ist nicht gekommen. Es ist sehr praktisch, dass Sie viel weniger Code schreiben können, aber der Nachteil ist, dass Sie dafür bezahlen müssen. Dies ist syntaktischer Zucker: Sie als Entwickler denken, dass dies eine API-Methode ist, aber in Wirklichkeit wird unter der Haube jede Variation der Methode mit fehlenden Parametern im Bytecode generiert. Und jede dieser Methoden prüft auch Stück für Stück, ob dieser Parameter angekommen ist. Wenn es kam, dann ok, wenn es nicht kam, dann machen wir eine Bitmaske und abhängig von dieser Bitmaske wird die ursprüngliche Methode, die Sie geschrieben haben, tatsächlich aufgerufen. Bitweise Operationen, alles
wenn / sonst ein wenig Geld kostet, aber sehr wenig, und es ist normal, dass Sie für die Bequemlichkeit bezahlen müssen. Es scheint mir, dass dies absolut normal ist.
Das nächste Element, das nicht verglichen werden kann, sind
Datenklassen .
Jeder weint, dass es in Java Parameter gibt, für die es Modellklassen gibt. Das heißt, Sie nehmen Parameter und machen mehr Methoden, Getter und Setter für all diese Parameter. Es stellt sich heraus, dass Sie für eine Klasse mit zehn Parametern immer noch eine ganze Menge Getter, Setter und eine ganze Menge mehr benötigen. Wenn Sie keine Generatoren verwenden, müssen Sie außerdem mit Ihren Händen schreiben, was im Allgemeinen schrecklich ist.
Mit Kotlin können Sie dem Alltag entfliehen. Erstens, da es Eigenschaften in Kotlin gibt, müssen Sie keine Getter und Setter schreiben. Es hat
keine Klassenparameter, alle Eigenschaften . Auf jeden Fall denken wir so. Zweitens, wenn Sie schreiben, dass dies Datenklassen sind, wird eine ganze Reihe von allem anderen generiert. Zum Beispiel equals (), toStrung () / hasCode () usw.
Dies hat natürlich auch Nachteile. Zum Beispiel musste ich nicht alle 20 Parameter meiner Datenklassen gleichzeitig in meinem equals () vergleichen, sondern nur 3 vergleichen. Jemand mag das alles nicht, weil die Leistung dadurch verloren geht und außerdem viel generiert wird Service-Funktionen, und der kompilierte Code ist ziemlich umfangreich. Das heißt, wenn Sie alles von Hand schreiben, gibt es weniger Code als bei Verwendung von Datenklassen.
Ich verwende keine Datenklassen aus einem anderen Grund. Zuvor gab es Einschränkungen bei der Erweiterung solcher Klassen und etwas anderem. Jetzt ist jeder besser damit, aber die Gewohnheit bleibt.
Was ist in Kotlin sehr, sehr cool und was wird es immer schneller sein als Java? Dies sind
Reified-Typen , die übrigens auch in Dart vorkommen.
Sie wissen, dass bei Verwendung von Generika die Typlöschung in der Kompilierungsphase gelöscht wird und Sie zur Laufzeit nicht mehr wissen, welches Objekt dieses Generikums tatsächlich verwendet wird.
Bei Reified-Typen müssen Sie Reflection nicht an vielen Stellen verwenden, wenn Sie es in Java benötigen würden, da Sie bei Inified-Methoden bei Reified den Typ kennen und sich daher herausstellt, dass Sie Reflection nicht verwenden und Ihr Code schneller arbeitet. Die Magie.
Und es gibt
Coroutinen . Sie sind sehr cool, ich mag sie sehr, aber zum Zeitpunkt der Aufführung waren sie nur in der Alpha-Version enthalten, so dass es nicht möglich war, korrekte Vergleiche mit ihnen anzustellen.
FELDER
Gehen wir also weiter zu dem, was wir mit Java vergleichen und was wir allgemein beeinflussen können.
class Test { var a = 5 var b = 6 val c = B() fun work () { val d = a + b val e = ca + cb } } class B (@JvmField var a: Int = 5,var b: Int = 6)
Wie gesagt, wir haben keine Parameter für die Klasse, wir haben Eigenschaften.
Wir haben var, wir haben val, wir haben eine externe Klasse, deren Eigenschaften @JvmField sind, und wir werden untersuchen, was tatsächlich mit der Funktion work () passiert: Wir summieren den Wert von Feld a und Feld b unserer eigenen Klasse und Werte von Feld a und Feld b der äußeren Klasse, die in das unveränderliche Feld c geschrieben sind.
Die Frage ist, wie tatsächlich in d = a + b genannt wird. Wir alle wissen, dass diese Eigenschaft einmal, der Getter dieser Klasse, für diesen Parameter aufgerufen wird.
L0 LINENUMBER 10 L0 ALOAD 0 GETFIELD kotlin/Test.a : I ALOAD 0 GETFIELD kotlin/Test.b : I IADD ISTORE 1
Wenn wir uns jedoch den Bytecode ansehen, werden wir sehen, dass tatsächlich auf getfield zugegriffen wird. Das heißt, dies im Bytecode ist kein Aufruf der InvokeVirtual-Funktion, sondern ein direkter Zugriff auf das Feld. Es wurde uns zunächst nichts versprochen, dass wir alle Eigenschaften haben, nicht die Felder. Es stellt sich heraus, dass Kotlin uns täuscht, es gibt einen direkten Appell.
Was passiert, wenn wir sehen, welcher Bytecode für eine andere Zeile generiert wird: val e = ca + cb?
L1 LINENUMBER 11 L1 ALOAD 0 GETFIELD kotlin/Test.c : Lkotlin/B; GETFIELD kotlin/Ba : I ALOAD 0 GETFIELD kotlin/Test.c : Lkotlin/B; INVOKEVIRTUAL kotlin/B.getB ()I IADD ISTORE 2
Wenn Sie zuvor auf eine private Immobilie zugegriffen haben, hatten Sie immer einen InvokeVirtual-Aufruf. Wenn dies ein Privateigentum war, erfolgte der Zugriff über GetField. GetField ist viel schneller als InvokeVirtual. Die Spezifikation von Android besagt, dass der direkte Zugriff auf ein Feld drei- bis siebenmal schneller ist. Daher wird empfohlen, dass Sie sich immer auf Field beziehen und nicht über Getter oder Setter. Insbesondere in der achten virtuellen ART-Maschine gibt es bereits unterschiedliche Nummern. Wenn Sie jedoch weiterhin 4.1 unterstützen, ist dies der Fall.
Daher stellt sich heraus, dass es für uns immer noch von Vorteil ist, GetField und nicht InvokeVirtual zu haben.
Jetzt können Sie GetField erreichen, wenn Sie auf eine Eigenschaft Ihrer eigenen Klasse zugreifen oder wenn dies eine öffentliche Eigenschaft ist, müssen Sie @JvmField festlegen. Genau das gleiche im Bytecode ist dann ein GetField-Aufruf, der drei- bis siebenmal schneller ist.
Es ist klar, dass wir hier in Nanosekunden sprechen und mit einem Thron sehr, sehr klein sind. Wenn Sie dies jedoch im UI-Thread tun, z. B. in der Ondraw-Methode, greifen Sie auf eine Ansicht zu, wirkt sich dies auf das Rendern jedes Frames aus, und Sie können dies etwas schneller tun.
Wenn wir alle Optimierungen addieren, kann dies insgesamt etwas ergeben.STATISCH !?
Was ist mit Statik? Wir alle wissen, dass Statik in Kotlin ein Begleitobjekt ist. Zuvor haben Sie wahrscheinlich eine Art Tag hinzugefügt, z. B. public static, final static usw. Wenn Sie dies in Kotlin-Code konvertieren, erhalten Sie ein Begleitobjekt, das etwa Folgendes schreibt:
companion object { var k = 5 fun work2() : Int = 42 }
Denken Sie, dass dieser Eintrag mit der statischen Standarddeklaration in Java identisch ist? Ist es überhaupt statisch oder nicht?
Ja, tatsächlich erklärt Kotlin, dass es hier in Kotlin ist - statisch, dieses Objekt sagt, dass es statisch ist. In Wirklichkeit ist dies nicht statisch.
Wenn wir uns den generierten Bytecode ansehen, sehen wir Folgendes:
L2 LINENUMBER 21 L2 GETSTATIC kotlin/Test.Companion : Lkotlin/Test$Companion; INVOKEVIRTUAL kotlin/Test$Companion.getK ()I GETSTATIC kotlin/Test.Companion : Lkotlin/Test$Companion; INVOKEVIRTUAL kotlin/Test$Companion.work2 ()I IADD ISTORE 3
Ein Test.Companion wird generiert, ein Singleton-Objekt, für das die Instanz erstellt wird. Diese Instanz wird in ein eigenes Feld geschrieben. Danach erfolgt der Zugriff auf eines der Begleitobjekte über dieses Objekt. Er nimmt getstatic, dh eine statische Instanz dieser Klasse, und ruft die getK-Funktion invKvirtual auf, und genau das gleiche gilt für die work2-Funktion. Wir bekommen also, dass es nicht statisch ist.
Dies ist wichtig, da bei älteren JVMs invokestatic etwa 30% schneller war als invokevirtual. Bei HotSpot läuft die optimierte Virtualisierung natürlich sehr cool und ist fast unsichtbar. Sie müssen dies jedoch berücksichtigen, zumal es eine zusätzliche Zuordnung gibt und ein zusätzlicher Speicherort auf 4ST1 700 Nanosekunden zu viel ist.
Schauen wir uns den Java-Code an, der ausgegeben wird, wenn Sie den Bytecode rückwärts bereitstellen:
private static int k = 5; public static final Test.Companion Companion = new Test.Companion((DefaultConstructorMarker)null); public static final class Companion { public final int getK() { return Test.k;} public final void setK(int var1) { Test.k = var1; } public final int work2() { return 42; } private Companion() { }
Ein statisches Feld wird erstellt, eine statische endgültige Implementierung des Companion-Objekts, Getter und Setter werden erstellt, und wie Sie sehen können, wird unter Bezugnahme auf das darin enthaltene statische Feld eine zusätzliche statische Methode angezeigt. Alles ist traurig genug.
Was können wir tun, um sicherzustellen, dass es nicht statisch ist? Wir können versuchen, @JvmField und @JvmStatic hinzuzufügen und sehen, was passiert.
val i = k + work2() companion object { @JvmField var k = 5 JvmStatic fun work2() : Int = 42 }
Ich sage sofort, dass Sie @JvmStatic nicht verlassen, es wird dasselbe Objekt sein, da es sich um ein Begleitobjekt handelt, wird dieses Objekt zusätzlich zugewiesen und es wird zusätzlich aufgerufen.
private static int k = 5; public static final Test.Companion Companion = new Test.Companion((DefaultConstructorMarker)null); public static final class Companion { @JvmStatic public final int work2() { return 42; } private Companion() {}
Der Aufruf ändert sich jedoch nur für k, da er @JvmField ist und direkt als getstatic verwendet wird. Getter und Setter werden nicht mehr generiert. Aber für die Funktion work2 wird sich nichts ändern.
L2 LINENUMBER 21 L2 GETSTATIC kotlin/Test.k : I GETSTATIC kotlin/Test.Companion : Lkotlin/Test$Companion; INVOKEVIRTUAL kotlin/Test$Companion.work2 ()I IADD ISTORE 3
Die zweite Option zum Erstellen von Statik wird in der Kotlin-Dokumentation vorgeschlagen. Es wird also gesagt, dass wir nur ein Objekt erstellen können, und dies ist statischer Code.
object A { fun test() = 53 }
In Wirklichkeit ist dies auch nicht so.
L3 LINENUMBER 23 L3 GETSTATIC kotlin/A.INSTANCE : Lkotlin/A; INVOKEVIRTUAL kotlin/A.test ()I POP
Es stellt sich heraus, dass wir einen getstatic-Instanzaufruf von Singletone ausführen, der erstellt wird, und genau dieselben virtuellen Methoden aufrufen.
Der einzige Weg, wie wir invokestatisch erreichen können, sind Funktionen höherer Ordnung. Wenn wir zum Beispiel nur eine Funktion außerhalb der Klasse schreiben, wird fun test2 wirklich als statisch bezeichnet.
fun test2() = 99 L4 LINENUMBER 24 L4 INVOKESTATIC kotlin/TestKt.test2 ()I POP
Das Interessanteste ist außerdem, dass eine Klasse erstellt wird, ein Objekt, in diesem Fall testKt, ein Objekt für sich selbst generiert, eine Funktion generiert, die in dieses Objekt eingefügt wird, und jetzt als invokestatisch bezeichnet wird.
Warum dies getan wurde, ist unverständlich. Viele sind damit unzufrieden, aber es gibt diejenigen, die eine solche Implementierung für ganz normal halten. Da die virtuelle Maschine inkl. Kunst verbessert sich, jetzt ist sie nicht mehr so kritisch. In der achten Version von Android ist genau wie bei HotSpot alles optimiert, aber diese kleinen Dinge wirken sich immer noch geringfügig auf die Gesamtleistung aus.
NULLABILITÄT
fun test(first: String, second: String?) : String { second ?: return first return "$first $second" }
Dies ist das nächste interessante Beispiel. Es scheint, dass wir festgestellt haben, dass die Sekunde nullwertfähig sein kann und überprüft werden muss, bevor etwas damit unternommen wird. In diesem Fall erwarte ich, dass wir eine haben, wenn. Wenn dieser Code bereitgestellt wird, wenn second ungleich Null ist, wird die Ausführung meiner Meinung nach weiter gehen und nur zuerst ausgegeben.
Wie entfaltet sich das alles wirklich im Java-Code? Eigentlich wird es einen Scheck geben.
@NotNull public final String test(@NotNull String first,@Nullable String second) { Intrinsics.checkParameterIsNotNull(first, "first"); return second != null ? (first + " " + second) : first; }
Wir werden zunächst Intrinsics bekommen. Nehmen wir an, ich sage das hier
If wird zu einem ternären Operator erweitert. Abgesehen davon, obwohl wir sogar behoben haben, dass der erste Parameter nicht nullwertfähig sein kann, wird er dennoch über Intrinsics überprüft.
Intrinsics ist eine interne Klasse in Kotlin, die bestimmte Parameter und Prüfungen enthält. Und jedes Mal, wenn Sie den Methodenparameter nicht nullbar machen, wird er trotzdem überprüft. Warum? Dann, dass wir in Interop Java arbeiten, und es kann vorkommen, dass Sie erwarten, dass es hier nicht nullbar ist, aber mit Java wird es von irgendwoher kommen.
Wenn Sie dies überprüfen, wird es weiter entlang des Codes gehen, und dann werden Sie nach 10-20 Methodenaufrufen etwas mit einem Parameter tun, der zwar nicht nullwertfähig ist, sich aber aus irgendeinem Grund herausgestellt hat. Alles wird auf Sie hereinfallen und Sie werden nicht verstehen können, was tatsächlich passiert ist. Um diese Situation zu vermeiden, müssen Sie den Parameter null jedes Mal überprüfen, wenn Sie ihn übergeben. Und wenn es nullbar ist, gibt es eine Ausnahme.
Dieser Scheck ist auch etwas wert, und wenn es viele davon gibt, wird es nicht sehr gut sein.
Wenn wir jedoch über HotSpot sprechen, dauern 10 Aufrufe dieser Intrinsics ungefähr vier Nanosekunden. Dies ist sehr, sehr klein, und Sie sollten sich darüber keine Sorgen machen, aber dies ist ein interessanter Faktor.
PRIMITIVE
In Java gibt es so etwas wie Primitive. Wie wir alle wissen, gibt es in Kotlin keine Grundelemente, wir arbeiten immer mit Objekten. In Java werden sie verwendet, um Objekten bei einigen kleineren Berechnungen eine höhere Leistung zu bieten. Das Hinzufügen von zwei Objekten ist viel teurer als das Hinzufügen von zwei Grundelementen. Betrachten Sie ein Beispiel.
var a = 5 var b = 6 var bOption : Int? = 6
Es gibt drei Zahlen, für die die ersten beiden nicht Null-Typen abgeleitet werden, und für die dritte sagen wir, dass es nullbar sein kann.
private int a = 5; private int b = 6; @Nullable private Integer bOption = Integer.valueOf(6);
Wenn Sie sich den Bytecode ansehen und sehen, welcher Java-Code generiert wird, sind die ersten beiden Zahlen nicht null und können daher primitiv sein. Das Grundelement kann jedoch keine Null enthalten. Dies kann nur ein Objekt. Daher wird ein Objekt für die dritte Zahl generiert.
AUTOBOXING
Wenn Sie mit Grundelementen arbeiten und eine Operation mit einem Grundelement und einem Nicht-Grundelement ausführen, müssen Sie entweder eines davon in ein Grundelement oder in ein Objekt übersetzen.
Und es scheint nicht verwunderlich, dass Sie ein wenig an Leistung verlieren, wenn Sie Operationen mit nullable und nicht nullable in Kotlin ausführen. Wenn es viele solcher Operationen gibt, verlieren Sie außerdem viel.
val a: String? = null var b = a?.isBlank() == true
Sehen Sie, wo Boxen / Unboxen hier sein wird? Ich habe es auch nicht gesehen, bis ich mir den Bytecode angesehen habe.
if (a != null && a.isBlank()) true else false
Eigentlich habe ich erwartet, dass es so etwas wie diesen Vergleich geben würde: Wenn die Zeichenfolge nicht null ist und wenn sie leer ist, dann auf true setzen, andernfalls auf false setzen. Alles scheint einfach zu sein, aber in Wirklichkeit wird der folgende Code generiert:
String a = (String)null; boolean b = Intrinsics.areEqual(a != null ? Boolean.valueOf(StringsKt.isBlank((CharSequence)a)) : null, Boolean.valueOf(true));
Schauen wir mal rein. Die Variable
a wird genommen, sie wird in CharSequence umgewandelt, nachdem sie umgewandelt wurde, was auch für einige Zeit ausgegeben wurde, wird eine weitere Prüfung aufgerufen - StringsKt.isBlank - auf diese Weise wird die Erweiterungsfunktion für CharSequence geschrieben, also wird sie umgewandelt und gesendet. Da der erste Ausdruck nullwertfähig sein kann, nimmt er ihn und führt Boxing aus und umschließt alles in Boolean.valueOf. Daher wird das wahre Grundelement auch zu einem Objekt, und erst danach findet die Überprüfung bereits statt und Intrinsics.areEqual wird aufgerufen.
Es scheint eine so einfache Operation zu sein, aber ein so unerwartetes Ergebnis. Tatsächlich gibt es nur sehr wenige solche Dinge. Aber wenn Sie nullable / nicht nullable haben können, können Sie eine Menge solcher Dinge generieren, und eines, das Sie nie erwartet hätten. Daher empfehle ich Ihnen, sich so schnell wie möglich von der Dunkelheit zu entfernen. Das heißt,
Kommen Sie so früh wie möglich zur Immunität von Werten und entfernen Sie sich von nullable, damit Sie nicht so schnell wie möglich null arbeiten.
Schleifen
Das nächste interessante Ding.
Sie können das übliche für verwenden, das in Java verfügbar ist, aber Sie können auch die neue praktische API verwenden - schreiben Sie die Aufzählung der Elemente in der Liste sofort. , work, it - .
list.forEach { work(it * 2) }
. , . , Google, , ArrayList for 3 , . .
, ArrayList, — foreach.
inline fun <reified T> List<T>.foreach(crossinline action: (T) -> Unit): Unit { val size = size var i = 0 while (i < size) { action(get(i)) i++ } } list.foreach { }
API, - . , Kotlin: extension , «», reified, .. , , , crossinline. , , . 3 , Android Google.
RANGES
Ranges.
inline fun <reified T> List<T>.foreach(crossinline action: (T) -> Unit): Unit { val size = size for(i in 0..size) { work(i * 2) } }
: Unit -. −1, until , , . , , ranges. Das heißt, , . step. .
INTRINSICS
- Intrinsics, :
class Test { fun concat(first: String, second: String) = "$first $second" }
Intrinsics — second, first.
public final class Test { @NotNull public final String concat(@NotNull String first, @NotNull String second) { Intrinsics.checkParameterIsNotNull(first, "first"); Intrinsics.checkParameterIsNotNull(second, "second"); return first + " " + second; } }
, gradle. , - 4 , . Kotlin UI, , nullable, Kotlin :
kotlinc -Xno-call-assertions -Xno-param-assertions Test.ktIntrinsics, , .
, , . — Xno-param-assertions — Intrinsics, .
, , , , , . , , , .
REDEX
, , , Proguard. , 99% , , . Android 8.0 , . , .
, Proguard, Facebook,
Redex . -, , . , Jvm Fields , .
, Redex . , , , Proguard, , . Redex 7% APK. , .
BENCHMARKS
. , , .
, . , dumpsys gfxinfo , . github
github.com/smred .
, Huawei.

. — , . , , 0,04 . , , — , .

Kotlin, . , , . - , Kotlin , Java. , , , , . .

, , , , Kotlin Java. , - , , , , , .

, : - Kotlin , .. . , . - - — 2 , Galaxy S6, .

Google Pixel. , 0,1 .
SCHLUSSFOLGERUNGEN
, , ,
- UI custom view.
- onmeasure-onlayout-ondraw. autoboxing, not null ..
- Kotlin, Java , .
- — .
, , . , , , , Kotlin, . , Kotlin .
, .
brand new AppsConf , Android . , . , 8 9 .