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

E22edc988280c3b6ff183318bc1590c2?s=128

weLaika

April 24, 2018
Tweet

Transcript

  1. Kotlin: un linguaggio pragmatico Federico Tomassetti

  2. 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
  3. 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?
  4. JetBrains aveva trovato una sola alternativa valida: Scala. Questo linguaggio

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

    Java. JetBrains poteva solo creare un nuovo linguaggio: Kotlin. Com’è nato Kotlin?
  6. 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?
  7. 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
  8. Su quali piattaforme si può usare Kotlin 1) JVM (&

    Android) 2) JS 3) Native
  9. 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
  10. Il punto d’ingresso di ogni programma è il main: fun

    main(args: Array<String>) { /* .. */ } Hello world
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. Se non indico il tipo di ritorno non ritornano nulla:

    fun returningNothing() { } Posso indicarlo esplicitamente con Unit: fun meNeither() : Unit { } Parliamo di funzioni
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. • 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
  27. • Stringhe multilinea val kArt = """| // .| \\"""

    println(kArt.trimMargin(".")) Stampa: | // | \\ Stringhe
  28. 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
  29. 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
  30. data class User(val name: String, var password: String, var age:

    Int) 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 Data classes
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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.
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. // 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
  52. 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
  53. 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
  54. 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
  55. Volete saperne di più su Kotlin? https://superkotlin.com Federico Tomassetti