1. Ti diamo il benvenuto.
In questo codelab imparerai a convertire il codice da Java a Kotlin. Scoprirai inoltre quali sono le convenzioni del linguaggio Kotlin e come assicurarti che il codice che stai scrivendo le segua.
Questo codelab è adatto a qualsiasi sviluppatore che utilizza Java e sta valutando la possibilità di eseguire la migrazione del proprio progetto a Kotlin. Inizieremo con un paio di classi Java che convertirai in Kotlin utilizzando l'IDE. Poi daremo un'occhiata al codice convertito e vedremo come possiamo migliorarlo rendendolo più idiomatico ed evitando gli errori comuni.
Cosa imparerai
Imparerai a convertire Java in Kotlin. In questo modo imparerai i seguenti concetti e funzionalità del linguaggio Kotlin:
- Gestione della nullità
- Implementare i singleton
- Classi di dati
- Gestione delle stringhe
- Operatore Elvis
- Destrutturazione
- Proprietà e proprietà di supporto
- Argomenti predefiniti e parametri denominati
- Utilizzo delle raccolte
- Funzioni di estensione
- Funzioni e parametri di primo livello
- Parole chiave
let
,apply
,with
erun
Premesse
Dovresti già conoscere Java.
Che cosa ti serve
2. Preparazione
Creare un nuovo progetto
Se utilizzi IntelliJ IDEA, crea un nuovo progetto Java con Kotlin/JVM.
Se utilizzi Android Studio, crea un nuovo progetto con il modello Nessuna attività. Scegli Kotlin come lingua del progetto. L'SDK minimo può avere qualsiasi valore e non influisce sul risultato.
Il codice
Creeremo un oggetto modello User
e una classe singleton User
che funzioni con gli oggetti User
ed esponga elenchi di utenti e nomi utente formattati.Repository
Crea un nuovo file denominato User.java
in app/java/<yourpackagename> e incolla il seguente codice:
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;
}
}
Noterai che l'IDE ti dice che @Nullable
non è definito. Importa androidx.annotation.Nullable
se utilizzi Android Studio o org.jetbrains.annotations.Nullable
se utilizzi IntelliJ.
Crea un nuovo file denominato Repository.java
e incolla il seguente codice:
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;
}
}
3. Dichiarazione di nullità, val, var e classi di dati
Il nostro IDE può convertire automaticamente il codice Java in codice Kotlin, ma a volte ha bisogno di un piccolo aiuto. Lasciamo che l'IDE esegua un primo passaggio di conversione. Poi esamineremo il codice risultante per capire come e perché è stato convertito in questo modo.
Vai al file User.java
e convertilo in Kotlin: barra dei menu -> Codice -> Converti file Java in file Kotlin.
Se l'IDE richiede una correzione dopo la conversione, premi Sì.
Dovresti vedere il seguente codice Kotlin:
class User(var firstName: String?, var lastName: String?)
Tieni presente che User.java
è stato rinominato in User.kt
. I file Kotlin hanno l'estensione .kt.
Nella nostra classe User
Java avevamo due proprietà: firstName
e lastName
. Ognuno aveva un metodo getter e setter, che rendeva il relativo valore mutabile. La parola chiave di Kotlin per le variabili mutabili è var
, pertanto il convertitore utilizza var
per ciascuna di queste proprietà. Se le nostre proprietà Java avessero solo getter, sarebbero di sola lettura e sarebbero state dichiarate come variabili val
. val
è simile alla parola chiave final
in Java.
Una delle differenze principali tra Kotlin e Java è che Kotlin specifica esplicitamente se una variabile può accettare un valore null. Aggiungendo un ?
alla dichiarazione di tipo.
Poiché abbiamo contrassegnato firstName
e lastName
come nullable, il convertitore automatico ha contrassegnato automaticamente le proprietà come nullable con String?
. Se annoti i membri Java come non null (utilizzando org.jetbrains.annotations.NotNull
o androidx.annotation.NonNull
), il convertitore lo riconoscerà e renderà i campi non null anche in Kotlin.
La conversione di base è già stata eseguita. Ma possiamo scriverlo in modo più idiomatico. Vediamo come.
Classe di dati
La nostra classe User
contiene solo dati. Kotlin ha una parola chiave per le classi con questo ruolo: data
. Se contrassegni questa classe come classe data
, il compilatore creerà automaticamente i getter e i setters. Inoltre, verranno derivate le funzioni equals()
, hashCode()
e toString()
.
Aggiungiamo la parola chiave data
al nostro corso User
:
data class User(var firstName: String?, var lastName: String?)
Kotlin, come Java, può avere un costruttore principale e uno o più costruttori secondari. Quello nell'esempio precedente è il costruttore principale della classe User
. Se stai convertendo una classe Java con più costruttori, il convertitore ne creerà automaticamente anche in Kotlin. Vengono definiti utilizzando la parola chiave constructor
.
Se vogliamo creare un'istanza di questa classe, possiamo farlo nel seguente modo:
val user1 = User("Jane", "Doe")
Uguaglianza
Kotlin ha due tipi di uguaglianza:
- L'uguaglianza strutturale utilizza l'operatore
==
e chiamaequals()
per determinare se due istanze sono uguali. - L'uguaglianza di riferimento utilizza l'operatore
===
e controlla se due riferimenti rimandano allo stesso oggetto.
Le proprietà definite nel costruttore principale della classe di dati verranno utilizzate per i controlli di uguaglianza strutturale.
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false
4. Argomenti predefiniti, argomenti con nome
In Kotlin, possiamo assegnare valori predefiniti agli argomenti nelle chiamate di funzioni. Il valore predefinito viene utilizzato quando l'argomento viene omesso. In Kotlin, i costruttori sono anche funzioni, quindi possiamo utilizzare gli argomenti predefiniti per specificare che il valore predefinito di lastName
è null
. Per farlo, basta assegnare null
a lastName
.
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("Joe", "Doe")
Kotlin ti consente di etichettare gli argomenti quando le funzioni vengono chiamate:
val john = User(firstName = "John", lastName = "Doe")
Come caso d'uso diverso, supponiamo che firstName
abbia null
come valore predefinito e lastName
no. In questo caso, poiché il parametro predefinito precede un parametro senza valore predefinito, devi chiamare la funzione con argomenti denominati:
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")
I valori predefiniti sono un concetto importante e spesso utilizzato nel codice Kotlin. Nel nostro codelab vogliamo specificare sempre il nome e il cognome in una dichiarazione di oggetto User
, quindi non abbiamo bisogno di valori predefiniti.
5. Inizializzazione degli oggetti, oggetto companion e oggetti singleton
Prima di continuare il codelab, assicurati che la classe User
sia una classe data
. Ora convertiamo la classe Repository
in Kotlin. Il risultato della conversione automatica dovrebbe essere simile al seguente:
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)
}
}
Vediamo cosa ha fatto il convertitore automatico:
- L'elenco di
users
è nullable perché l'oggetto non è stato istanziato al momento della dichiarazione - Le funzioni in Kotlin come
getUsers()
vengono dichiarate con il modificatorefun
- Il metodo
getFormattedUserNames()
ora è una proprietà denominataformattedUserNames
- L'iterazione sull'elenco di utenti (che inizialmente faceva parte di
getFormattedUserNames(
) ha una sintassi diversa da quella di Java - Il campo
static
ora fa parte di un bloccocompanion object
- È stato aggiunto un blocco
init
Prima di procedere, riordinamo un po' il codice. Se guardiamo nel costruttore, notiamo che il convertitore ha reso il nostro elenco users
un elenco mutabile che contiene oggetti nullable. Sebbene l'elenco possa essere null, supponiamo che non possa contenere utenti null. Quindi, procedi nel seguente modo:
- Rimuovi
?
inUser?
all'interno della dichiarazione di tipousers
- Rimuovi
?
inUser?
per il tipo di reso digetUsers()
in modo che restituiscaList<User>?
Blocco di inizializzazione
In Kotlin, il costruttore principale non può contenere alcun codice, pertanto il codice di inizializzazione viene inserito in blocchi init
. La funzionalità è la stessa.
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)
}
}
Gran parte del codice init
gestisce le proprietà di inizializzazione. Questa operazione può essere eseguita anche nella dichiarazione della proprietà. Ad esempio, nella versione Kotlin della nostra classe Repository
, vediamo che la proprietà users è stata inizializzata nella dichiarazione.
private var users: MutableList<User>? = null
Proprietà e metodi di static
di Kotlin
In Java, utilizziamo la parola chiave static
per i campi o le funzioni per indicare che appartengono a una classe, ma non a un'istanza della classe. Per questo motivo abbiamo creato il campo statico INSTANCE
nella classe Repository
. L'equivalente in Kotlin è il blocco companion object
. Qui devi dichiarare anche i campi e le funzioni statiche. Il convertitore ha creato il blocco dell'oggetto companion e spostato il campo INSTANCE
qui.
Gestione degli oggetti singoli
Poiché abbiamo bisogno di una sola istanza della classe Repository
, abbiamo utilizzato il pattern singleton in Java. Con Kotlin, puoi applicare questo pattern a livello di compilatore sostituendo la parola chiave class
con object
.
Rimuovi il costruttore privato e sostituisci la definizione della classe con object Repository
. Rimuovi anche l'oggetto companion.
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)
}
}
Quando utilizziamo la classe object
, basta chiamare funzioni e proprietà direttamente sull'oggetto, ad esempio:
val formattedUserNames = Repository.formattedUserNames
Tieni presente che se una proprietà non ha un modificatore di visibilità, è pubblica per impostazione predefinita, come nel caso della proprietà formattedUserNames
nell'oggetto Repository
.
6. Gestione della nullità
Durante la conversione della classe Repository
in Kotlin, il convertitore automatico ha reso l'elenco di utenti nullable, perché non è stato inizializzato a un oggetto al momento della dichiarazione. Di conseguenza, per tutti gli utilizzi dell'oggetto users
, è necessario utilizzare l'operatore di affermazione non nullo !!
. Vedrai users!!
e user!!
nel codice convertito. L'operatore !!
converte qualsiasi variabile in un tipo non nullo, in modo da poter accedere alle proprietà o chiamare funzioni. Tuttavia, verrà lanciata un'eccezione se il valore della variabile è effettivamente null. Se utilizzi !!
, rischi che vengano lanciate eccezioni in fase di esecuzione.
Preferisci gestire la nullabilità utilizzando uno di questi metodi:
- Eseguire un controllo NULL (
if (users != null) {...}
) - Utilizzo dell'operatore elvis
?:
(trattato più avanti nel codelab) - Utilizzo di alcune delle funzioni standard di Kotlin (trattate più avanti nel codelab)
Nel nostro caso, sappiamo che l'elenco di utenti non deve essere nullable, poiché viene inizializzato subito dopo la costruzione dell'oggetto (nel blocco init
). Pertanto, possiamo creare direttamente un'istanza dell'oggetto users
quando lo dichiariamo.
Quando crei istanze di tipi di raccolte, Kotlin fornisce diverse funzioni di assistenza per rendere il codice più leggibile e flessibile. Qui utilizziamo un MutableList
per users
:
private var users: MutableList<User>? = null
Per semplicità, possiamo utilizzare la funzione mutableListOf()
e fornire il tipo di elemento dell'elenco. mutableListOf<User>()
crea un elenco vuoto che può contenere oggetti User
. Poiché il tipo di dati della variabile ora può essere dedotto dal compilatore, rimuovi la dichiarazione di tipo esplicita della proprietà users
.
private val users = mutableListOf<User>()
Abbiamo anche modificato var
in val
perché gli utenti conterranno un riferimento di sola lettura all'elenco di utenti. Tieni presente che il riferimento è di sola lettura, quindi non può mai puntare a un nuovo elenco, ma l'elenco stesso è ancora modificabile (puoi aggiungere o rimuovere elementi).
Poiché la variabile users
è già inizializzata, rimuovi questa inizializzazione dal blocco init
:
users = ArrayList<Any?>()
Il blocco init
dovrebbe avere il seguente aspetto:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
Con queste modifiche, la proprietà users
non è più nulla e possiamo rimuovere tutte le occorrenze dell'operatore !!
non necessarie. Tieni presente che continuerai a visualizzare errori di compilazione in Android Studio, ma continua con i passaggi successivi dei codelab per risolverli.
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)
}
Inoltre, per il valore userNames
, se specifichi il tipo di ArrayList
come contenente Strings
, puoi rimuovere il tipo esplicito nella dichiarazione perché verrà dedotto.
val userNames = ArrayList<String>(users.size)
Destrutturazione
Kotlin consente di destrutturare un oggetto in una serie di variabili utilizzando una sintassi chiamata dichiarazione di destrutturazione. Creiamo più variabili e possiamo utilizzarle in modo indipendente.
Ad esempio, le classi data
supportano la destrutturazione, quindi possiamo destrutturare l'oggetto User
nel ciclo for
in (firstName, lastName)
. Questo ci consente di lavorare direttamente con i valori firstName
e lastName
. Aggiorna il loop for
come mostrato di seguito. Sostituisci tutte le istanze di user.firstName
con firstName
e user.lastName
con lastName
.
for ((firstName, lastName) in users) {
var name: String
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
userNames.add(name)
}
espressione if
I nomi nell'elenco di userNames non sono ancora nel formato che vogliamo. Poiché sia lastName
che firstName
possono essere null
, dobbiamo gestire la nullità quando creiamo l'elenco dei nomi utente formattati. Vogliamo visualizzare "Unknown"
se manca uno dei due nomi. Poiché la variabile name
non verrà modificata dopo essere stata impostata una volta, possiamo utilizzare val
anziché var
. Apporta prima questa modifica.
val name: String
Dai un'occhiata al codice che imposta la variabile name. Potrebbe sembrare una novità vedere una variabile impostata come uguale a un blocco di codice if
/ else
. Questo è consentito perché in Kotlin if
e when
sono espressioni, ovvero restituiscono un valore. L'ultima riga dell'istruzione if
verrà assegnata a name
. Lo scopo di questo blocco è solo inizializzare il valore name
.
In sostanza, questa logica presentata qui è che se lastName
è nullo, name
è impostato su firstName
o "Unknown"
.
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
Operatore Elvis
Questo codice può essere scritto in modo più idiomatico utilizzando l'operatore elvis ?:
. L'operatore elvis restituisce l'espressione a sinistra se non è null o l'espressione a destra se quella a sinistra è null.
Pertanto, nel codice seguente, firstName
viene restituito se non è nullo. Se firstName
è null, l'espressione restituisce il valore a destra , "Unknown"
:
name = if (lastName != null) {
...
} else {
firstName ?: "Unknown"
}
7. Modelli di stringa
Kotlin semplifica l'utilizzo delle String
con i modelli di stringa. I modelli di stringa ti consentono di fare riferimento alle variabili all'interno delle dichiarazioni di stringa utilizzando il simbolo $ prima della variabile. Puoi anche inserire un'espressione all'interno di una dichiarazione di stringa, inserendola tra { } e utilizzando il simbolo $ prima. Esempio: ${user.firstName}
.
Al momento il codice utilizza la concatenazione di stringhe per combinare firstName
e lastName
nel nome utente.
if (firstName != null) {
firstName + " " + lastName
}
Sostituisci la concatenazione di stringhe con:
if (firstName != null) {
"$firstName $lastName"
}
L'utilizzo di modelli di stringhe può semplificare il codice.
L'IDE mostrerà avvisi se esiste un modo più idiomatico per scrivere il codice. Vedrai un'onda sottolineata nel codice e, se passi il mouse sopra, vedrai un suggerimento su come eseguire il refactoring del codice.
Al momento, dovresti vedere un avviso che ti informa che la dichiarazione name
può essere unita all'assegnazione. Applichiamo questa regola. Poiché il tipo della variabile name
può essere dedotto, possiamo rimuovere la dichiarazione esplicita del tipo String
. Ora il nostro formattedUserNames
ha il seguente aspetto:
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
}
Possiamo apportare un'altra modifica. La nostra logica dell'interfaccia utente mostra "Unknown"
se mancano il nome e il cognome, pertanto non supportiamo gli oggetti null. Pertanto, per il tipo di dati formattedUserNames
, sostituisci List<String?>
con List<String>
.
val formattedUserNames: List<String>
8. Operazioni sulle raccolte
Diamo un'occhiata più da vicino al getter formattedUserNames
e vediamo come renderlo più idiomatico. Al momento il codice esegue le seguenti operazioni:
- Crea un nuovo elenco di stringhe
- Esegue l'iterazione dell'elenco di utenti
- Costruisce il nome formattato per ogni utente in base al nome e al cognome dell'utente
- Restituisce l'elenco appena creato
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 fornisce un ampio elenco di trasformazioni delle raccolte che rendono lo sviluppo più rapido e sicuro espandendo le funzionalità dell'API Java Collections. Una di queste è la funzione map
. Questa funzione restituisce un nuovo elenco contenente i risultati dell'applicazione della funzione di trasformazione specificata a ogni elemento dell'elenco originale. Pertanto, anziché creare un nuovo elenco ed eseguire l'iterazione manuale dell'elenco di utenti, possiamo utilizzare la funzione map
e spostare la logica del ciclo for
all'interno del corpo di map
. Per impostazione predefinita, il nome dell'elemento dell'elenco corrente utilizzato in map
è it
, ma per una maggiore leggibilità puoi sostituire it
con il nome della tua variabile. Nel nostro caso, chiamiamolo 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
}
}
Tieni presente che utilizziamo l'operatore Elvis per restituire "Unknown"
se user.lastName
è nullo, poiché user.lastName
è di tipo String?
e per name
è richiesto un String
.
...
else {
user.lastName ?: "Unknown"
}
...
Per semplificare ulteriormente, possiamo rimuovere completamente la variabile name
:
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"
}
}
}
9. Proprietà e proprietà di supporto
Abbiamo notato che il convertitore automatico ha sostituito la funzione getFormattedUserNames()
con una proprietà denominata formattedUserNames
che ha un getter personalizzato. Sotto il cofano, Kotlin genera comunque un metodo getFormattedUserNames()
che restituisce un List
.
In Java, esponiamo le proprietà della classe tramite funzioni getter e setter. Kotlin ci consente di distinguere meglio le proprietà di una classe, espresse con i campi, e le funzionalità, le azioni che una classe può eseguire, espresse con le funzioni. Nel nostro caso, la classe Repository
è molto semplice e non esegue alcuna azione, quindi ha solo campi.
La logica attivata nella funzione Java getFormattedUserNames()
viene ora attivata quando viene chiamato il getter della proprietà Kotlin formattedUserNames
.
Anche se non abbiamo esplicitamente un campo corrispondente alla proprietà formattedUserNames
, Kotlin ci fornisce un campo di supporto automatico denominato field
a cui possiamo accedere, se necessario, da getter e setter personalizzati.
A volte, però, ci serve una funzionalità aggiuntiva che il campo di supporto automatico non fornisce.
Vediamo un esempio.
All'interno della nostra classe Repository
, abbiamo un elenco mutabile di utenti che viene esposto nella funzione getUsers()
generata dal nostro codice Java:
fun getUsers(): List<User>? {
return users
}
Poiché non volevamo che i chiamanti della classe Repository
modificassero l'elenco di utenti, abbiamo creato la funzione getUsers()
che restituisce un List<User>
di sola lettura. In Kotlin, preferiamo utilizzare le proprietà anziché le funzioni per questi casi. Più precisamente, mostreremo un List<User>
di sola lettura basato su un mutableListOf<User>
.
Per prima cosa, rinomina users
in _users
. Evidenzia il nome della variabile, fai clic con il tasto destro del mouse per Ristruttura > Rinomina la variabile. Aggiungi una proprietà pubblica di sola lettura che restituisce un elenco di utenti. Chiamiamola users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
A questo punto, puoi eliminare il metodo getUsers()
.
Con la modifica precedente, la proprietà privata _users
diventa la proprietà di base per la proprietà pubblica users
. Al di fuori della classe Repository
, l'elenco _users
non è modificabile, poiché i consumatori della classe possono accedere all'elenco solo tramite users
.
Quando users
viene chiamato dal codice Kotlin, viene utilizzata l'implementazione di List
della libreria standard di Kotlin, in cui l'elenco non è modificabile. Se users
viene chiamato da Java, viene utilizzata l'implementazione java.util.List
, in cui l'elenco è modificabile e sono disponibili operazioni come add() e remove().
Codice completo:
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)
}
}
10. Funzioni e proprietà di primo livello ed estensioni
Al momento la classe Repository
sa calcolare il nome utente formattato per un oggetto User
. Tuttavia, se vogliamo riutilizzare la stessa logica di formattazione in altri corsi, dobbiamo copiarla e incollarla o spostarla nel corso User
.
Kotlin offre la possibilità di dichiarare funzioni e proprietà al di fuori di qualsiasi classe, oggetto o interfaccia. Ad esempio, la funzione mutableListOf()
utilizzata per creare una nuova istanza di List
è già definita in Collections.kt
della libreria standard di Kotlin.
In Java, ogni volta che hai bisogno di una funzionalità di utilità, molto probabilmente creerai una classe Util
e la dichiari come funzione statica. In Kotlin puoi dichiarare funzioni di primo livello senza avere una classe. Tuttavia, Kotlin offre anche la possibilità di creare funzioni di estensione. Si tratta di funzioni che estendono un determinato tipo, ma sono dichiarate al di fuori del tipo.
La visibilità delle funzioni e delle proprietà dell'estensione può essere limitata utilizzando i modificatori di visibilità. Questi limitano l'utilizzo solo alle classi che richiedono le estensioni e non inquinano lo spazio dei nomi.
Per la classe User
, possiamo aggiungere una funzione di estensione che calcola il nome formattato oppure possiamo memorizzare il nome formattato in una proprietà di estensione. Può essere aggiunto all'esterno del corso Repository
, nello stesso file:
// 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.userFormattedName
Possiamo quindi utilizzare le funzioni e le proprietà di estensione come se facessero parte della classe User
.
Poiché il nome formattato è una proprietà della classe User
e non una funzionalità della classe Repository
, utilizziamo la proprietà estensione. Il nostro file Repository
ora ha il seguente aspetto:
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)
}
}
La libreria standard di Kotlin utilizza le funzioni di estensione per estendere la funzionalità di diverse API Java; molte delle funzionalità di Iterable
e Collection
sono implementate come funzioni di estensione. Ad esempio, la funzione map
utilizzata in un passaggio precedente è una funzione di estensione di Iterable
.
11. Funzioni di ambito: let, apply, with, run, also
Nel codice della classe Repository
, aggiungiamo diversi oggetti User
all'elenco _users
. Queste chiamate possono essere rese più idiomatiche con l'aiuto delle funzioni di ambito Kotlin.
Per eseguire il codice solo nel contesto di un oggetto specifico, senza dover accedere all'oggetto in base al nome, Kotlin offre cinque funzioni di ambito: let
, apply
, with
, run
e also
. Queste funzioni rendono il codice più facile da leggere e più conciso. Tutte le funzioni di ambito hanno un ricevente (this
), possono avere un argomento (it
) e possono restituire un valore.
Ecco una pratica scheda di riferimento per aiutarti a ricordare quando utilizzare ciascuna funzione:
Poiché stiamo configurando l'oggetto _users
in Repository
, possiamo rendere il codice più idiomatico utilizzando la funzione apply
:
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)
}
}
12. Conclusione
In questo codelab abbiamo illustrato le nozioni di base necessarie per iniziare a convertire il codice da Java a Kotlin. Questa conversione è indipendente dalla piattaforma di sviluppo e contribuisce a garantire che il codice scritto sia in Kotlin idiomatico.
Kotlin idiomatico rende la scrittura di codice breve e concisa. Con tutte le funzionalità offerte da Kotlin, esistono molti modi per rendere il codice più sicuro, conciso e leggibile. Ad esempio, possiamo anche ottimizzare la nostra classe Repository
creando un'istanza dell'elenco _users
con gli utenti direttamente nella dichiarazione, eliminando il blocco init
:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
Abbiamo trattato una vasta gamma di argomenti, dalla gestione della nullabilità, dei singleton, delle stringhe e delle raccolte ad argomenti come funzioni di estensione, funzioni di primo livello, proprietà e funzioni di ambito. Siamo passati da due classi Java a due classi Kotlin che ora hanno il seguente aspetto:
User.kt
data 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 }
}
Ecco un TL;DR delle funzionalità Java e della loro mappatura a Kotlin:
Java | Kotlin |
Oggetto | Oggetto |
|
|
|
|
Classe che contiene solo dati | Corso |
Inizializzazione nel costruttore | Inizializzazione nel blocco |
| campi e funzioni dichiarati in un |
Classe singleton |
|
Per scoprire di più su Kotlin e su come utilizzarlo sulla tua piattaforma, consulta queste risorse:
- Kotlin Koans
- Tutorial su Kotlin
- Concetti di base di Kotlin per Android
- Bootcamp di Kotlin per programmatori
- Kotlin per sviluppatori Java: corso senza costi in modalità di controllo