Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Kotlin
Code

Kotlin Da Zero: Funzioni Avanzate

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: More Fun With Functions
Kotlin From Scratch: Classes and Objects

Italian (Italiano) translation by Mirko Pizii (you can also view the original English article)

Kotlin è un linguaggio funzionale e ciò significa che le funzioni sono anteriori e centrali. La lingua è ricca di funzionalità per rendere le funzioni di codifica più facili ed esplicative. In questo post, potrai conoscere le funzioni di estensione, funzioni di ordine superiore, chiusure e funzioni inline in Kotlin.

Nell'articolo precedente, hai imparato le funzioni di alto livello, le espressioni di lambda, le funzioni anonime, le funzioni locali, le funzioni infine e, infine, le funzioni dei membri in Kotlin. In questo tutorial, continueremo a saperne di più sulle funzioni di Kotlin scavando:

  • Funzioni di estensione
  • Funzioni di ordine superiore
  • Chiusure
  • Funzioni inline

1. Funzioni di estensione

Non sarebbe bello se il tipo String in Java avesse un metodo per capitalizzare la prima lettera in uno String-like ucfirst() in PHP? Potremmo chiamare questo metodo upperCaseFirstLetter().

Per capire ciò, è possibile creare una sottoclasse String che estende il tipo String in Java. Ma ricorda che la classe String in Java è finale, il che significa che non è possibile estenderlo. Una possibile soluzione per Kotlin sarebbe quella di creare funzioni di aiuto o di funzioni di livello superiore, ma questo potrebbe non essere ideale perché non abbiamo potuto utilizzare la funzionalità Auto-Complete IDE per visualizzare l'elenco dei metodi disponibili per il tipo String. Quello che sarebbe veramente bello sarebbe in qualche modo aggiungere una funzione a una classe senza dover ereditare da quella classe.

Beh, Kotlin ci ha coperti con un'altra caratteristica impressionante: funzioni di estensione. Ci danno la possibilità di estendere una classe con nuove funzionalità senza dover ereditare da quella classe. In altre parole, non abbiamo bisogno di creare un nuovo sottotipo o di modificare il tipo originale.

Una funzione di estensione viene dichiarata al di fuori della classe che desidera estendere. In altre parole, è anche una funzione di alto livello (se si desidera un aggiornamento sulle funzioni di alto livello in Kotlin, visita il tutorial più divertente con le funzioni in questa serie).

Oltre alle funzioni di estensione, Kotlin supporta anche le proprietà di estensione. In questo post, discuteremo le funzioni di estensione e aspetteremo un post futuro per discutere le proprietà di estensione con le classi di Kotlin.

Creazione di una funzione di estensione

Come potete vedere nel codice qui sotto, abbiamo definito una funzione di livello superiore come normale per dichiarare una funzione di estensione. Questa funzione di estensione si trova all'interno di un pacchetto chiamato com.chike.kotlin.strings.

Per creare una funzione di estensione, devi prefigurare il nome della classe che state estendendo prima del nome della funzione. Il nome di classe o il tipo su cui viene definita l'estensione si chiama il tipo di ricevitore e l'oggetto ricevente è l'istanza di classe o il valore su cui viene chiamata la funzione di estensione.

Nota che la parola chiave this all'interno del corpo della funzione fa riferimento all'oggetto di ricevimento o all'istanza.

Chiamare una funzione di estensione

Dopo aver creato la funzione di estensione, devi prima importare la funzione di estensione in altri pacchetti o file da utilizzare in quel file o pacchetto. Quindi, chiamare la funzione è uguale a chiamare qualsiasi altro metodo della classe di tipo ricevente.

Nell'esempio precedente, il tipo di ricevitore è String di classe e l'oggetto ricevente è "chike". Se stai utilizzando un IDE come IntelliJ IDEA con la funzione IntelliSense, vedrai la nuova funzione di estensione proposta nell'elenco di altre funzioni in un tipo String.

IntelliJ IDEA intellisense feature

Interoperabilità di Java

Notate che dietro le quinte, Kotlin creerà un metodo statico. Il primo argomento del metodo statico è l'oggetto ricevente. Così è facile per i chiamanti Java chiamare questo metodo statico e quindi passare l'oggetto ricevente come un argomento.

Ad esempio, se la nostra funzione di estensione è stata dichiarata in un file StringUtils.kt, il compilatore Kotlin creerebbe una classe Java StringUtilsKt con un metodo statico upperCaseFirstLetter().

Ciò significa che i chiamanti Java possono semplicemente chiamare il metodo facendo riferimento alla sua classe generata, proprio come per qualsiasi altro metodo statico.

Ricorda che questo meccanismo Java interop è simile a come funzionano le funzioni di alto livello in Kotlin, come abbiamo discusso nel post di More Fun With Functions!

Funzioni di estensione contro le funzioni dei membri

Si noti che le funzioni di estensione non possono ignorare le funzioni già dichiarate in una classe o un'interfaccia conosciuta come funzioni membro (se si desidera un aggiornamento sulle funzioni membro di Kotlin, dare un'occhiata al tutorial precedente di questa serie). Quindi, se hai definito una funzione di estensione con la stessa identica funzionalità dello stesso nome di funzione e lo stesso numero, tipi e ordine degli argomenti, indipendentemente dal tipo di ritorno, il compilatore Kotlin non lo invoca. Nel processo di compilazione, quando viene richiamata una funzione, il compilatore Kotlin cerca innanzitutto una corrispondenza nelle funzioni membro definite nel tipo di istanza o nelle sue superclassi. Se c'è una corrispondenza, allora quella funzione membro è quella che viene invocata o associata. Se non c'è corrispondenza, il compilatore richiamerà qualsiasi funzione di estensione di quel tipo.

Così in sintesi: le funzioni dei membri vincono sempre.

Vediamo un esempio pratico.

Nel codice sopra abbiamo definito un tipo chiamato Student con due funzioni membro: printResult() e expel(). Abbiamo quindi definito due funzioni di estensione che hanno gli stessi nomi delle funzioni membro.

Chiamiamo la funzione printResult() e vediamo il risultato.

Come potete vedere, la funzione che è stata invocata o associata era la funzione membro e non la funzione di estensione con la stessa firma di funzioni (anche se IntelliJ IDEA avrebbe ancora un suggerimento su di esso).

Tuttavia, chiamare la funzione membro expel() e la funzione expel(reason: String) produrrà risultati diversi perché le firme delle funzioni sono diverse.

Funzioni di estensione di membro

Dichiara una funzione di estensione come funzione di livello superiore per la maggior parte del tempo, ma è anche possibile dichiararle come funzioni membro.

Nel codice precedente, abbiamo dichiarato una funzione di estensione exFunction() di tipo ClassB all'interno di un'altra classe ClassA. Il ricevitore di spedizione è l'istanza della classe in cui viene dichiarata l'estensione e l'istanza del tipo di ricevitore del metodo di estensione è chiamata il ricevitore di estensione. Quando esiste un conflitto di nomi o una ombra tra il ricevitore di spedizione e il ricevitore di estensione, si noti che il compilatore sceglie il ricevitore di estensione.

Quindi, nell'esempio di codice sopra, il ricevitore di estensione è un'istanza di ClassB, quindi significa che il metodo toString() è di tipo ClassB quando viene chiamato all'interno della funzione di estensione exFunction(). Per noi invocare il metodo toString() del destinatario di spedizione ClassA, invece, dobbiamo utilizzare un qualificato this:

2. Funzioni di ordine superiore

Una funzione di ordine superiore è solo una funzione che prende un'altra funzione (o l'espressione lambda) come parametro, restituisce una funzione o entrambe le funzioni. La funzione di raccolta last() è un esempio di una funzione di ordine superiore dalla libreria standard.

Qui abbiamo passato un lambda alla funzione last per servire come predicato per cercare in un sottoinsieme di elementi. Ora ci immergeremo nella creazione delle nostre funzioni di ordine superiore a Kotlin.

Creare una funzione di ordine superiore

Guardando la funzione circleOperation() sotto, ha due parametri. Il primo, il raggio, accetta un double, e il secondo, op, è una funzione che accetta un double come ingresso e restituisce anche un double come output: possiamo dire più sinteticamente che il secondo parametro è "una funzione da double a double" .

Osserva che i tipi di parametri della funzione op per la funzione sono avvolti in parentesi () e il tipo di output è separato da una freccia. La funzione circleOperation() è un tipico esempio di una funzione di ordine superiore che accetta una funzione come parametro.

Invocare funzioni di ordine superiore

Nell'invocazione di questa funzione di circleOperation(), passiamo ad essa un'altra funzione, calArea(). (Si noti che se la firma del metodo della funzione passata non corrisponde a quella che dichiara la funzione di ordine superiore, la chiamata di funzione non verrà compilata.)

Per passare la funzione calArea() come parametro a circleOperation(), dobbiamo prefisso con :: e ignorare le parentesi ().

L'utilizzo di funzioni di ordine superiore consente di rendere il codice più leggibile e più comprensibile.

Lambda e funzioni di ordine superiore

Possiamo anche passare una lambda (o funzione literale) ad una funzione di ordine superiore quando si invoca la funzione:

Ricorda, per evitare di denominare esplicitamente l'argomento, possiamo usare il suo nome di argomento che viene generato automaticamente per noi solo se l'lambda ha un argomento. (Se si desidera un aggiornamento su lambda in Kotlin, visitare il tutorial più divertente con funzioni in questa serie).

Ritornare una funzione

Ricorda che oltre ad accettare una funzione come parametro, le funzioni di ordine superiore possono anche restituire una funzione ai chiamanti.

Qui la funzione multiplier() restituirà una funzione che applica il dato fattore a qualsiasi numero passato in esso. Questa funzione restituita è una lambda (o funzione letterale) dal doppio al doppio (il parametro di ingresso della funzione restituita è un tipo doppio e il risultato dell'output è anche un tipo doppio).

Per verificare questo, abbiamo passato in un fattore di due e assegnato la funzione restituita alla variabile doubler. Possiamo invocare questo come una funzione normale e qualunque valore che passiamo in esso sarà raddoppiato.

3. Chiusure

Una chiusura è una funzione che ha accesso a variabili e parametri definiti in ambito esterno.

Nel codice precedente, lambda passato alla funzione di raccolta filter() utilizza il parametro length della funzione esterna printFilteredNamesByLength(). Si noti che questo parametro è definito al di fuori dell'ambito dell'ambia, ma che lambda è ancora in grado di accedere alla lunghezza. Questo meccanismo è un esempio di chiusura nella programmazione funzionale.

4. Funzioni inline

In More Fun With Functions, ho citato che il compilatore Kotlin crea una classe anonima nelle versioni precedenti di Java dietro le quinte quando si creano espressioni lambda.

Purtroppo questo meccanismo introduce l'overhead perché una classe anonima viene creata sotto il cofano ogni volta che creiamo un lambda. Inoltre, un lambda che utilizza il parametro di funzione esterna o la variabile locale con una chiusura aggiunge la propria overhead di allocazione della memoria perché un nuovo oggetto viene assegnato al heap con ogni invocazione.

Confronto delle funzioni inline con funzioni normali

Per evitare questi overheads, il team Kotlin ci ha fornito il modificatore inline per funzioni. Una funzione di ordine superiore con il modificatore inline sarà in linea durante la compilazione di codice. In altre parole, il compilatore copierà lambda (o funzione letterale) e anche il corpo funzione di ordine superiore e li incolla nel sito di chiamata.

Diamo un'occhiata all'esempio pratico.

Nel codice riportato di seguito abbiamo una funzione di ordine superiore per circleOperation() che non dispone del modificatore inline. Ora vediamo il codice bytecode di Kotlin generato quando compiliamo e decompilare il codice e poi lo confrontiamo con quello che ha il modificatore inline.

Nel codice di bytecode generato di seguito sopra, è possibile vedere che il compilatore chiamato la funzione circleOperation() all'interno del metodo main().

Ora specifichiamo la funzione di ordine superiore come inline e vediamo anche il bytecode generato.

Per fare in linea una funzione di ordine superiore, dobbiamo inserire il modificatore inline prima della parola chiave fun, proprio come abbiamo fatto nel codice precedente. Controlliamo anche il bytecode generato per questa funzione inline.

Guardando il bytecode generato per la funzione inline all'interno della funzione main(), è possibile osservare che invece di chiamare la funzione circleOperation() ha ora copiato il corpo funzione di circleOperation() incluso il corpo lambda e incollato al suo call-site.

Con questo meccanismo, il nostro codice è stato ottimizzato in modo significativo: non è più la creazione di classi anonimi o allocazioni di memoria extra. Ma essere molto consapevoli che avremmo un codice di bytecode più grande dietro le quinte di prima. Per questo motivo, si raccomanda di inline solo le funzioni di ordine superiore più alto che accettino lambda come parametri.

Molte delle funzioni standard di ordine superiore di libreria a Kotlin hanno il modificatore in linea. Ad esempio, se si esegue un'occhiata al filter() e first() delle funzioni di operazioni di raccolta, vedrai che hanno il modificatore inline e sono anche di dimensioni ridotte.

Ricorda di non inserire le funzioni normali che non accettano un lambda come parametro! Saranno compilati, ma non ci saranno miglioramenti significativi delle prestazioni (IntelliJ IDEA avrebbe anche fornito un suggerimento su questo).

Il modificatore noinline

Se si dispone di più di due parametri lambda in una funzione, si ha l'opzione di decidere quale lambda non entrare in linea usando il modificatore noinline sul parametro. Questa funzionalità è utile soprattutto per un parametro lambda che richiederà un sacco di codice. In altre parole, il compilatore Kotlin non copia e incolla lambda dove viene chiamato, ma creerà invece una classe anonima dietro la scena.

Qui abbiamo inserito il modificatore noinline al secondo parametro lambda. Tieni presente che questo modificatore è valido solo se la funzione ha il modificatore inline.

Stack trace nelle funzioni inline

Si noti che quando viene presentata un'eccezione all'interno di una funzione inline, lo stack di chiamata di metodo nella traccia dello stack è diverso da una normale funzione senza il modificatore inline. Ciò è dovuto al meccanismo di copia e incolla utilizzato dal compilatore per le funzioni inline. La cosa più fredda è che l'IDEA di IntelliJ ci aiuta a navigare facilmente nella pila di metodo chiamata nella traccia dello stack per una funzione in linea. Vediamo un esempio.

Nel codice precedente, un'eccezione viene generata deliberatamente all'interno della funzione inline myFunc(). Vediamo ora la traccia dello stack all'interno di IntelliJ IDEA quando viene eseguito il codice. Guardando lo screenshot qui sotto, puoi vedere che ci sono due opzioni di navigazione da scegliere: il corpo di funzione Inline o il sito di chiamata in linea. La scelta del primo ci porterà al punto che l'eccezione è stata gettata nel corpo funzione, mentre quest'ultimo ci porterà al punto che il metodo è stato chiamato.

IntelliJ IDEA stack trace for inline function

Se la funzione non era in linea, la nostra traccia di stack sarebbe come quella che potrebbe essere già conosciuta:

IntelliJ IDEA stack trace for normal function

Conclusione

In questo tutorial, hai imparato ancor più cose che puoi fare con le funzioni di Kotlin. Abbiamo coperto:

  • Funzioni di estensione
  • Funzioni di ordine superiore
  • Chiusure
  • Funzioni in linea

Nel prossimo tutorial nella serie Kotlin Da Zero, scaveremo nella programmazione orientata agli oggetti e iniziare ad imparare come funzionano le classi in Kotlin. A presto!

Per saperne di più sul linguaggio Kotlin, consiglio di visitare la documentazione di Kotlin. Oppure vedi alcuni dei nostri altri post di sviluppo di applicazioni Android qui su Envato Tuts+!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.