Cours Kotlin Complet 🟣

Les 12 chapitres essentiels — null safety, data classes, coroutines et l'écosystème Android/multiplateforme

12
Chapitres
100+
Exemples de code
Kotlin 2.x
Version
A1→C2
Niveaux

CHAPITRE 01

Introduction et premier programme

🟣 Hello World
fun main()

Kotlin = Java en mieux. Créé par JetBrains, adopté par Google comme langage officiel pour Android en 2019. Il est 100% interopérable avec Java — vous pouvez appeler du Java depuis Kotlin et vice versa. Mais Kotlin est plus concis, plus sûr (null safety), et plus expressif.

⚙️ Installer et exécuter
# Installer
# – IntelliJ IDEA (recommandé) ou Android Studio
# – ou via sdkman : sdk install kotlin

# Compiler et exécuter
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar

# REPL interactif
kotlinc

# Avec Gradle (standard)
# build.gradle.kts
# plugins
gradle run

🏗️ Kotlin en résumé
CaractéristiqueKotlin
TypageStatique et fort avec inférence
ParadigmePOO + fonctionnel
CompilationJVM, JS, Native (LLVM), WASM
Null SafetyIntégré dans le système de types
Interop100% compatible Java
UsageAndroid (officiel), backend (Ktor, Spring), multiplateforme
ConventioncamelCase (fonctions/variables), PascalCase (classes)
🧠 Quiz
Pourquoi Kotlin est préféré à Java pour Android ?
Kotlin est plus concis (40% de code en moins), plus sûr (null safety intégré), supporte les coroutines (async natif), et est 100% interopérable avec Java. Google l'a adopté comme langage recommandé en 2019.

CHAPITRE 02

Variables et types de données

📦 val vs var
// val = immuable (PRÉFÉRÉ — comme let en Swift, const en JS)
val nom = « Alice » // Type inféré : String
val pi = 3.14159 // Double
val age: Int = 25 // Type explicite

// var = mutable
var score = 0
score += 10 // ✅

// nom = « Bob » ❌ Val cannot be reassigned

🔢 Types
TypeTailleExemple
Int32 ou 64 bits42
Long64 bits42L
Double64 bits3.14
Float32 bits3.14f
Boolean1 bittrue / false
Char16 bits'A'
Stringvariable« Hello $name »

En Kotlin, tout est objet. Pas de types primitifs — Int, Boolean, etc. sont des classes. Le compilateur optimise en primitifs JVM quand possible. Pas besoin de int vs Integer comme en Java.

📝 Strings
val s = « Hello World »

// String templates
val msg = « Nom: $nom, Age: $ »

// Multi-ligne
val texte = «  » »
|Ligne 1
|Ligne 2
«  » »
.trimMargin()

// Méthodes
s.length // 11
s.isEmpty() // false
s.uppercase() // « HELLO WORLD »
s.lowercase() // « hello world »
s.contains(« World ») // true
s.startsWith(« He ») // true
s.endsWith(« ld ») // true
s.replace(« World », « Kotlin ») // « Hello Kotlin »
s.split( » « ) // [« Hello », « World »]
s.trim()
s.substring(0, 5) // « Hello »
s.toIntOrNull() // null (safe conversion)

// Conversion
« 42 ».toInt() // 42 (throws si invalide)
« 42 ».toIntOrNull() // 42 ou null (safe)
42.toString() // « 42 »

CHAPITRE 03

Fonctions

🔧 Fonctions en Kotlin
// Fonction standard
fun add(a: Int, b: Int): Int

// Single-expression (= au lieu de )
fun add(a: Int, b: Int) = a + b

// Paramètres par défaut
fun greet(name: String, greeting: String = « Bonjour ») =
« $greeting $name ! »

greet(« Alice ») // « Bonjour Alice ! »
greet(« Alice », « Salut ») // « Salut Alice ! »

// Named arguments
greet(greeting = « Hey », name = « Bob »)

// Varargs
fun sum(vararg nums: Int): Int = nums.sum()
sum(1, 2, 3) // 6

// Unit = void (type de retour par défaut)
fun log(msg: String): Unit
fun log(msg: String) // Unit implicite

Les paramètres par défaut remplacent la surcharge de méthodes. En Java, il faut écrire 3 constructeurs pour 3 combinaisons de paramètres. En Kotlin, une seule fonction avec des défauts suffit.

⚡ Extension functions
// Ajouter des méthodes à un type existant (sans modifier la classe)
fun String.addExclamation() = « $this! »
« Hello ».addExclamation() // « Hello! »

fun Int.isEven() = this % 2 == 0
4.isEven() // true

fun <T> List<T>.secondOrNull(): T? = getOrNull(1)

Les extension functions sont l'une des fonctionnalités les plus puissantes de Kotlin. Elles permettent d'enrichir des classes existantes (même de la librairie standard) sans héritage ni modification.

CHAPITRE 04

Structures de contrôle

🔀 if, when, for
// if est une EXPRESSION (retourne une valeur)
val label = if (age >= 18) « majeur » else « mineur »

// when — le switch surpuissant de Kotlin (expression aussi)
val label = when (age)

// when sans argument (remplace if/else if)
val label = when

// when avec type check (smart cast)
fun describe(x: Any): String = when (x)

// for
for (i in 0..4) // 0, 1, 2, 3, 4 (inclusif)
for (i in 0 until 5) // 0, 1, 2, 3, 4 (exclusif)
for (i in 10 downTo 0 step 2) // 10, 8, 6, 4, 2, 0
for (item in list)
for ((i, v) in list.withIndex())
for ((key, value) in map)

CHAPITRE 05

POO : Classes et objets

🏛️ Classes
// Classe avec primary constructor
class Person(val name: String, var age: Int)

val alice = Person(« Alice », 25) // Pas de « new » !
alice.name // « Alice »
alice.age = 26

val/var dans le constructeur = déclare + initialise les propriétés en une seule ligne. En Java, il faudrait les champs, le constructeur, les getters et les setters — facilement 20 lignes. En Kotlin : 1 ligne.

📊 data class — le tueur de boilerplate
// data class génère automatiquement :
// equals(), hashCode(), toString(), copy(), componentN()
data class User(val name: String, val email: String, val age: Int)

val alice = User(« Alice », « alice@mail.com », 25)
println(alice) // User(name=Alice, email=alice@mail.com, age=25)

// copy — copie avec modification
val bob = alice.copy(name = « Bob », email = « bob@mail.com »)

// Destructuring
val (name, email, age) = alice

// Comparaison structurelle (pas de référence)
val alice2 = User(« Alice », « alice@mail.com », 25)
alice == alice2 // true ✅ (comparaison par valeur)

En Java, la même data class nécessiterait ~60 lignes (champs, constructeur, getters, equals, hashCode, toString). En Kotlin : 1 ligne. C'est le type le plus utilisé pour transporter des données.

CHAPITRE 06

Héritage et interfaces

🔗 Héritage
// Les classes sont FINAL par défaut (il faut open pour hériter)
open class Animal(val name: String)

class Dog(name: String) : Animal(name)

class Cat(name: String) : Animal(name)

📋 Interfaces
interface Drawable

interface Clickable

// Implémentation multiple
class Button : Drawable, Clickable

// Abstract class
abstract class Shape

🧠 Quiz
Pourquoi les classes sont-elles final par défaut en Kotlin ?
Pour encourager la composition plutôt que l'héritage (Effective Java Item 19). L'héritage peut être dangereux si la classe de base n'a pas été conçue pour. open force le développeur à être explicite.

CHAPITRE 07

Null Safety

🛡️ Le système de types nullable
// Par défaut, RIEN ne peut être null
var name: String = « Alice »
name = null // ❌ Erreur de compilation !

// String? = nullable (peut être null)
var name: String? = « Alice »
name = null // ✅ OK

// ⚠️ On ne peut PAS appeler des méthodes directement sur un nullable
name.length // ❌ Erreur — name est peut-être null

// Safe call (?.) — retourne null si null
name?.length // Int? (null si name est null)

// Elvis operator (??) — valeur par défaut
val len = name?.length ?: 0

// Safe call chaîné
val city = user?.address?.city ?: « Inconnue »

// let — exécuter un bloc si non-null
name?.let

// Smart cast — le compilateur sait que ce n'est plus null
if (name != null)

// !! — force non-null (DANGEREUX — NullPointerException si null)
name!!.length // ⚠️ Crash si null !

Le null safety de Kotlin élimine les NullPointerException à la compilation. C'est le problème n°1 en Java (appelé le « billion dollar mistake »). En Kotlin, le compilateur force la gestion de null avant d'utiliser une valeur.

CHAPITRE 08

Collections et fonctions

📋 List, Set, Map
// IMMUABLES par défaut (lecture seule)
val nums: List<Int> = listOf(1, 2, 3)
val tags: Set<String> = setOf(« a », « b », « a ») //
val ages: Map<String, Int> = mapOf(« Alice » to 25, « Bob » to 30)

// MUTABLES
val nums = mutableListOf(1, 2, 3)
nums.add(4)
nums.removeAt(0)
nums += 5

val ages = mutableMapOf(« Alice » to 25)
ages[« Bob »] = 30
ages.getOrDefault(« Eve », 0) // 0

// Propriétés communes
nums.size // 4
nums.isEmpty() // false
nums.contains(3) // true
3 in nums // true (operator)
nums.first() // 2
nums.last() // 5

Immuable par défaut. listOf, setOf, mapOf créent des collections en lecture seule. Utilisez les versions mutable uniquement quand vous devez modifier la collection. C'est plus sûr pour la concurrence.

🌊 Lambdas et méthodes fonctionnelles
val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// Lambda syntax :
val double =
// it = paramètre implicite (un seul param)
val double =

// Chaîner
val result = nums
.filter // [2, 4, 6, 8, 10]
.map // [6, 12, 18, 24, 30]
.take(3) // [6, 12, 18]

// Autres méthodes essentielles
nums.map // Transformer
nums.filter // Filtrer
nums.reduce // Réduire (55)
nums.fold(0) // Réduire avec valeur initiale
nums.sum() // 55
nums.average() // 5.5
nums.min() // 1
nums.max() // 10
nums.count // 5
nums.any // true
nums.all // true
nums.none // true
nums.sortedDescending() // [10, 9, …, 1]
nums.distinct() // Dédupliquer
nums.groupBy //
nums.flatMap // [1,2, 2,4, 3,6, …]
nums.associate //
nums.joinToString(« , « ) // « 1, 2, 3, … »

// Sequence (lazy — pour les grandes collections)
nums.asSequence()
.filter
.map
.take(3)
.toList()

CHAPITRE 09

Classes avancées

🎭 Enum, Sealed, Object
// Enum class
enum class Color(val hex: String)

// Sealed class — enum sur stéroïdes (variantes avec données)
sealed class Result

// when exhaustif sur sealed (pas besoin de else)
fun handle(result: Result) = when (result)

// object — singleton
object Database
Database.connect()

// companion object — méthodes « statiques »
class User(val name: String)
val u = User.create(« Alice »)

Sealed classes = l'arme secrète de Kotlin. Comme les enums avec associated values de Swift ou Rust. Le compilateur vérifie que when couvre tous les cas — ajoutez une variante et le code ne compile plus tant qu'elle n'est pas gérée.

CHAPITRE 10

Generics

🔄 Generics
// Fonction générique
fun <T> firstOrNull(list: List<T>): T? = list.firstOrNull()

// Avec contrainte
fun <T : Comparable<T>> max(a: T, b: T): T = if (a > b) a else b

// Classe générique
class Stack<T>

val stack = Stack<Int>()
stack.push(42)

// Variance
// out T = covariant (producteur — List)
// in T = contravariant (consommateur — Comparable)
fun printAll(items: List<out Any>)

// Reified type (inline function — accès au type T au runtime)
inline fun <reified T> isType(value: Any): Boolean = value is T

CHAPITRE 11

Coroutines

⚡ Coroutines — concurrence légère
// Dépendance : implementation(« org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 »)

import kotlinx.coroutines.*

// suspend = fonction qui peut être suspendue (non-bloquante)
suspend fun fetchUser(id: Int): User

// launch — fire-and-forget (Job)
fun main() = runBlocking

// async — retourne un résultat (Deferred)
fun main() = runBlocking

// withContext — changer de dispatcher
suspend fun heavyComputation(): Int = withContext(Dispatchers.Default)

Les coroutines Kotlin sont comme les goroutines Go ou async/await JavaScript — mais intégrées dans le langage. Elles sont ultra-légères (pas de thread par coroutine) et annulables via les scopes structurés.

🔄 Dispatchers et Structured Concurrency
// Dispatchers
Dispatchers.Main // Thread principal (Android UI)
Dispatchers.IO // I/O (réseau, fichiers)
Dispatchers.Default // CPU-intensive

// Structured concurrency — le scope gère le cycle de vie
suspend fun loadDashboard() = coroutineScope

// Flow — stream asynchrone (comme Observable/RxJava)
fun countDown(): Flow<Int> = flow

countDown()
.filter
.collect

CHAPITRE 12

Kotlin idiomatique

🔧 Scope functions : let, run, with, apply, also
val user = User(« Alice »)

// let — transformer un résultat, chaîner sur nullable
val len = name?.let

// apply — configurer un objet (retourne l'objet)
val user = User().apply

// also — side effect (retourne l'objet)
val nums = mutableListOf(1, 2, 3)
.also

// run — exécuter un bloc et retourner le résultat
val result = user.run

// with — comme run mais pas en extension
val info = with(user)

FonctionRéf objetRetourneUsage typique
letitLambda resultNullable check, transformer
runthisLambda resultInitialiser + calculer
withthisLambda resultAppeler plusieurs méthodes
applythisObjectConfigurer un objet
alsoitObjectSide effects (logging)
⚡ Kotlin idiomatique
// Delegation — by lazy (initialisation paresseuse)
val heavyData: List<String> by lazy

// Destructuring
val (name, age) = Person(« Alice », 25)
for ((key, value) in map)

// Type aliases
typealias UserMap = Map<String, List<User>>

// Operator overloading
data class Point(val x: Double, val y: Double)
Point(1.0, 2.0) + Point(3.0, 4.0) // Point(4.0, 6.0)

// Kotlin DSL (type-safe builders)
val html = buildString

✅ Bonnes pratiques

✅ À FAIRE
val par défaut (immutabilité)
data class pour les DTO
sealed class pour les états
?. et ?: pour le null safety
when au lieu de if/else if
• Extension functions
• Coroutines pour l'async
listOf par défaut (immuable)
• Named arguments pour la lisibilité

❌ À ÉVITER
!! (force non-null) en production
var quand val suffit
mutableListOf quand listOf suffit
• Java-style getters/setters
lateinit abusif
• Scope functions imbriquées
• Ignorer les warnings
Any au lieu de generics
• Callbacks au lieu de coroutines

🧠 Quiz
Quelle est la différence entre sealed class et enum class ?
Enum = chaque cas est une instance unique (singleton). Sealed class = chaque cas peut être une classe avec ses propres données (data class, object). Sealed class est plus flexible pour les états complexes.
Qu'est-ce que le smart cast ?
Après un is check ou un != null check, le compilateur cast automatiquement la variable vers le type vérifié. Pas besoin de cast explicite. Ex : après if (x is String), x est utilisable comme String.

Cours Kotlin Complet — Null Safety, Coroutines et écosystème Android

Référence : kotlinlang.org | Android Kotlin