In diesem Codelab erfahren Sie, wie Sie Ihren Code von Java zu Kotlin konvertieren. Außerdem erfahren Sie, welche Konventionen für die Kotlin-Sprache gelten und wie Sie sicherstellen, dass der von Ihnen geschriebene Code diesen Konventionen entspricht.
Dieses Codelab eignet sich für alle Entwickler, die Java verwenden und ihr Projekt zu Kotlin migrieren möchten. Wir beginnen mit einigen Java-Klassen, die Sie mit der IDE in Kotlin konvertieren. Anschließend sehen wir uns den konvertierten Code an und überlegen, wie wir ihn verbessern können, indem wir ihn idiomatischer gestalten und häufige Fehler vermeiden.
Lerninhalte
Sie lernen, wie Sie Java in Kotlin umwandeln. Dabei lernen Sie die folgenden Kotlin-Sprachfunktionen und ‑Konzepte kennen:
- Null-Zulässigkeit verarbeiten
- Singletons implementieren
- Datenklassen
- Umgang mit Strings
- Elvis-Operator
- Destrukturierung
- Properties und Backing-Properties
- Standardargumente und benannte Parameter
- Mit Sammlungen arbeiten
- Erweiterungsfunktionen
- Funktionen und Parameter der obersten Ebene
let-,apply-,with- undrun-Keywords
Annahmen
Sie sollten bereits mit Java vertraut sein.
Voraussetzungen
Neues Projekt erstellen
Wenn Sie IntelliJ IDEA verwenden, erstellen Sie ein neues Java-Projekt mit Kotlin/JVM.
Wenn Sie Android Studio verwenden, erstellen Sie ein neues Projekt ohne Aktivität.
Der Code
Wir erstellen ein User-Modellobjekt und eine Repository-Singleton-Klasse, die mit User-Objekten funktioniert und Listen von Nutzern und formatierten Nutzernamen bereitstellt.
Erstellen Sie unter „app/java/<yourpackagename>“ eine neue Datei mit dem Namen User.java und fügen Sie den folgenden Code ein:
public class User {
@Nullable
private String firstName;
@Nullable
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}Importieren Sie je nach Projekttyp androidx.annotation.Nullable, wenn Sie ein Android-Projekt verwenden, oder org.jetbrains.annotations.Nullable.
Erstellen Sie eine neue Datei mit dem Namen Repository.java und fügen Sie den folgenden Code ein:
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}Unsere IDE kann Java-Code recht gut automatisch in Kotlin-Code refaktorieren, aber manchmal ist etwas Hilfe erforderlich. Wir werden das zuerst tun und dann den refaktorisierten Code durchgehen, um zu verstehen, wie und warum er so refaktorisiert wurde.
Rufen Sie die Datei User.java auf und konvertieren Sie sie in Kotlin: Menüleiste > Code > Convert Java File to Kotlin File (Java-Datei in Kotlin-Datei konvertieren).
Wenn Ihre IDE nach der Konvertierung eine Korrektur vorschlägt, drücken Sie Ja.

Der folgende Kotlin-Code sollte angezeigt werden:
class User(var firstName: String?, var lastName: String?)User.java wurde in User.kt umbenannt. Kotlin-Dateien haben die Endung „.kt“.
In unserer Java-Klasse User hatten wir zwei Attribute: firstName und lastName. Jede hatte eine Getter- und eine Setter-Methode, wodurch ihr Wert geändert werden konnte. Das Kotlin-Schlüsselwort für veränderliche Variablen ist var. Der Konverter verwendet daher var für jede dieser Eigenschaften. Wenn unsere Java-Properties nur Getter hätten, wären sie unveränderlich und als val-Variablen deklariert worden. val ähnelt dem Keyword final in Java.
Einer der Hauptunterschiede zwischen Kotlin und Java besteht darin, dass in Kotlin explizit angegeben wird, ob eine Variable einen Nullwert akzeptieren kann. Dazu wird der Typdeklaration ein `?` angehängt.
Da wir firstName und lastName als „nullable“ markiert haben, hat der automatische Konverter die Properties automatisch mit String? als „nullable“ markiert. Wenn Sie Ihre Java-Member mit „non-null“ annotieren (mit org.jetbrains.annotations.NotNull oder androidx.annotation.NonNull), erkennt der Konverter dies und macht die Felder auch in Kotlin zu „non-null“.
Die grundlegende Umgestaltung ist bereits erfolgt. Wir können das aber auch idiomatisch ausdrücken. Aber was bedeutet das eigentlich genau?
Datenklasse
Unsere User-Klasse enthält nur Daten. In Kotlin gibt es ein Keyword für Klassen mit dieser Rolle: data. Wenn wir diese Klasse als data-Klasse kennzeichnen, erstellt der Compiler automatisch Getter und Setter für uns. Außerdem werden die Funktionen equals(), hashCode() und toString() abgeleitet.
Fügen wir der Klasse User das Schlüsselwort data hinzu:
data class User(var firstName: String, var lastName: String)In Kotlin kann es wie in Java einen primären Konstruktor und einen oder mehrere sekundäre Konstruktoren geben. Der im Beispiel oben ist der primäre Konstruktor der Klasse „User“. Wenn Sie eine Java-Klasse mit mehreren Konstruktoren konvertieren, erstellt der Konverter automatisch auch mehrere Konstruktoren in Kotlin. Sie werden mit dem Keyword constructor definiert.
Wenn wir eine Instanz dieser Klasse erstellen möchten, können wir das so tun:
val user1 = User("Jane", "Doe")Equality
In Kotlin gibt es zwei Arten von Gleichheit:
- Bei der strukturellen Gleichheit wird der Operator
==verwendet undequals()aufgerufen, um festzustellen, ob zwei Instanzen gleich sind. - Bei der referenziellen Gleichheit wird der Operator
===verwendet, um zu prüfen, ob zwei Referenzen auf dasselbe Objekt verweisen.
Die im primären Konstruktor der Datenklasse definierten Properties werden für strukturelle Gleichheitsprüfungen verwendet.
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // falseIn Kotlin können wir Argumenten in Funktionsaufrufen Standardwerte zuweisen. Der Standardwert wird verwendet, wenn das Argument weggelassen wird. In Kotlin sind Konstruktoren auch Funktionen. Wir können also Standardargumente verwenden, um anzugeben, dass der Standardwert von lastName null ist. Dazu weisen wir null einfach lastName zu.
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")Funktionsparameter können beim Aufrufen von Funktionen benannt werden:
val john = User(firstName = "John", lastName = "Doe") Nehmen wir als anderes Beispiel an, dass firstName den Standardwert null hat und lastName nicht. Da der Standardparameter einem Parameter ohne Standardwert vorangestellt wäre, müssten Sie die Funktion mit benannten Argumenten aufrufen:
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")Bevor Sie fortfahren, prüfen Sie, ob Ihre User-Klasse eine data-Klasse ist. Wandeln wir die Klasse Repository in Kotlin um. Das Ergebnis der automatischen Konvertierung sollte so aussehen:
import java.util.*
class Repository private constructor() {
private var users: MutableList<User?>? = null
fun getUsers(): List<User?>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}Sehen wir uns an, was der automatische Konverter gemacht hat:
- Ein
init-Block wurde hinzugefügt (Repository.kt#L50). - Das Feld
staticist jetzt Teil des Blockscompanion object(Repository.kt#L33). - Die Liste von
usersist nullfähig, da das Objekt bei der Deklaration nicht instanziiert wurde (Repository.kt#L7). - Die Methode
getFormattedUserNames()ist jetzt eine Eigenschaft namensformattedUserNames(Repository.kt#L11). - Die Iteration über die Liste der Nutzer (die ursprünglich Teil von
getFormattedUserNames(war) hat eine andere Syntax als die Java-Syntax (Repository.kt#L15).
Bevor wir fortfahren, bereinigen wir den Code ein wenig. Wir sehen, dass der Konverter unsere users-Liste in eine veränderliche Liste mit nullable-Objekten umgewandelt hat. Die Liste kann zwar null sein, aber sie kann keine Null-Nutzer enthalten. Gehen wir also so vor:
- Entfernen Sie
?inUser?innerhalb der Typdeklarationusers. getUserssollteList<User>?zurückgeben.
Außerdem wurden die Variablendeklarationen der Nutzervariablen und der im Init-Block definierten Variablen unnötigerweise in zwei Zeilen aufgeteilt. Wir setzen jede Variablendeklaration in eine eigene Zeile. So sollte unser Code aussehen:
class Repository private constructor() {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}Init-Block
In Kotlin darf der primäre Konstruktor keinen Code enthalten. Daher wird Initialisierungscode in init-Blöcke eingefügt. Die Funktionalität ist dieselbe.
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}Ein Großteil des init-Codes dient zum Initialisieren von Attributen. Dies kann auch in der Deklaration der Property erfolgen. In der Kotlin-Version unserer Repository-Klasse sehen wir beispielsweise, dass die „users“-Eigenschaft in der Deklaration initialisiert wurde.
private var users: MutableList<User>? = nullstatic -Attribute und -Methoden von Kotlin
In Java verwenden wir das Keyword static für Felder oder Funktionen, um anzugeben, dass sie zu einer Klasse, aber nicht zu einer Instanz der Klasse gehören. Aus diesem Grund haben wir das statische Feld INSTANCE in unserer Klasse Repository erstellt. Das Kotlin-Äquivalent hierfür ist der companion object-Block. Hier deklarieren Sie auch die statischen Felder und statischen Funktionen. Der Konverter hat das Feld INSTANCE erstellt und hierher verschoben.
Singletons verarbeiten
Da wir nur eine Instanz der Klasse Repository benötigen, haben wir in Java das Singleton-Muster verwendet. In Kotlin können Sie dieses Muster auf Compilerebene erzwingen, indem Sie das Schlüsselwort class durch object ersetzen.
Entfernen Sie den privaten Konstruktor und das Companion-Objekt und ersetzen Sie die Klassendefinition durch object Repository.
object Repository {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String>
get() {
val userNames: MutableList<String> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}Wenn Sie die Klasse object verwenden, rufen Sie Funktionen und Eigenschaften einfach direkt für das Objekt auf:
val users = Repository.usersDestrukturierung
In Kotlin kann ein Objekt mithilfe einer Syntax, die als destrukturierende Deklaration bezeichnet wird, in eine Reihe von Variablen zerlegt werden. Wir erstellen mehrere Variablen, die unabhängig voneinander verwendet werden können.
Datenklassen unterstützen beispielsweise die Destrukturierung. Daher können wir das User-Objekt in der for-Schleife in (firstName, lastName) zerlegen. So können wir direkt mit den Werten firstName und lastName arbeiten. Aktualisieren wir die for-Schleife so:
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
Beim Konvertieren der Klasse Repository in Kotlin hat der automatische Konverter die Liste der Nutzer als nullable festgelegt, da sie bei der Deklaration nicht mit einem Objekt initialisiert wurde. Für alle Verwendungen des users-Objekts wird der Not-Null-Assertionsoperator !! verwendet. Sie konvertiert jede Variable in einen Typ, der nicht null ist, und löst eine Ausnahme aus, wenn der Wert null ist. Wenn Sie !! verwenden, riskieren Sie, dass zur Laufzeit Ausnahmen ausgelöst werden.
Verwenden Sie stattdessen eine der folgenden Methoden, um Nullwerte zu verarbeiten:
- NULL-Prüfung durchführen (
if (users != null) {...}) - Elvis-Operator
?:verwenden (wird später im Codelab behandelt) - Einige der Kotlin-Standardfunktionen verwenden (werden später im Codelab behandelt)
In unserem Fall wissen wir, dass die Liste der Nutzer nicht nullfähig sein muss, da sie direkt nach der Erstellung des Objekts initialisiert wird. Wir können das Objekt also direkt bei der Deklaration instanziieren.
Beim Erstellen von Instanzen von Sammlungstypen bietet Kotlin mehrere Hilfsfunktionen, um Ihren Code lesbarer und flexibler zu machen. Hier verwenden wir ein MutableList für users:
private var users: MutableList<User>? = nullZur Vereinfachung können wir die Funktion mutableListOf() verwenden, den Listenelementtyp angeben, den ArrayList-Konstruktoraufruf aus dem init-Block entfernen und die explizite Typdeklaration der users-Eigenschaft entfernen.
private val users = mutableListOf<User>()Außerdem haben wir „var“ in „val“ geändert, da „users“ eine unveränderliche Referenz auf die Liste der Nutzer enthält. Die Referenz ist unveränderlich, die Liste selbst jedoch veränderlich (Sie können Elemente hinzufügen oder entfernen).
Durch diese Änderungen ist die users-Property jetzt nicht mehr null und wir können alle unnötigen Vorkommen des !!-Operators entfernen.
val userNames: MutableList<String?> = ArrayList(users.size)for ((firstName, lastName) in users) {
...
}Da die Variable „users“ bereits initialisiert ist, müssen wir die Initialisierung aus dem init-Block entfernen:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}Da sowohl lastName als auch firstName null sein können, müssen wir die Nullable-Eigenschaft berücksichtigen, wenn wir die Liste der formatierten Nutzernamen erstellen. Da wir "Unknown" anzeigen möchten, wenn einer der beiden Namen fehlt, können wir den Namen nicht null machen, indem wir ? aus der Typdeklaration entfernen.
val name: StringWenn lastName null ist, ist name entweder firstName oder "Unknown":
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}Das lässt sich idiomatisch mit dem Elvis-Operator ?: schreiben. Der Elvis-Operator gibt den Ausdruck auf der linken Seite zurück, wenn er nicht null ist, oder den Ausdruck auf der rechten Seite, wenn die linke Seite null ist.
Im folgenden Code wird also user.firstName zurückgegeben, wenn es nicht null ist. Wenn user.firstName null ist, gibt der Ausdruck den Wert auf der rechten Seite zurück , "Unknown":
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}Mit Kotlin ist die Arbeit mit Strings dank String-Vorlagen ganz einfach. Mit Stringvorlagen können Sie in Stringdeklarationen auf Variablen verweisen.
Der automatische Konverter hat die Verkettung von Vor- und Nachname so aktualisiert, dass direkt im String auf den Variablennamen verwiesen wird. Dazu wurde das Symbol $ verwendet und der Ausdruck zwischen { } gesetzt.
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"Ersetzen Sie im Code die Stringverkettung durch Folgendes:
name = "$firstName $lastName"In Kotlin sind if, when, for und while Ausdrücke, die einen Wert zurückgeben. Ihre IDE zeigt sogar eine Warnung an, dass die Zuweisung aus if herausgenommen werden sollte:

Folgen wir dem Vorschlag der IDE und heben wir die Zuweisung für beide if-Anweisungen auf. Die letzte Zeile der IF-Anweisung wird zugewiesen. So ist deutlicher zu erkennen, dass dieser Block nur dazu dient, den Namenswert zu initialisieren:
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}Als Nächstes erhalten wir eine Warnung, dass die name-Deklaration mit der Zuweisung zusammengefasst werden kann. Wenden wir das auch an. Da der Typ der Variablen „name“ abgeleitet werden kann, können wir die explizite Typdeklaration entfernen. Unsere formattedUserNames sieht jetzt so aus:
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}Sehen wir uns den formattedUserNames-Getter genauer an und überlegen, wie wir ihn idiomatisch gestalten können. Der Code führt derzeit Folgendes aus:
- Erstellt eine neue Liste mit Strings.
- Die Liste der Nutzer wird durchlaufen.
- Erstellt den formatierten Namen für jeden Nutzer basierend auf dem Vor- und Nachnamen des Nutzers.
- Gibt die neu erstellte Liste zurück.
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}Kotlin bietet eine umfangreiche Liste von Sammlungstransformationen, die die Entwicklung durch die Erweiterung der Funktionen der Java Collections API schneller und sicherer machen. Eine davon ist die Funktion map. Diese Funktion gibt eine neue Liste zurück, die die Ergebnisse der Anwendung der angegebenen Transformationsfunktion auf jedes Element in der ursprünglichen Liste enthält. Anstatt eine neue Liste zu erstellen und die Liste der Nutzer manuell zu durchlaufen, können wir die Funktion map verwenden und die Logik, die wir in der for-Schleife hatten, in den map-Body verschieben. Standardmäßig ist der Name des aktuellen Listenelements, das in map verwendet wird, it. Zur besseren Lesbarkeit können Sie it jedoch durch einen eigenen Variablennamen ersetzen. In unserem Fall nennen wir sie user:
val formattedUserNames: List<String>
get() {
return users.map { user ->
val name = if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
name
}
}Um das Ganze noch weiter zu vereinfachen, können wir die Variable name vollständig entfernen:
val formattedUserNames: List<String>
get() {
return users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}Wir haben gesehen, dass der automatische Konverter die Funktion getFormattedUserNames() durch eine Eigenschaft namens formattedUserNames mit einem benutzerdefinierten Getter ersetzt hat. Intern generiert Kotlin weiterhin eine getFormattedUserNames()-Methode, die eine List zurückgibt.
In Java würden wir unsere Klassenattribute über Getter- und Setter-Funktionen verfügbar machen. Mit Kotlin können wir besser zwischen Eigenschaften einer Klasse, die mit Feldern ausgedrückt werden, und Funktionen, Aktionen, die eine Klasse ausführen kann, die mit Funktionen ausgedrückt werden, unterscheiden. In unserem Fall ist die Klasse Repository sehr einfach und führt keine Aktionen aus. Sie enthält also nur Felder.
Die Logik, die in der Java-Funktion getFormattedUserNames() ausgelöst wurde, wird jetzt beim Aufrufen des Getters der Kotlin-Property formattedUserNames ausgelöst.
Es gibt zwar kein Feld, das der Eigenschaft formattedUserNames entspricht, aber Kotlin bietet uns ein automatisches Backing-Feld namens field , auf das wir bei Bedarf über benutzerdefinierte Getter und Setter zugreifen können.
Manchmal benötigen wir jedoch zusätzliche Funktionen, die das automatische Backing-Feld nicht bietet. Sehen wir uns ein Beispiel an.
In unserer Repository-Klasse haben wir eine veränderliche Liste von Nutzern, die in der Funktion getUsers() verfügbar gemacht wird, die aus unserem Java-Code generiert wurde:
fun getUsers(): List<User>? {
return users
}Das Problem hierbei ist, dass jeder, der die Repository-Klasse verwendet, unsere Liste von Nutzern ändern kann – keine gute Idee!users Wir beheben das Problem, indem wir eine Backing-Property verwenden.
Benennen wir zuerst users in _users um. Fügen Sie nun eine öffentliche unveränderliche Property hinzu, die eine Liste von Nutzern zurückgibt. Nennen wir sie users:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _usersDurch diese Änderung wird die private Property _users zur Backing Property für die öffentliche Property users. Außerhalb der Klasse Repository ist die Liste _users nicht änderbar, da Nutzer der Klasse nur über users auf die Liste zugreifen können.
Vollständiger Code:
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}Derzeit weiß die Klasse Repository, wie der formatierte Nutzername für ein User-Objekt berechnet wird. Wenn wir dieselbe Formatierungslogik in anderen Klassen wiederverwenden möchten, müssen wir sie entweder kopieren und einfügen oder in die Klasse User verschieben.
In Kotlin können Funktionen und Eigenschaften außerhalb von Klassen, Objekten oder Schnittstellen deklariert werden. Die Funktion mutableListOf(), die wir zum Erstellen einer neuen Instanz von List verwendet haben, ist beispielsweise direkt in Collections.kt aus der Standardbibliothek definiert.
In Java erstellen Sie für jede benötigte Hilfsfunktion höchstwahrscheinlich eine Util-Klasse und deklarieren die Funktion als statische Funktion. In Kotlin können Sie Funktionen der obersten Ebene deklarieren, ohne eine Klasse zu haben. Kotlin bietet jedoch auch die Möglichkeit, Erweiterungsfunktionen zu erstellen. Dies sind Funktionen, die einen bestimmten Typ erweitern, aber außerhalb des Typs deklariert werden. Daher haben sie eine Affinität zu diesem Typ.
Die Sichtbarkeit von Erweiterungsfunktionen und ‑attributen kann mithilfe von Sichtbarkeitsmodifizierern eingeschränkt werden. Dadurch wird die Verwendung auf Klassen beschränkt, die die Erweiterungen benötigen, und der Namespace wird nicht überladen.
Für die Klasse User können wir entweder eine Erweiterungsfunktion hinzufügen, die den formatierten Namen berechnet, oder den formatierten Namen in einer Erweiterungseigenschaft speichern. Sie kann außerhalb der Repository-Klasse in derselben Datei hinzugefügt werden:
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedNameWir können die Erweiterungsfunktionen und ‑attribute dann so verwenden, als wären sie Teil der Klasse User.
Da der formatierte Name eine Eigenschaft des Nutzers und keine Funktion der Klasse Repository ist, verwenden wir die Erweiterungseigenschaft. Unsere Repository-Datei sieht jetzt so aus:
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}Die Kotlin-Standardbibliothek verwendet Erweiterungsfunktionen, um die Funktionalität verschiedener Java-APIs zu erweitern. Viele Funktionen von Iterable und Collection werden als Erweiterungsfunktionen implementiert. Die Funktion map, die wir in einem vorherigen Schritt verwendet haben, ist beispielsweise eine Erweiterungsfunktion für Iterable.
Im Code der Klasse Repository fügen wir der Liste _users mehrere Nutzerobjekte hinzu. Diese Aufrufe können mithilfe von Bereichsfunktionen idiomatisch gestaltet werden.
Damit Code nur im Kontext eines bestimmten Objekts ausgeführt werden kann, ohne dass auf das Objekt anhand seines Namens zugegriffen werden muss, hat Kotlin fünf Bereichsfunktionen erstellt: let, apply, with, run und also. Diese Funktionen sind kurz und leistungsstark. Sie haben alle einen Empfänger (this), möglicherweise ein Argument (it) und geben möglicherweise einen Wert zurück. Welche Sie verwenden, hängt davon ab, was Sie erreichen möchten.
Hier finden Sie eine praktische Übersicht:

Da wir unser _users-Objekt in unserem Repository konfigurieren, können wir den Code mit der apply-Funktion idiomatisch gestalten:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.apply {
// this == _users
add(user1)
add(user2)
add(user3)
}
}In diesem Codelab haben wir die Grundlagen behandelt, die Sie benötigen, um Ihren Code von Java zu Kotlin zu refaktorieren. Dieses Refactoring ist unabhängig von Ihrer Entwicklungsplattform und trägt dazu bei, dass der von Ihnen geschriebene Code idiomatisch ist.
Idiomatisches Kotlin ermöglicht es, Code kurz und prägnant zu schreiben. Mit all den Funktionen, die Kotlin bietet, gibt es viele Möglichkeiten, Ihren Code sicherer, prägnanter und lesbarer zu machen. Wir können beispielsweise unsere Repository-Klasse optimieren, indem wir die _users-Liste mit Nutzern direkt in der Deklaration instanziieren und den init-Block entfernen:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))Wir haben eine Vielzahl von Themen behandelt, von der Verarbeitung von Nullable-Typen, Singletons, Strings und Sammlungen bis hin zu Themen wie Erweiterungsfunktionen, Funktionen der obersten Ebene, Eigenschaften und Bereichsfunktionen. Wir haben zwei Java-Klassen durch zwei Kotlin-Klassen ersetzt, die jetzt so aussehen:
User.kt
class User(var firstName: String?, var lastName: String?)Repository.kt
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}Hier ist eine Zusammenfassung der Java-Funktionen und ihrer Zuordnung zu Kotlin:
Java | Kotlin |
|
|
|
|
|
|
Klasse, die nur Daten enthält | Klasse |
Initialisierung im Konstruktor | Initialisierung im Block |
| Felder und Funktionen, die in einem |
Singleton-Klasse |
|
Weitere Informationen zu Kotlin und zur Verwendung auf Ihrer Plattform finden Sie in den folgenden Ressourcen:
- Kotlin Koans
- Kotlin-Tutorials
- Android-Apps mit Kotlin entwickeln – kostenloser Kurs
- Kotlin-Bootcamp für Programmierer
- Kotlin für Java-Entwickler – kostenloser Kurs im Prüfmodus