Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Come fa Kotlin a darti i super-poteri

Come fa Kotlin a darti i super-poteri

weLaika

April 24, 2018
Tweet

More Decks by weLaika

Other Decks in Programming

Transcript

  1. Come Java, che prende il nome da un’isola omonima, Kotlin

    prende il nome dalla piccola isola di Kotlin vicino a San Pietroburgo Com’è nato Kotlin? Dopo Java arriva Kotlin
  2. Dmitry Jemerov, JetBrains development lead, ha detto che JetBrains era

    insoddisfatta dalle limitazioni di Java, molte delle quali sono impossibili da risolvere per la necessità di mantenere la retro-compatibilità con il codice già presente. Com’è nato Kotlin?
  3. JetBrains aveva trovato una sola alternativa valida: Scala. Questo linguaggio

    aveva molte caratteristiche giuste… Com’è nato Kotlin?
  4. …ma era troppo lento nella compilazione e troppo diverso da

    Java. JetBrains poteva solo creare un nuovo linguaggio: Kotlin. Com’è nato Kotlin?
  5. Andrey Breslav, lead designer di Kotlin, ha detto che l'obiettivo

    era rendere Kotlin un linguaggio ad oggetti pronto all'uso professionale, migliore di Java e che permettesse alle aziende una graduale migrazione da Java. Com’è nato Kotlin?
  6. Kotlin oggi KotlinConf: • 1.200 sviluppatori • Tutte le aziende

    principali (Google, Facebook, Amazon, etc.) è diventato linguaggio ufficiale per lo sviluppo su Android, con supporto di Google e dell’IDE ufficiale, Android Studio
  7. Il linguaggio e gli strumenti di sviluppo rimangono gli stessi,

    ma varia l’ambiente. La strategia di Kotlin è principalmente fornire librerie e tool di supporto per • permettere agli sviluppatori che usano Kotlin di sfruttare le proprie competenze e i punti forti del linguaggio su più piattaforme • essere il più produttivi possibile su ogni piattaforma Su quali piattaforme si può usare Kotlin
  8. Il punto d’ingresso di ogni programma è il main: fun

    main(args: Array<String>) { /* .. */ } Hello world
  9. Questa dichiarazione ci dice quattro cose: • Per dichiarare funzioni

    si usa la keyword fun • Si possono dichiarare funzioni al livello più alto, non bisogna inserirle dentro una classe • I nomi degli argomenti vengono prima del tipo (come in Pascal) • In Kotlin gli array sono comuni classi e non hanno una sintassi speciale fun main(args: Array<String>) Hello world
  10. fun main(args: Array<String>) { println("Ciao, mondo!") } Per stampare su

    schermo si usa la classica println, solo che Kotlin offre un wrapper che permette di evitare di scrivere System.out.println per intero. Stampare su schermo Println
  11. Println è un esempio di funzione inline: public inline fun

    println(message: CharArray) { System.out.println(message) } Non vi è un costo a runtime nell’usare println invece di System.out.println. Println
  12. class MyApplication { public static void main(String[] args) { System.out.println(“Hello

    world”); } } fun main(args: Array<String>) { println(“Hello world”) } Hello world: Java vs Kotlin
  13. Dichiarare una qualsiasi funzione è altrettanto facile fun moltiplicaPerCinque(valore: Int)

    : Int { return valore * 5 } Il tipo del valore restituito è indicato con due punti dopo gli argomenti della funzione. L’uso del punto e virgola dopo un’istruzione è opzionale. Parliamo di funzioni
  14. Se non indico il tipo di ritorno non ritornano nulla:

    fun returningNothing() { } Posso indicarlo esplicitamente con Unit: fun meNeither() : Unit { } Parliamo di funzioni
  15. Spesso in Java creiamo varianti di un metodo: // cerca

    un valore nell’intera lista int find(List<Integer> myList, value: Int) // cerca un valore in una lista a partire da un indice int find(List<Integer> myList, value: Int, from: Int) // scrive il numero in base 10 String toString(int value) // scrive il numero nella base specificata String toString(int value, int base) Parliamo di funzioni
  16. In Kotlin posso usare valori di default per i parametri

    fun find(list: List<Int>, value: Int, from: Int = 0) : Int { … } fun toString(value:Int, base:Int = 10) : String { … } Parliamo di funzioni
  17. val myList = listOf(1, 10, 8, 2, -2, 3) find(myList,

    1) find(myList, 10) find(myList, 7) // specifico il “from” usando la posizione find(myList, 1, 2) // specifico il “from” usando il nome find(myList, 1, from=2) Usiamo la funzione find appena dichiarata Parliamo di funzioni
  18. Kotlin incoraggia a scrivere piccole funzioni e combinarle. Per farlo

    mi da la possibilità di usare una scorciatoia: fun double(v: Int) = v * 2 fun numberOfExamsToGraduation() = Math.pow(2.0, 10.0) fun fullName(name: String, surname: String) = name + " " + surname Parliamo di funzioni
  19. Le classi su Kotlin si definiscono con la keyword class.

    Se una classe non ha un corpo, si possono omettere le parentesi graffe class Vuota Una classe può avere un costruttore primario integrato nell’ header. Le proprietà dichiarate nel costruttore sono disponibili nel corpo della classe. Un costruttore si dichiara con la parola chiave constructor. class Persona constructor(nome: String) { } Classi
  20. La parola chiave constructor può essere omessa, per il costruttore

    primario, se questo non ha modificatori o annotazioni. class Persona constructor(nome: String) { } // normalmente si scrive così class Persona(nome: String) { } Classi
  21. Cosa facciamo nel 99% dei costruttori? Prendiamo valori e li

    assegniamo a campi omonimi. In Kotlin posso farlo facilemtne: class Persona( val nome: String, val età: Int, val madre: Persona, val padre: Persona) { … } Classi
  22. class Persona( val nome: String, var età: Int) { …

    } // utilizzo val p = Persona(“Gino”, 18) println(”${p.nome} ha ${p.età} anni") u.età = u.età + 2 Classi
  23. Cinque piccole cose che fanno la differenza fra Java e

    Kotlin: 1. Stringhe 2. Extension methods 3. Nullability 4. Data classes 5. Smart cast e Safe cast Kotlin come un Java migliorato
  24. • String interpolation con variabili val nome = "Michele" println("Ciao

    $nome") // stampa su schermo "Ciao Michele” // in Java sarebbe "ciao" + nome • String interpolation con espressioni arbitrarie println("Ciao ${nome.toUpperCase()}") // stampa su schermo "Ciao MICHELE" Stringhe
  25. • Stringhe multilinea val kArt = """| // .| \\"""

    println(kArt.trimMargin(".")) Stampa: | // | \\ Stringhe
  26. Si possono aggiungere metodi che apparentemente estendono classi esistenti fun

    String.siiCortese() = "${this}, per piacere” var richiesta = "Passa il sale" println(testo.siiCortese()) // stampa Passa il sale, per piacere Questi metodi però sono solo syntactic sugar: non modificano la classe originale e non possono accedere ai membri privati della classe Estendere le classi esistenti
  27. Utili per dividere il codice per aspetti In serializzazione.kt: String.toXML():

    String { … } Int.toXML(): String { … } List.toXML(): String { … } Person.toXML(): String { … } Address.toXML(): String { … } Book.toXML(): String { … } Estendere le classi esistenti
  28. data class User(val name: String, var password: String, var age:

    Int) • Ottieni getters e setters (solo per variabili) per leggere e scrivere tutte le proprietà e costruttore Data classes
  29. data class User(val name: String, var password: String, var age:

    Int) • Ottieni getters e setters (solo per variabili) per leggere e scrivere tutte le proprietà e costruttore • equals(), hashCode() e copy() per gestire oggetti, ovvero confrontarli e copiarli Data classes
  30. data class User(val name: String, var password: String, var age:

    Int) • Ottieni getters e setters (solo per variabili) per leggere e scrivere tutte le proprietà e costruttore • equals(), hashCode() e copy() per gestire oggetti, ovvero confrontarli e copiarli • toString() per la stampa di un oggetto in forma leggibile Name_of_the_class(Name_of_the_variable=Value_of_t he_variable, /* .. */)” Data classes
  31. data class User(val name: String, var password: String, var age:

    Int) • Ottieni getters e setters (solo per variabili) per leggere e scrivere tutte le proprietà e costruttore • equals(), hashCode() e copy() per gestire oggetti, ovvero confrontarli e copiarli • toString() per la stampa di un oggetto in forma leggibile Name_of_the_class(Name_of_the_variable=Value_of_t he_variable, /* .. */)” • component1() .. componentN() per tutte le proprietà in ordine di dichiarazione. Usate per destructuring declaration Data classes
  32. data class User(val name: String, val surname: String, val age:

    Int) fun update() { val originalUser = User("Federico", "Tomassetti", 20) originalUser.age = 21 // errore, immutabile val olderUser = originalUser.copy(age = 21) } fun esempioDestructuring() { val originalUser = User("Federico", "Tomassetti", 20) val (name, _, eta) = originalUser } Data classes
  33. Il costruttore primario (di tutte le classi, non solo delle

    data classes) non può contenere alcun codice. Per ovviare a questo problema si possono usare i blocchi di inizializzazione, che sono identificati dalla keyword init. Nei blocchi si può accedere alle proprietà definite nel costruttore primario. Data classes
  34. data class User(val name: String, var password: String, var age:

    Int) { init { // accesso alle proprietà definite // nel costruttore val account = "${name.toLowerCase()} + ${age}” recordNewAccount(account) } } Data classes
  35. Nullability I call it my billion-dollar mistake…At that time, I

    was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. – Tony Hoare, inventor of ALGOL W.
  36. In Kotlin normalmente le variabili non possono avere valore null

    val numero: Int = 1 val numero1: Int = null // invalido, non compila var stringa: String = "testo" var stringa1: String = null // invalido, non compila Nullability
  37. A meno che non sia dichiarato esplicitamente con l'aggiunta di

    ? val numero: Int? = 123 // valido val numero: Int? = null // valido var stringa: String? = “foo” // valido var stringa: String? = null // valido Nullability
  38. Prima di accedere alle variabili potenzialmente null bisogna controllare che

    abbiano un valore valido var testo: String? = "Prova" print(testo.length) // accesso invalido, non compila if(testo != null) { // controllo di validità print(testo.length) // accesso valido, smart cast } Nullability
  39. Una forma più concisa usa l'operatore safe call ?. var

    testo: String? = "Prova" print(testo?.length) // accesso valido, testo è acceduto solo se non-null Nullability
  40. Posso usarlo anche in cascata print(utente?.indirizzo?.nomeVia) Nel frattempo in Java…

    String res = null; if(utente != null) { if (utente.indirizzo != null) { res = utente.indirizzo.nomeVia; } } System.out.print(res); Nullability
  41. Qualche volta siamo sicuri che un valore che potenzialmente può

    essere nullo non lo sia nel caso attuale. Per quei casi c’è l’operatore !!. Se sbagliamo però… NPE data class Identifier(val person : Boolean, val codiceFiscale: String?, val partitaIva: String?) { init { require(!person || (codiceFiscale != null && partitaIva == null)) require(person || (codiceFiscale == null && partitaIva != null)) } … } Nullability
  42. Qualche volta siamo sicuri che un valore che potenzialmente può

    essere nullo non lo sia nel caso attuale. Per quei casi c’è l’operatore !!. Se sbagliamo però… NPE class Identifier(…) { … fun code() : String? { if (person) { return codiceFiscale } return partitaIva } … } Nullability
  43. Qualche volta siamo sicuri che un valore che potenzialmente può

    essere nullo non lo sia nel caso attuale. Per quei casi c’è l’operatore !!. Se sbagliamo però… NPE class Identifier(…) { … fun code() : String { if (person) { return codiceFiscale!! } return partitaIva!! } … } Nullability
  44. Spesso vogliamo fare qualcosa di particolare se un valore è

    nullo. Per quei casi abbiamo l’operatore Elvis ?: fun cleanList(list: List<Int>?) : List<Int> { return list ?: emptyList<Int>() } fun sizeOfList(list: List<Int>?) : Int { return list?.size ?: 0 } Nullability
  45. In Java a volte verifichiamo una condizione e, nel caso,

    effettuiamo un cast: void printSize(value: Object) { if (value instanceof String) { System.out.println("String of size " + ((String)value).length); } if (value instanceof List) { System.out.println("List of size " + ((List)value).size); } } Smart cast & Safe cast
  46. Kotlin supporta una caratteristica chiamata smart cast. In pratica in

    certe situazioni il compilatore è in grado di applicare un’operazione di cast automaticamente. fun printSize(value: Any) { if (value is String) { println("String of size ${value.length}") } if (value is List<*>) { println("List of size ${value.size}") } } Smart cast & Safe cast
  47. Il compilatore applica lo smart cast con espressioni is, when

    e while. Lo smart cast è applicato anche quando è presente un !is seguito da un return o con un is dalle operazione logiche and (&&) e or (||). Lo smart cast non è applicato quando il compilatore non è in grado di garantire che la variabile non sia cambiata tra il controllo e l’uso. Smart cast & Safe cast
  48. // return dopo !is if (testo !is String) return println(testo.length)

    // testo è automaticamente convertito in una String // a destra di && if (a is Int && a > 0) { println(a) // a è automaticamente convertito in Int } Smart cast & Safe cast
  49. fun sizeOfListAlt(list: List<Int>?) : Int { if (list == null)

    { return 0 } // qui list non puó essere null per cui // posso usare . e non ?. return list.size } Smart cast & Safe cast
  50. Il solo operatore di cast as opera in maniera non

    sicura. Ciò significa che se il cast fallisce viene lanciata un’eccezione. Ad esempio se il valore è nullo, ma il tipo in cui deve essere trasformato non lo accetta viene lanciata un’eccezione. Per questo viene definito unsafe cast. Un modo per evitare questo rischio è usare l’operatore di cast con tipi annullabili. // null è valido per b as String? val a: String? = b as String? Smart cast & Safe cast
  51. Un altro modo per evitare il rischio è usare il

    safe cast (as?), se il cast fallisce ritorna null. Comunque il valore che riceve il risultato dell’operazione di cast deve essere in grado di accettare il null. // la funzione ritorna String? (non String) fun stringOrNull(value: Any) : String? { // as? String è valido anche se value è null return value as? String } println(stringOrNull("string")) println(stringOrNull(1)) Smart cast & Safe cast