Kotlin è stato originariamente progettato come un'alternativa sicura e concisa a Java. Una delle sue principali forze è il meccanismo sviluppato di inferenza dei tipi, che consente di scrivere codice meno verboso senza perdere il typing. L'inferenza dei tipi è stata ispirata da linguaggi funzionali (come Scala e Haskell), così come dalle attuali tendenze nella progettazione di linguaggi staticamente tipizzati.
In Java e in altri linguaggi statici è necessario specificare esplicitamente i tipi, il che porta a una ridondanza di codice. Tuttavia, l'assenza di tipi espliciti può rendere difficile comprendere il codice e portare a errori non ovvi se l'inferenza del tipo non funziona correttamente.
In Kotlin, il compilatore può spesso determinare autonomamente il tipo di una variabile o di un'espressione in base al contesto. Questo funziona per le variabili, i valori restituiti dalle funzioni e all'interno delle espressioni con lambda. Tuttavia, ci sono situazioni in cui il compilatore richiede che il tipo venga specificato esplicitamente, ad esempio, nella dichiarazione di una funzione senza valore di ritorno all'interno di una classe ('fun doSomething()') o quando le espressioni sono ambigue.
Esempio di codice:
val a = 42 // Int val s = "hello" // String fun sum(x: Int, y: Int) = x + y // tipo restituito Int dedotto automaticamente val list = listOf(1, 2, 3) // List<Int> // Specifica esplicita del tipo necessaria se il valore non può essere dedotto val emptyList: List<String> = emptyList() // altrimenti sarà List<Nothing>
Caratteristiche chiave:
Perché non si può sempre omettere il tipo dopo i due punti, ad esempio per le proprietà della classe?
Per le proprietà inizializzate non nel punto di dichiarazione (ad esempio, tramite getter o nel blocco init), il compilatore non può dedurre automaticamente il tipo poiché non vede l'inizializzatore.
class User { val fullName: String // Deve essere specificato il tipo, altrimenti errore get() = "name" }
Quale tipo avrà la variabile se si utilizza emptyList() senza tipo esplicito?
Saranno dedotti il tipo List<Nothing>, rendendo il risultato praticamente inutile.
val list = emptyList() // List<Nothing>
Quando l'inferenza del tipo non funziona con i parametri delle funzioni?
Nella firma della funzione è sempre necessario specificare esplicitamente i tipi dei parametri, altrimenti il compilatore genererà un errore.
// Errore: // fun foo(x) = x * 2 // Corretto: fun foo(x: Int) = x * 2
Lo sviluppatore utilizza emptyList() per restituire un valore da una funzione API, senza specificare esplicitamente il tipo. Di conseguenza, viene ottenuto il tipo List<Nothing>, il che provoca problemi durante l'uso di questa API.
Pro:
Lo sviluppatore specifica sempre esplicitamente il tipo quando lavora con collezioni vuote e dove questo migliora la leggibilità, mentre in altri casi si affida all'inferenza del tipo del compilatore.
Pro: