Bases
Variables et types
Préférez val (immuable) à var (mutable). Kotlin infère les types mais permet les annotations explicites. Utilisez 'is' pour les vérifications de type (smart-casts automatiquement). Tout est un objet—pas de primitives dans la syntaxe Kotlin.
val name = "Alice" // immutable (preferred)
var age = 30 // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List<Int> = listOf(1, 2, 3)
println(name::class) // class kotlin.String
println(age is Int) // trueTemplates de chaînes
$var pour les variables simples, ${expr} pour les expressions. Les chaînes à triple guillemets préservent les nouvelles lignes—utilisez trimIndent() pour un formatage propre. Les templates de chaînes rendent la construction de chaînes concise et lisible sans format strings.
val name = "Alice"
val age = 30
println("Name: $name, Age: $age") // simple var
println("Length: ${name.length}") // expression
println("Upper: ${name.uppercase()}")
val multiline = """
Hello, $name
Age: $age
""".trimIndent()Sécurité null
La sécurité null de Kotlin : ? marque les types nullables, ?. appel sûr (retourne null si null), !! asserte non-null (lance NPE si null), ?: elvis (valeur par défaut si null). Cela élimine NullPointerException à la compilation—fonctionnalité centrale de Kotlin.
var name: String? = null // nullable type
println(name?.length) // null (safe call)
name = "Alice"
println(name!!.length) // 5 (non-null assertion)
val len: Int = name?.length ?: 0 // elvis operator
// Non-null types can never be null
val s: String = "hi" // not nullableConversion de type
Kotlin nécessite une conversion de type explicite (pas de widening implicite comme Java). Utilisez toInt()/toDouble()/toString() pour la conversion. toIntOrNull() retourne null en cas d'échec—plus sûr que toInt() qui lance une exception. Gérez toujours null depuis l'analyse.
val n: Int = 42
val d: Double = n.toDouble()
val s: String = n.toString()
val fromStr: Int? = "100".toIntOrNull()
val invalid: Int? = "abc".toIntOrNull() // null
println(d, s, fromStr, invalid)Vérifications de type et smart cast
is vérifie le type et smart-cast la variable dans cette branche—pas de cast explicite nécessaire. !is est la négation. Utilisez as pour les casts non sûrs (lance ClassCastException), as? pour les casts sûrs (retourne null en cas d'échec). Le smart cast est une commodité majeure de Kotlin.
fun describe(x: Any): String =
when (x) {
is Int -> "Int: ${x + 1}" // x smart-cast to Int
is String -> "String of length ${x.length}"
is List<*> -> "List with ${x.size} items"
else -> "Unknown"
}
println(describe(42)) // Int: 43
println(describe("hello")) // String of length 5Chaînes de caractères
Méthodes courantes de chaînes
Les chaînes Kotlin ont de riches fonctions d'extension de la bibliothèque standard. La plupart retournent de nouvelles chaînes (immuables). Utilisez-les au lieu de boucles manuelles. Des fonctions comme uppercase() sont locale-aware—utilisez uppercase(Locale.ROOT) pour des résultats cohérents.
val s = "Hello, World"
println(s.length) // 12
println(s.uppercase()) // HELLO, WORLD
println(s.lowercase()) // hello, world
println(s.split(", ")) // [Hello, World]
println(s.replace("o", "0")) // Hell0, W0rld
println(s.reversed()) // dlroW ,olleH
println(s.startsWith("Hello")) // trueChaînes multilignes et brutes
Les chaînes à triple guillemets sont brutes—pas de séquences d'échappement nécessaires (sauf triple double-guillemets). trimMargin('|') ne garde que le texte après | pour une indentation propre. trimIndent() supprime les espaces communs. Idéal pour SQL, JSON, HTML dans le code.
val text = """
|Hello,
|World!
""".trimMargin() // | marks line start
println(text)
val json = """{"name": "Alice", "age": 30}"""
println(json) // no escape needed for quotes
// trimIndent() removes common leading whitespaceConstruction de chaînes
joinToString est la manière idiomatique de joindre des collections avec séparateurs, préfixe, postfixe. buildString fournit une portée StringBuilder pour une construction concise. Évitez + répété dans les boucles—utilisez joinToString ou StringBuilder pour l'efficacité.
val parts = listOf("apple", "banana", "cherry")
println(parts.joinToString(", ")) // apple, banana, cherry
println(parts.joinToString(prefix="[", postfix="]"))
val sb = StringBuilder()
for (p in parts) sb.append(p).append(" ")
println(sb.toString().trim())
// buildString for concise building
val result = buildString {
parts.forEach { append(it).append(";") }
}Chaîne vers nombre
toInt()/toDouble() lancent NumberFormatException sur entrée invalide. toIntOrNull() retourne null—associez avec ?: pour des valeurs par défaut sûres. toBooleanStrict() n'accepte que 'true'/'false'. Utilisez les variantes OrNull pour l'entrée utilisateur ou les données non fiables.
val n = "42".toInt() // 42 (throws if invalid)
val d = "3.14".toDouble() // 3.14
val safe = "abc".toIntOrNull() // null
val withDefault = "x".toIntOrNull() ?: 0 // 0
val bool = "true".toBooleanStrict() // true
println(n, d, safe, withDefault)Regex
La classe Regex enveloppe le Pattern de Java. find retourne MatchResult? (première correspondance), findAll retourne toutes. matches vérifie la correspondance complète, containsMatchIn vérifie partielle. Utilisez les chaînes à triple guillemets pour les patterns afin d'éviter le double-escaping des backslashes.
val email = Regex("[\w.]+@[\w]+\.[a-z]+")
val text = "Contact: [email protected]"
val match = email.find(text)
println(match?.value) // [email protected]
println(email.matches("[email protected]")) // true
val replaced = text.replace(Regex("\d+"), "#")
println(email.containsMatchIn(text)) // trueStructures de données
List
listOf crée des listes immuables, mutableListOf pour mutables. Les listes sont indexées à partir de zéro. Utilisez first()/last() pour les extrémités (lancent si vide), firstOrNull() pour la sécurité. contains vérifie l'appartenance. Préférez les listes immuables sauf si vous devez modifier.
val nums = listOf(1, 2, 3) // immutable
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable[0] = 0
println(nums.size) // 3
println(nums.first()) // 1
println(nums.last()) // 3
println(nums.contains(2)) // true
println(nums.indexOf(2)) // 1Map
mapOf crée des maps immuables, mutableMapOf pour mutables. 'to' infix crée des paires. [] access retourne V? nullable (null si manquant). Utilisez getOrDefault ou getValue (lance si manquant). Itérez avec déstructuration (k, v).
val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"]) // 30
println(ages.getOrDefault("Eve", 0)) // 0
println(ages.containsKey("Alice")) // true
for ((k, v) in ages) println("$k: $v")
val mutable = mutableMapOf("a" to 1)
mutable["b"] = 2 // add/updateSet
setOf crée des sets immuables, mutableSetOf pour mutables. union/intersect/subtract retournent de nouveaux sets. Les sets imposent l'unicité—add retourne false si l'élément existe. Utilisez pour la déduplication et les opérations ensemblistes. LinkedHashSet préserve l'ordre d'insertion.
val a = setOf(1, 2, 3)
val b = setOf(3, 4, 5)
println(a union b) // [1,2,3,4,5]
println(a intersect b) // [3]
println(a subtract b) // [1,2]
println(a.contains(2)) // true
val mutable = mutableSetOf(1, 2)
mutable.add(3) // true if addedArray et tableaux primitifs
arrayOf crée Array<T> (boxé pour les primitives). Utilisez intArrayOf/doubleArrayOf pour les tableaux de primitives unboxed (meilleure performance). Array(size) { init } crée avec une fonction d'init. Les arrays sont mutables ; préférez List pour la plupart des usages.
val arr = arrayOf(1, 2, 3) // Array<Int>
arr[0] = 10
println(arr.size) // 3
// Specialized arrays for primitives (no boxing)
val ints = intArrayOf(1, 2, 3)
val doubles = doubleArrayOf(1.0, 2.0)
// Array constructor
val squares = Array(5) { it * it } // [0,1,4,9,16]
println(squares.toList())Pair et Triple
Pair et Triple groupent 2-3 valeurs. 'to' infix crée des Pairs (courant pour les maps). Déstructurez avec val (a, b) = pair. Pour plus de 3 valeurs ou des champs nommés, utilisez les data classes—elles sont plus claires et plus maintenables.
val pair = "Alice" to 30 // Pair<String, Int>
println(pair.first) // Alice
println(pair.second) // 30
val (name, age) = pair // destructuring
val triple = Triple(1, "a", 2.0)
println(triple.first, triple.third)
// Useful for returning two values
fun minMax(list: List<Int>): Pair<Int, Int> =
Pair(list.min(), list.max())Flux de contrôle
If / Else (expression)
Dans Kotlin, if/else est une expression qui retourne une valeur. Cela remplace l'opérateur ternaire. Les deux branches doivent retourner des types compatibles. Utilisez pour une affectation conditionnelle concise. Comme instruction (sans else), retourne Unit.
val score = 85
val grade = if (score >= 90) "A"
else if (score >= 80) "B"
else if (score >= 70) "C"
else "F"
println(grade) // B
// if returns a value—no ternary needed
// Both branches must have compatible typesWhen (switch)
when est le switch puissant de Kotlin—supporte les littéraux, ranges (in), négation (!in), valeurs multiples (virgule) et is-checks. Doit être exhaustif lorsqu'utilisé comme expression avec des types sealed. Sans sujet, c'est une chaîne if/else plus propre.
val n = 2
val label = when (n) {
0 -> "zero"
1, 2, 3 -> "small"
in 4..10 -> "medium"
!in 1..100 -> "out of range"
else -> "large"
}
println(label) // small
// When without subject = multi-condition if
when {
n > 0 -> println("positive")
n < 0 -> println("negative")
else -> println("zero")
}Boucles for et ranges
.. est un range inclusif, until est exclusif, downTo compte à rebours, step définit l'incrément. withIndex() donne l'itération indexée. for-in fonctionne avec n'importe quel Iterable. Ces ranges sont concis et lisibles pour les boucles de comptage.
for (i in 0 until 5) print(i) // 01234 (excludes 5)
for (i in 1..5) print(i) // 12345 (includes 5)
for (i in 5 downTo 1) print(i) // 54321
for (i in 1..10 step 2) print(i) // 13579
val list = listOf("a", "b", "c")
for ((index, value) in list.withIndex()) {
println("$index: $value")
}While et do-while
while vérifie avant, do-while vérifie après (s'exécute au moins une fois). Les deux nécessitent un état mutable. Préférez les boucles for ou les opérations fonctionnelles (map, filter) pour l'immutabilité. Utilisez while pour les boucles pilotées par condition où le compte n'est pas connu.
var count = 0
while (count < 3) {
println(count)
count++
}
var j = 0
do {
println(j)
j++
} while (j < 3)
// do-while runs at least once
// Prefer for loops when possibleBreak et continue (labels)
continue passe à l'itération suivante, break sort de la boucle. Les labels (@outer) permettent de sortir des boucles imbriquées—définissez avec label@ avant la boucle, break/continue avec @label. Utilisez avec parcimonie ; le refactoring en fonctions est souvent plus propre.
for (i in 1..5) {
if (i == 3) continue // skip 3
if (i == 5) break // stop at 5
println(i) // 1, 2, 4
}
// Labeled breaks for nested loops
outer@ for (i in 1..3) {
for (j in 1..3) {
if (i == 2 && j == 2) break@outer
println("$i,$j")
}
}Fonctions et lambdas
Définition de fonction
Les fonctions utilisent fun name(params): ReturnType. Corps d'expression (= expr) pour les expressions uniques. Paramètres par défaut et arguments nommés supportés. Type de retour Unit = effet de bord uniquement. Les params par défaut réduisent le besoin de surcharge.
fun add(a: Int, b: Int): Int = a + b // expression body
fun greet(name: String, greeting: String = "Hello"): String {
return "$greeting, $name!"
}
println(add(3, 4)) // 7
println(greet("Alice")) // Hello, Alice!
println(greet("Bob", greeting = "Hi")) // named arg
fun log(msg: String): Unit = println(msg) // Unit = voidLambdas et ordre supérieur
Lambdas : { params -> body }. 'it' est le raccourci pour un paramètre unique. Passez les lambdas aux fonctions d'ordre supérieur comme map/filter/reduce. Syntaxe trailing lambda : si le dernier param est une fonction, il peut aller en dehors de (). C'est du Kotlin idiomatique.
val square: (Int) -> Int = { x -> x * x }
println(square(5)) // 25
val nums = listOf(1, 2, 3)
println(nums.map { it * 2 }) // [2, 4, 6]
println(nums.filter { it > 1 }) // [2, 3]
println(nums.reduce { a, b -> a + b }) // 6
// 'it' is the implicit single parameterFonctions d'extension
Les fonctions d'extension ajoutent des méthodes aux classes existantes (même de Java) sans héritage ni modification. Elles sont du sucre syntaxique pour des fonctions statiques. 'this' fait référence au récepteur. Utilisez pour rendre les APIs fluentes. Résolu à la compilation (pas de dispatch dynamique).
fun String.shout(): String = this.uppercase() + "!"
fun Int.isEven(): Boolean = this % 2 == 0
println("hello".shout()) // HELLO!
println(4.isEven()) // true
// Extensions don't modify the class—they're resolved statically
// Use to add utility methods to any typeInline et infix
inline copie le corps de la fonction aux sites d'appel (élimine l'overhead des lambdas). Utilisez pour les fonctions d'ordre supérieur dans les chemins chauds. infix permet les appels de fonction sans point/parenthèses (a op b)—utilisez pour les DSL et opérations lisibles comme 'to', 'in', 'and'.
inline fun measure(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println("Took ${System.currentTimeMillis() - start}ms")
}
measure { println("working...") }
// Infix functions for readable calls
infix fun Int.times(str: String): String = str.repeat(this)
println(3 times "ab") // abababVararg et spread
vararg accepte des arguments variables (collectés dans un tableau). * spread un tableau en vararg. Les params nommés peuvent suivre vararg. Utilisez pour des APIs flexibles comme listOf(), fonctions style printf. L'opérateur spread est l'équivalent Kotlin du spread JS.
fun sum(vararg nums: Int): Int = nums.sum()
println(sum(1, 2, 3, 4)) // 10
// Spread operator to pass array
val arr = intArrayOf(1, 2, 3)
println(sum(*arr)) // 6
// Named/default params can follow vararg
fun greet(vararg names: String, greeting: String = "Hi") =
names.joinToString { "$greeting, $it!" }
println(greet("A", "B"))Classes et POO
Classe et constructeur
Le constructeur primaire est dans l'en-tête de classe. Les params val/var deviennent des propriétés (avec getters/setters). Sans val/var, ce sont juste des params de constructeur (non accessibles). Le bloc init s'exécute pendant la construction. Kotlin génère getters/setters automatiquement.
class Person(val name: String, val age: Int) {
fun greet(): String = "Hi, I'm $name"
fun isAdult(): Boolean = age >= 18
}
val p = Person("Alice", 30)
println(p.greet()) // Hi, I'm Alice
println(p.name) // Alice (val = property)
println(p.isAdult()) // true
// val/var in constructor = auto propertyData class
data class auto-génère equals, hashCode, toString, copy et componentN (pour la déstructuration). Utilisez pour les conteneurs de données. Doit avoir au moins un param val/var. copy() crée un clone modifié—excellent pour les mises à jour immuables. Déstructurez avec val (a, b) = point.
data class Point(val x: Int, val y: Int)
val p1 = Point(3, 4)
val p2 = Point(3, 4)
println(p1 == p2) // true (value equality)
println(p1.copy(x = 5)) // Point(x=5, y=4)
println(p1) // Point(x=3, y=4)
// Auto: equals, hashCode, toString, copy, componentN
val (x, y) = p1 // destructuring via componentN()Sealed class
Les sealed classes restreignent les sous-types au même fichier/module kotlin. Les expressions when sont exhaustives (le compilateur vérifie tous les cas). Utilisez pour les ADT (Algebraic Data Types) représentant des états finis. Combiné avec when, cela permet du pattern matching sûr et vérifié à la compilation.
sealed class Result
data class Success(val value: Int) : Result()
data class Failure(val error: String) : Result()
object Loading : Result()
fun handle(r: Result): String = when (r) {
is Success -> "Got ${r.value}"
is Failure -> "Error: ${r.error}"
Loading -> "Loading..."
}
// when is exhaustive—compiler enforces all casesObject et companion
object déclare un singleton (une instance, initialisé paresseusement). companion object dans une classe contient des membres 'statiques' (accessibles via ClassName.member). Utilisez object pour les singletons, companion pour les méthodes factory et constantes. Companion peut implémenter des interfaces.
object Config { // singleton
val version = "1.0"
fun load() = mapOf("key" to "value")
}
println(Config.version) // 1.0
class User(val name: String) {
companion object {
fun create(name: String) = User(name)
const val MAX = 100
}
}
val u = User.create("Alice") // like static methodHéritage et interface
Les classes sont final par défaut—utilisez 'open' pour permettre l'héritage. 'override' est requis. Les interfaces peuvent avoir des implémentations par défaut. Une classe étend une classe mais implémente plusieurs interfaces. Utilisez abstract pour l'implémentation partielle. Préférez la composition à l'héritage.
open class Animal(val name: String) {
open fun speak() = "..."
}
class Dog(name: String) : Animal(name) {
override fun speak() = "Woof"
}
interface Greetable {
val name: String
fun greet(): String // can have default impl
}
class Person(override val name: String) : Greetable {
override fun greet() = "Hi, $name"
}Sécurité null et fonctions de portée
Appels sûrs et elvis
?. appel sûr (retourne null si le récepteur est null), ?: elvis (fournit une valeur par défaut). Chaînez ?. pour l'accès profond (user?.address?.city). Utilisez ?: return/throw pour les sorties anticipées. Cela rend la gestion null concise et sûre—pas de vérifications null explicites nécessaires.
var name: String? = "Alice"
println(name?.length) // 5
name = null
println(name?.length) // null
println(name?.length ?: 0) // 0 (elvis: default if null)
println(name?.length ?: return) // early return
// Safe call chain
val city: String? = user?.address?.citylet (vérification null)
let exécute un bloc uniquement si la valeur est non-null. 'it' est la valeur non-null dans le bloc. Utilisez pour les opérations gardées par null au lieu des vérifications if-null. Pattern courant : value?.let { ... } pour un traitement sûr. Retourne le résultat du bloc.
var name: String? = "Alice"
name?.let {
println("Name is $it") // only runs if not null
println("Length: ${it.length}") // it is non-null
}
// Common for null-guarded blocks
val result = name?.let {
process(it) // it: String (non-null)
} ?: "default"apply et also
apply configure un objet (retourne l'objet, récepteur 'this')—idéal pour les builders. also effectue des effets de bord (retourne l'objet, param 'it')—bon pour le logging/debugging dans les chaînes. Les deux retournent l'objet original, permettant des chaînes fluentes.
val list = mutableListOf<Int>().apply {
add(1); add(2); add(3) // 'this' = the list
println("Built $size items")
}
val config = Config().also {
it.timeout = 30 // 'it' = config
println("Configured")
}
// apply: configure and return object (this)
// also: side effects, return object (it)run et with
run exécute un bloc sur un objet (retourne le résultat du bloc, récepteur 'this')—utilisez pour transformer un objet. with est comme run mais prend l'objet comme paramètre (pas chaînable sur nullables). Utilisez pour grouper des opérations sur le même objet.
val result = "Hello".run {
length // 'this' = string, returns last expr
} // 5
val r2 = with(StringBuilder()) {
append("a"); append("b")
toString() // returns this
}
// run: object.run { } - returns block result
// with: with(obj) { } - returns block result (not chainable)TakeIf et TakeUnless
takeIf retourne l'objet si le prédicat est vrai, null sinon. takeUnless est l'opposé. Utilisez pour le filtrage conditionnel dans les chaînes—évite les vérifications if séparées. Combinez avec ?: pour les valeurs par défaut. Élégant pour les pipelines de validation.
val age = 25
val valid = age.takeIf { it >= 18 } // 25 (if true)
val invalid = age.takeIf { it < 18 } // null (if false)
val adult = age.takeUnless { it < 18 } // 25
// Useful for filtering in chains
val email = getUser()?.email?.takeIf { it.contains("@") }
println(valid, invalid, adult)Collections et fonctionnel
Map / Filter / Fold
map transforme, filter sélectionne, reduce/fold agrège. 'it' est l'élément implicite. fold prend une graine ; reduce non (lance si vide). Ce sont le cœur du traitement fonctionnel des collections—utilisez au lieu des boucles pour la clarté.
val nums = listOf(1, 2, 3, 4, 5)
println(nums.map { it * 2 }) // [2,4,6,8,10]
println(nums.filter { it % 2 == 0 }) // [2,4]
println(nums.reduce { a, b -> a + b }) // 15
println(nums.fold(0) { a, b -> a + b }) // 15
println(nums.sum()) // 15
println(nums.joinToString(", ")) // 1, 2, 3, 4, 5FlatMap et GroupBy
flatten supprime un niveau d'imbrication. flatMap map et aplatit en une étape—essentiel pour les transformations imbriquées. groupBy partitionne par clé dans un Map. Ce sont puissants pour le traitement de données et les pipelines d'analyse.
val nested = listOf(listOf(1, 2), listOf(3, 4))
println(nested.flatten()) // [1,2,3,4]
println(nested.flatMap { it.map { n -> n * 2 } }) // [2,4,6,8]
val words = listOf("apple", "bat", "ant")
val byFirst = words.groupBy { it.first() }
// {a=[apple, ant], b=[bat]}
println(byFirst)Tri
sorted/sortedDescending trient naturellement. sortedBy/sortedByDescending trient par un sélecteur de clé. Ils retournent de nouvelles listes (immuables). Pour les listes mutables, utilisez sort/sortBy (sur place). Utilisez les sélecteurs de clé pour trier par un champ spécifique.
val nums = listOf(3, 1, 4, 1, 5)
println(nums.sorted()) // [1,1,3,4,5]
println(nums.sortedDescending()) // [5,4,3,1,1]
val people = listOf("Alice" to 30, "Bob" to 25)
val byAge = people.sortedBy { it.second } // by age
println(byAge)
val byNameDesc = people.sortedByDescending { it.first }
println(byNameDesc)Sequences (paresseux)
Les sequences sont paresseuses—les opérations sont différées jusqu'à une opération terminale (toList, sum, count). Évite les collections intermédiaires pour une meilleure performance sur les grandes données. Utilisez asSequence() pour les pipelines multi-étapes sur de grandes collections. Comme Java Streams.
val nums = (1..1000000).toList()
// Eager: creates intermediate lists
val eager = nums.filter { it % 2 == 0 }.map { it * 2 }.take(5)
// Lazy: no intermediate collections
val lazy = nums.asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.take(5)
.toList() // forces evaluation
println(lazy) // [4, 8, 12, 16, 20]Partition et Chunked
partition sépare en deux listes par un prédicat (retourne Pair). chunked divise en listes de taille fixe. windowed crée des fenêtres glissantes. Utiles pour le batching, la pagination et les algorithmes à fenêtre glissante. Tous retournent de nouvelles collections.
val nums = listOf(1, 2, 3, 4, 5)
val (evens, odds) = nums.partition { it % 2 == 0 }
println(evens) // [2, 4]
println(odds) // [1, 3, 5]
val chunked = nums.chunked(2)
println(chunked) // [[1, 2], [3, 4], [5]]
val windowed = nums.windowed(3)
println(windowed) // [[1,2,3], [2,3,4], [3,4,5]]Coroutines et async
Launch (fire and forget)
launch démarre une coroutine qui ne retourne pas de résultat (fire-and-forget). delay est non-bloquant (contrairement à Thread.sleep). runBlocking fait le pont sync/async (utilisez dans main/tests). Les coroutines sont légères—des milliers peuvent s'exécuter sur quelques threads.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000)
println("World!") // after 1s
}
println("Hello") // immediately
// launch doesn't return a result
// Use for side-effect coroutines
}
// Output: Hello, then World! after 1sAsync et await
async démarre une coroutine qui retourne un Deferred<T>. await() suspend jusqu'