Feuille de chaleur de Kotlin¶
Kotlin - langage de programmation moderne pour Android et au-delà
Kotlin est un langage de programmation moderne, concis et sûr qui fonctionne sur le JVM et est entièrement interopérable avec Java. C'est la langue préférée de Google pour le développement Android et est également utilisé pour le développement côté serveur, web et multiplateforme.
Sommaire¶
- [Installation] (#installation)
- [Syntaxe de base] (#basic-syntax)
- [Variables et constantes] (#variables-and-constants)
- Types de données
- [Flux de contrôle] (#control-flow)
- [Fonctions] (#functions)
- [Classes et objets] (#classes-and-objects)
- [Héritage] (#inheritance)
- [Interfaces] (#interfaces)
- [Classes de données] (#data-classes)
- [Classes scellées] (#sealed-classes)
- [Génériques] (#generics)
- [Collectes] (#collections)
- [Sécurité nucléaire] (#null-safety)
- [Fonctions d'extension] (#extension-functions)
- [Fonctions supérieures de commande] (#higher-order-functions)
- [Coroutines] (#coroutines)
- [Développement Android] (#android-development)
- [Meilleures pratiques] (#best-practices)
Installation¶
Configuration Android Studio¶
# Download Android Studio from developer.android.com
# Kotlin is included by default
# Create new Kotlin project
# File > New > New Project > Select Kotlin
# Verify Kotlin version in build.gradle
kotlin_version = '1.8.20'
Configuration IntelliJ IDEA¶
# Download IntelliJ IDEA
# Kotlin plugin is bundled
# Create new Kotlin project
# File > New > Project > Kotlin
# Command line compilation
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
```_
### Configuration de la ligne de commande
```bash
# Install Kotlin compiler
# macOS with Homebrew
brew install kotlin
# Linux/Windows - download from kotlinlang.org
# Extract and add to PATH
# Verify installation
kotlin -version
kotlinc -version
# REPL (Read-Eval-Print Loop)
kotlinc-jvm
# Compile and run
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# Direct execution
kotlin hello.kt
```_
## Syntaxe de base
### Bonjour Monde
```kotlin
// Simple main function
fun main() {
println("Hello, World!")
}
// Main with arguments
fun main(args: Array<String>) {
println("Hello, ${args.getOrNull(0) ?: "World"}!")
}
// Top-level function
fun greet(name: String) {
println("Hello, $name!")
}
fun main() {
greet("Kotlin")
}
// Comments
// This is a single-line comment
/*
* This is a multi-line comment
* that spans multiple lines
*/
/**
* This is a documentation comment
* @param name The name to greet
* @return A greeting string
*/
fun createGreeting(name: String): String {
return "Hello, $name!"
}
Paquet et importations¶
// Package declaration
package com.example.myapp
// Imports
import kotlin.math.*
import java.util.Date
import android.content.Context
import com.example.utils.Helper as UtilHelper
// Import all from package
import kotlin.collections.*
// Import specific function
import kotlin.math.sqrt
class MyClass {
fun useImports() {
val result = sqrt(16.0)
val date = Date()
val helper = UtilHelper()
}
}
Variables et constantes¶
Déclaration variable¶
// Mutable variables (var)
var name = "John" // Type inferred as String
var age: Int = 25 // Explicit type
var height = 5.9 // Type inferred as Double
// Immutable variables (val)
val pi = 3.14159 // Type inferred as Double
val maxUsers: Int = 100 // Explicit type
val appName = "MyApp" // Type inferred as String
// Late initialization
lateinit var database: Database
val lazyValue: String by lazy {
"Computed only when accessed"
}
// Nullable variables
var nullableName: String? = null
var nonNullName: String = "John"
// Multiple variable declaration
val (x, y, z) = Triple(1, 2, 3)
val (first, second) = Pair("Hello", "World")
Type Inférence et types explicites¶
// Type inference
val number = 42 // Int
val decimal = 3.14 // Double
val text = "Hello" // String
val flag = true // Boolean
// Explicit types
val explicitInt: Int = 42
val explicitDouble: Double = 3.14
val explicitString: String = "Hello"
val explicitBoolean: Boolean = true
// Collections with type inference
val numbers = listOf(1, 2, 3, 4, 5) // List<Int>
val names = mutableListOf("Alice", "Bob") // MutableList<String>
val ages = mapOf("Alice" to 30, "Bob" to 25) // Map<String, Int>
// Explicit collection types
val explicitList: List<String> = listOf("a", "b", "c")
val explicitMap: Map<String, Int> = mapOf("key" to 1)
Types de données¶
Types de base¶
// Numbers
val byte: Byte = 127
val short: Short = 32767
val int: Int = 2147483647
val long: Long = 9223372036854775807L
val float: Float = 3.14f
val double: Double = 3.141592653589793
// Characters and Strings
val char: Char = 'A'
val string: String = "Hello, Kotlin!"
// Booleans
val isTrue: Boolean = true
val isFalse: Boolean = false
// Arrays
val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)
val stringArray: Array<String> = arrayOf("a", "b", "c")
val nullableArray: Array<String?> = arrayOfNulls(5)
// Type conversion
val intValue = 42
val longValue = intValue.toLong()
val doubleValue = intValue.toDouble()
val stringValue = intValue.toString()
// Number literals
val binary = 0b1010 // Binary
val hex = 0xFF // Hexadecimal
val scientific = 1.23e10 // Scientific notation
Opérations de chaînes¶
// String templates
val name = "Alice"
val age = 30
val message = "My name is $name and I'm $age years old"
val calculation = "2 + 3 = ${2 + 3}"
// Multi-line strings
val poem = """
Roses are red,
Violets are blue,
Kotlin is awesome,
And so are you!
""".trimIndent()
// Raw strings
val regex = """[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"""
// String operations
val text = "Hello, Kotlin!"
println(text.length) // 14
println(text.uppercase()) // HELLO, KOTLIN!
println(text.lowercase()) // hello, kotlin!
println(text.substring(0, 5)) // Hello
println(text.contains("Kotlin")) // true
println(text.startsWith("Hello")) // true
println(text.endsWith("!")) // true
println(text.replace("Kotlin", "World")) // Hello, World!
// String comparison
val str1 = "hello"
val str2 = "HELLO"
println(str1 == str2) // false
println(str1.equals(str2, ignoreCase = true)) // true
Aperçu général¶
// Lists
val readOnlyList = listOf("apple", "banana", "cherry")
val mutableList = mutableListOf("apple", "banana")
mutableList.add("cherry")
// Sets
val readOnlySet = setOf(1, 2, 3, 3, 4) // {1, 2, 3, 4}
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
// Maps
val readOnlyMap = mapOf("a" to 1, "b" to 2, "c" to 3)
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3
// Ranges
val range1 = 1..10 // 1 to 10 (inclusive)
val range2 = 1 until 10 // 1 to 9 (exclusive)
val range3 = 10 downTo 1 // 10 to 1 (descending)
val range4 = 1..10 step 2 // 1, 3, 5, 7, 9
// Checking membership
println(5 in range1) // true
println('x' in 'a'..'z') // true
Débit de contrôle¶
Déclarations conditionnelles¶
// if expression
val max = if (a > b) a else b
// if-else chain
val result = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else if (score >= 70) {
"C"
} else {
"F"
}
// when expression (similar to switch)
val dayOfWeek = when (day) {
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6, 7 -> "Weekend"
else -> "Invalid day"
}
// when with ranges
val grade = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
in 60..69 -> "D"
else -> "F"
}
// when with type checking
fun describe(obj: Any): String = when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
// when without argument
val temperature = 25
val description = when {
temperature < 0 -> "Freezing"
temperature < 20 -> "Cold"
temperature < 30 -> "Warm"
else -> "Hot"
}
Boucles¶
// for loop with range
for (i in 1..5) {
println(i)
}
// for loop with until
for (i in 1 until 5) {
println(i) // 1, 2, 3, 4
}
// for loop with step
for (i in 1..10 step 2) {
println(i) // 1, 3, 5, 7, 9
}
// for loop with downTo
for (i in 10 downTo 1 step 2) {
println(i) // 10, 8, 6, 4, 2
}
// for loop with collections
val fruits = listOf("apple", "banana", "cherry")
for (fruit in fruits) {
println(fruit)
}
// for loop with indices
for (i in fruits.indices) {
println("$i: ${fruits[i]}")
}
// for loop with withIndex
for ((index, value) in fruits.withIndex()) {
println("$index: $value")
}
// while loop
var count = 0
while (count < 5) {
println("Count: $count")
count++
}
// do-while loop
var number = 0
do {
println("Number: $number")
number++
} while (number < 3)
// Loop control
for (i in 1..10) {
if (i == 3) continue // Skip iteration
if (i == 8) break // Exit loop
println(i)
}
// Labeled breaks and continues
outer@ for (i in 1..3) {
inner@ for (j in 1..3) {
if (i == 2 && j == 2) break@outer
println("$i, $j")
}
}
Fonctions¶
Fonctions de base¶
// Simple function
fun greet() {
println("Hello!")
}
// Function with parameters
fun greet(name: String) {
println("Hello, $name!")
}
// Function with return type
fun add(a: Int, b: Int): Int {
return a + b
}
// Single-expression function
fun multiply(a: Int, b: Int): Int = a * b
// Function with default parameters
fun greet(name: String, greeting: String = "Hello") {
println("$greeting, $name!")
}
// Function with named arguments
fun createUser(name: String, age: Int, email: String) {
// Implementation
}
// Calling with named arguments
createUser(name = "Alice", email = "alice@example.com", age = 30)
// Vararg parameters
fun sum(vararg numbers: Int): Int {
return numbers.sum()
}
val result = sum(1, 2, 3, 4, 5)
// Spreading arrays
val array = intArrayOf(1, 2, 3)
val total = sum(*array)
Types de fonctions et fonctions de commande supérieure¶
// Function type
val operation: (Int, Int) -> Int = { a, b -> a + b }
// Higher-order function
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = calculate(5, 3) { x, y -> x * y }
// Function returning function
fun getOperation(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"multiply" -> { a, b -> a * b }
else -> { a, b -> 0 }
}
}
val addFunction = getOperation("add")
val sum = addFunction(5, 3)
// Lambda expressions
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }
val sum = numbers.reduce { acc, n -> acc + n }
// Lambda with multiple parameters
val pairs = listOf(Pair(1, 2), Pair(3, 4), Pair(5, 6))
val sums = pairs.map { (first, second) -> first + second }
// Trailing lambda syntax
numbers.forEach { number ->
println(number)
}
// it parameter (single parameter lambda)
numbers.forEach {
println(it)
}
Fonctions en ligne¶
// Inline function
inline fun measureTime(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
val end = System.currentTimeMillis()
return end - start
}
// Usage
val time = measureTime {
// Some operation
Thread.sleep(1000)
}
// noinline parameter
inline fun processData(
data: List<String>,
noinline logger: (String) -> Unit,
processor: (String) -> String
) {
data.forEach { item ->
logger("Processing: $item")
processor(item)
}
}
// crossinline parameter
inline fun runAsync(crossinline block: () -> Unit) {
Thread {
block()
}.start()
}
Classes et objets¶
Classes de base¶
// Simple class
class Person {
var name: String = ""
var age: Int = 0
fun introduce() {
println("Hi, I'm $name and I'm $age years old")
}
}
// Class with primary constructor
class Person(val name: String, var age: Int) {
fun introduce() {
println("Hi, I'm $name and I'm $age years old")
}
}
// Class with init block
class Person(name: String, age: Int) {
val name: String
var age: Int
init {
this.name = name.uppercase()
this.age = if (age >= 0) age else 0
println("Person created: ${this.name}")
}
}
// Secondary constructors
class Person(val name: String) {
var age: Int = 0
var email: String = ""
constructor(name: String, age: Int) : this(name) {
this.age = age
}
constructor(name: String, age: Int, email: String) : this(name, age) {
this.email = email
}
}
// Usage
val person1 = Person("Alice")
val person2 = Person("Bob", 30)
val person3 = Person("Charlie", 25, "charlie@example.com")
Propriétés¶
class Rectangle(val width: Double, val height: Double) {
// Computed property
val area: Double
get() = width * height
// Property with custom getter and setter
var isSquare: Boolean
get() = width == height
set(value) {
if (value) {
// Make it a square by setting height to width
// Note: This is just an example, height is val so this won't work
println("Cannot make rectangle a square - height is immutable")
}
}
// Property with backing field
var color: String = "white"
set(value) {
field = value.lowercase()
}
// Late-initialized property
lateinit var description: String
fun initializeDescription() {
description = "A $color rectangle with area $area"
}
}
// Property delegation
class User {
var name: String by Delegates.observable("") { prop, old, new ->
println("$old -> $new")
}
val lazyValue: String by lazy {
println("Computed!")
"Hello"
}
}
Modificateurs de visibilité¶
// Visibility modifiers
class Example {
private val privateProperty = "Only visible within this class"
protected val protectedProperty = "Visible in this class and subclasses"
internal val internalProperty = "Visible within the same module"
public val publicProperty = "Visible everywhere" // public is default
private fun privateFunction() { }
protected fun protectedFunction() { }
internal fun internalFunction() { }
fun publicFunction() { } // public is default
}
// Top-level declarations
private fun topLevelPrivate() { } // Visible within the same file
internal fun topLevelInternal() { } // Visible within the same module
fun topLevelPublic() { } // Visible everywhere
Déclarations et expressions d'objets¶
// Object declaration (Singleton)
object DatabaseManager {
fun connect() {
println("Connecting to database...")
}
fun disconnect() {
println("Disconnecting from database...")
}
}
// Usage
DatabaseManager.connect()
// Object expression (Anonymous object)
val clickListener = object : View.OnClickListener {
override fun onClick(v: View?) {
println("Button clicked!")
}
}
// Object expression with multiple interfaces
val handler = object : Runnable, Serializable {
override fun run() {
println("Running...")
}
}
// Companion object
class MyClass {
companion object {
const val CONSTANT = "Hello"
fun create(): MyClass {
return MyClass()
}
}
}
// Usage
val instance = MyClass.create()
println(MyClass.CONSTANT)
Héritage¶
Héritage de base¶
// Base class (open for inheritance)
open class Animal(val name: String) {
open fun makeSound() {
println("$name makes a sound")
}
open val species: String = "Unknown"
}
// Derived class
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("$name barks")
}
override val species: String = "Canis lupus"
fun fetch() {
println("$name fetches the ball")
}
}
class Cat(name: String, val breed: String) : Animal(name) {
override fun makeSound() {
println("$name meows")
}
override val species: String = "Felis catus"
fun purr() {
println("$name purrs")
}
}
// Usage
val dog = Dog("Buddy")
val cat = Cat("Whiskers", "Persian")
dog.makeSound() // Buddy barks
cat.makeSound() // Whiskers meows
Classes abstraites¶
// Abstract class
abstract class Shape {
abstract val area: Double
abstract val perimeter: Double
abstract fun draw()
// Concrete method
fun describe() {
println("This shape has area $area and perimeter $perimeter")
}
}
class Circle(val radius: Double) : Shape() {
override val area: Double
get() = Math.PI * radius * radius
override val perimeter: Double
get() = 2 * Math.PI * radius
override fun draw() {
println("Drawing a circle with radius $radius")
}
}
class Rectangle(val width: Double, val height: Double) : Shape() {
override val area: Double
get() = width * height
override val perimeter: Double
get() = 2 * (width + height)
override fun draw() {
println("Drawing a rectangle ${width}x${height}")
}
}
Appel à l'implémentation Superclass¶
open class Vehicle(val brand: String) {
open fun start() {
println("$brand vehicle is starting...")
}
open fun stop() {
println("$brand vehicle is stopping...")
}
}
class Car(brand: String, val model: String) : Vehicle(brand) {
override fun start() {
super.start() // Call superclass implementation
println("$brand $model engine is running")
}
override fun stop() {
println("$brand $model is parking")
super.stop() // Call superclass implementation
}
}
// Usage
val car = Car("Toyota", "Camry")
car.start()
// Output:
// Toyota vehicle is starting...
// Toyota Camry engine is running
Interfaces¶
Interfaces de base¶
// Interface definition
interface Drawable {
fun draw()
fun getArea(): Double
}
interface Clickable {
fun click()
fun showTooltip() = println("Tooltip") // Default implementation
}
// Implementing interfaces
class Button : Drawable, Clickable {
override fun draw() {
println("Drawing a button")
}
override fun getArea(): Double {
return 100.0 // Example area
}
override fun click() {
println("Button clicked!")
}
// showTooltip() is inherited from Clickable with default implementation
}
// Interface with properties
interface Named {
val name: String
val displayName: String
get() = name.uppercase() // Property with default getter
}
class User(override val name: String) : Named {
// displayName is automatically available
}
Conflits d'interface¶
interface A {
fun foo() {
println("A")
}
fun bar()
}
interface B {
fun foo() {
println("B")
}
fun bar() {
println("bar from B")
}
}
class C : A, B {
override fun foo() {
super<A>.foo() // Call A's implementation
super<B>.foo() // Call B's implementation
}
override fun bar() {
super<B>.bar() // Call B's implementation
}
}
Interfaces fonctionnelles (SAM)¶
// Functional interface
fun interface StringProcessor {
fun process(input: String): String
}
// Usage with lambda
val processor = StringProcessor { input ->
input.uppercase()
}
// Or as a function parameter
fun processText(text: String, processor: StringProcessor): String {
return processor.process(text)
}
val result = processText("hello") { it.uppercase() }
// Built-in functional interfaces
val runnable = Runnable {
println("Running...")
}
val comparator = Comparator<String> { a, b ->
a.length.compareTo(b.length)
}
Classes de données¶
Classes de données de base¶
// Data class
data class User(val name: String, val age: Int, val email: String)
// Automatically generated methods:
// - equals() and hashCode()
// - toString()
// - copy()
// - componentN() functions for destructuring
val user1 = User("Alice", 30, "alice@example.com")
val user2 = User("Alice", 30, "alice@example.com")
println(user1 == user2) // true (structural equality)
println(user1) // User(name=Alice, age=30, email=alice@example.com)
// Copy with modifications
val user3 = user1.copy(age = 31)
println(user3) // User(name=Alice, age=31, email=alice@example.com)
// Destructuring
val (name, age, email) = user1
println("Name: $name, Age: $age, Email: $email")
Classe de données avec comportement personnalisé¶
data class Point(val x: Double, val y: Double) {
// Additional methods
fun distanceFromOrigin(): Double {
return kotlin.math.sqrt(x * x + y * y)
}
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
val point1 = Point(3.0, 4.0)
val point2 = Point(1.0, 2.0)
println(point1.distanceFromOrigin()) // 5.0
val sum = point1 + point2
println(sum) // Point(x=4.0, y=6.0)
// Data class with validation
data class Email(val address: String) {
init {
require(address.contains("@")) { "Invalid email address" }
}
}
// Data class with computed properties
data class Rectangle(val width: Double, val height: Double) {
val area: Double
get() = width * height
val perimeter: Double
get() = 2 * (width + height)
}
Collecte de données¶
data class Product(val id: Int, val name: String, val price: Double)
val products = listOf(
Product(1, "Laptop", 999.99),
Product(2, "Mouse", 29.99),
Product(3, "Keyboard", 79.99)
)
// Using data classes in collections
val expensiveProducts = products.filter { it.price > 50.0 }
val productNames = products.map { it.name }
val totalPrice = products.sumOf { it.price }
// Grouping
val productsByPriceRange = products.groupBy { product ->
when {
product.price < 50 -> "Cheap"
product.price < 100 -> "Medium"
else -> "Expensive"
}
}
// Finding
val laptop = products.find { it.name == "Laptop" }
val hasExpensiveItem = products.any { it.price > 500 }
Classes scellées¶
Classes de base scellées¶
// Sealed class for representing states
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// Usage with when expression
fun handleResult(result: Result<String>) {
when (result) {
is Result.Success -> println("Data: ${result.data}")
is Result.Error -> println("Error: ${result.exception.message}")
is Result.Loading -> println("Loading...")
// No else clause needed - compiler knows all cases are covered
}
}
// Example usage
val successResult = Result.Success("Hello, World!")
val errorResult = Result.Error(Exception("Network error"))
val loadingResult = Result.Loading
handleResult(successResult)
handleResult(errorResult)
handleResult(loadingResult)
Classes scellées pour la navigation¶
sealed class Screen(val route: String) {
object Home : Screen("home")
object Profile : Screen("profile")
data class UserDetail(val userId: String) : Screen("user/$userId")
data class ProductDetail(val productId: String) : Screen("product/$productId")
}
fun navigate(screen: Screen) {
when (screen) {
is Screen.Home -> println("Navigating to home")
is Screen.Profile -> println("Navigating to profile")
is Screen.UserDetail -> println("Navigating to user ${screen.userId}")
is Screen.ProductDetail -> println("Navigating to product ${screen.productId}")
}
}
// Usage
navigate(Screen.Home)
navigate(Screen.UserDetail("123"))
navigate(Screen.ProductDetail("abc"))
Interfaces scellées¶
sealed interface NetworkResult
data class Success(val data: String) : NetworkResult
data class Error(val code: Int, val message: String) : NetworkResult
object Loading : NetworkResult
fun processNetworkResult(result: NetworkResult) {
when (result) {
is Success -> println("Success: ${result.data}")
is Error -> println("Error ${result.code}: ${result.message}")
is Loading -> println("Loading...")
}
}
Générique¶
Générique de base¶
// Generic class
class Box<T>(val value: T) {
fun get(): T = value
fun <U> transform(transformer: (T) -> U): Box<U> {
return Box(transformer(value))
}
}
val intBox = Box(42)
val stringBox = Box("Hello")
val doubledBox = intBox.transform { it * 2 }
val upperBox = stringBox.transform { it.uppercase() }
// Generic function
fun <T> swap(a: T, b: T): Pair<T, T> {
return Pair(b, a)
}
val swapped = swap("hello", "world")
val swappedNumbers = swap(1, 2)
// Multiple type parameters
class Pair<A, B>(val first: A, val second: B) {
fun <C> mapFirst(transform: (A) -> C): Pair<C, B> {
return Pair(transform(first), second)
}
fun <C> mapSecond(transform: (B) -> C): Pair<A, C> {
return Pair(first, transform(second))
}
}
Type Contraintes¶
// Upper bound constraint
fun <T : Number> sum(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}
val intSum = sum(1, 2)
val doubleSum = sum(1.5, 2.5)
// val stringSum = sum("a", "b") // Compilation error
// Multiple constraints
interface Drawable {
fun draw()
}
interface Clickable {
fun click()
}
fun <T> handleUI(element: T) where T : Drawable, T : Clickable {
element.draw()
element.click()
}
// Constraint with class and interface
open class View
interface OnClickListener
fun <T> setupView(view: T) where T : View, T : OnClickListener {
// T must extend View and implement OnClickListener
}
Écart¶
// Covariance (out)
interface Producer<out T> {
fun produce(): T
}
class StringProducer : Producer<String> {
override fun produce(): String = "Hello"
}
// Can assign Producer<String> to Producer<Any>
val stringProducer: Producer<String> = StringProducer()
val anyProducer: Producer<Any> = stringProducer
// Contravariance (in)
interface Consumer<in T> {
fun consume(item: T)
}
class AnyConsumer : Consumer<Any> {
override fun consume(item: Any) {
println("Consuming: $item")
}
}
// Can assign Consumer<Any> to Consumer<String>
val anyConsumer: Consumer<Any> = AnyConsumer()
val stringConsumer: Consumer<String> = anyConsumer
// Invariance (default)
class MutableBox<T>(var value: T) {
fun get(): T = value
fun set(value: T) {
this.value = value
}
}
// Star projection
fun printList(list: List<*>) {
for (item in list) {
println(item)
}
}
Paramètres de type rectifiés¶
// Reified type parameters (only in inline functions)
inline fun <reified T> isInstance(value: Any): Boolean {
return value is T
}
val result1 = isInstance<String>("hello") // true
val result2 = isInstance<Int>("hello") // false
// Reified with class access
inline fun <reified T> createInstance(): T? {
return try {
T::class.java.getDeclaredConstructor().newInstance()
} catch (e: Exception) {
null
}
}
// Reified with JSON parsing (example)
inline fun <reified T> parseJson(json: String): T {
// Implementation would use T::class.java
TODO("Implementation depends on JSON library")
}
Recouvrement¶
Listes¶
// Immutable list
val readOnlyList = listOf("apple", "banana", "cherry")
val emptyList = emptyList<String>()
// Mutable list
val mutableList = mutableListOf("apple", "banana")
mutableList.add("cherry")
mutableList.addAll(listOf("date", "elderberry"))
mutableList.remove("banana")
mutableList.removeAt(0)
// List operations
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }
val sum = numbers.sum()
val max = numbers.maxOrNull()
val sorted = numbers.sortedDescending()
// List access
val first = numbers.first()
val last = numbers.last()
val secondElement = numbers[1]
val safeAccess = numbers.getOrNull(10) // null if index out of bounds
// List slicing
val sublist = numbers.subList(1, 4) // [2, 3, 4]
val take = numbers.take(3) // [1, 2, 3]
val drop = numbers.drop(2) // [3, 4, 5]
// List transformation
val fruits = listOf("apple", "banana", "cherry")
val lengths = fruits.map { it.length }
val uppercase = fruits.map { it.uppercase() }
val indexed = fruits.mapIndexed { index, fruit -> "$index: $fruit" }
// Flattening
val nestedLists = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))
val flattened = nestedLists.flatten() // [1, 2, 3, 4, 5, 6]
val words = listOf("hello", "world")
val characters = words.flatMap { it.toList() } // [h, e, l, l, o, w, o, r, l, d]
Ensembles¶
// Immutable set
val readOnlySet = setOf(1, 2, 3, 3, 4) // {1, 2, 3, 4}
val emptySet = emptySet<Int>()
// Mutable set
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
mutableSet.addAll(setOf(5, 6))
mutableSet.remove(2)
// Set operations
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
val union = set1 union set2 // {1, 2, 3, 4, 5, 6}
val intersection = set1 intersect set2 // {3, 4}
val difference = set1 subtract set2 // {1, 2}
// Set membership
val contains = 3 in set1 // true
val containsAll = set1.containsAll(setOf(1, 2)) // true
// Converting between collections
val listToSet = listOf(1, 2, 2, 3).toSet() // {1, 2, 3}
val setToList = setOf(1, 2, 3).toList() // [1, 2, 3]
Cartes¶
// Immutable map
val readOnlyMap = mapOf("a" to 1, "b" to 2, "c" to 3)
val emptyMap = emptyMap<String, Int>()
// Mutable map
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3
mutableMap.put("d", 4)
mutableMap.putAll(mapOf("e" to 5, "f" to 6))
mutableMap.remove("b")
// Map access
val value = readOnlyMap["a"] // 1 (nullable)
val safeValue = readOnlyMap.getValue("a") // 1 (throws if not found)
val defaultValue = readOnlyMap.getOrDefault("z", 0) // 0
val computedValue = mutableMap.getOrPut("g") { 7 }
// Map operations
val ages = mapOf("Alice" to 30, "Bob" to 25, "Charlie" to 35)
val names = ages.keys
val ageValues = ages.values
val entries = ages.entries
// Map transformation
val upperNames = ages.mapKeys { it.key.uppercase() }
val ageInMonths = ages.mapValues { it.value * 12 }
val filtered = ages.filter { it.value >= 30 }
// Map iteration
for ((name, age) in ages) {
println("$name is $age years old")
}
ages.forEach { (name, age) ->
println("$name: $age")
}
Opérations de recouvrement¶
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Filtering
val evens = numbers.filter { it % 2 == 0 }
val odds = numbers.filterNot { it % 2 == 0 }
val nonNulls = listOf(1, null, 2, null, 3).filterNotNull()
// Mapping
val doubled = numbers.map { it * 2 }
val strings = numbers.map { "Number: $it" }
// Reducing
val sum = numbers.reduce { acc, n -> acc + n }
val product = numbers.fold(1) { acc, n -> acc * n }
// Grouping
val words = listOf("apple", "banana", "cherry", "apricot", "blueberry")
val groupedByFirstLetter = words.groupBy { it.first() }
val groupedByLength = words.groupBy { it.length }
// Partitioning
val (evens2, odds2) = numbers.partition { it % 2 == 0 }
// Finding
val firstEven = numbers.find { it % 2 == 0 }
val lastOdd = numbers.findLast { it % 2 == 1 }
// Checking conditions
val allPositive = numbers.all { it > 0 }
val anyEven = numbers.any { it % 2 == 0 }
val noneNegative = numbers.none { it < 0 }
// Sorting
val sorted = numbers.sorted()
val sortedDesc = numbers.sortedDescending()
val sortedByLength = words.sortedBy { it.length }
val sortedByLengthDesc = words.sortedByDescending { it.length }
// Distinct
val duplicates = listOf(1, 2, 2, 3, 3, 3, 4)
val unique = duplicates.distinct()
val uniqueByLength = words.distinctBy { it.length }
// Zipping
val letters = listOf("a", "b", "c")
val zipped = numbers.zip(letters) // [(1, a), (2, b), (3, c)]
val zippedWithTransform = numbers.zip(letters) { num, letter -> "$num$letter" }
Sécurité Null¶
Types nullables¶
// Nullable vs non-nullable types
var nonNullString: String = "Hello"
var nullableString: String? = null
// nonNullString = null // Compilation error
nullableString = "World"
nullableString = null // OK
// Safe call operator
val length = nullableString?.length
val uppercase = nullableString?.uppercase()
// Chaining safe calls
class Person(val name: String?)
class Company(val ceo: Person?)
val company: Company? = Company(Person("John"))
val ceoNameLength = company?.ceo?.name?.length
// Elvis operator
val name = nullableString ?: "Default Name"
val length2 = nullableString?.length ?: 0
// Not-null assertion operator (use with caution)
val definitelyNotNull = nullableString!!
val length3 = nullableString!!.length
// Safe cast
val stringValue: Any = "Hello"
val safeString = stringValue as? String // null if cast fails
val unsafeString = stringValue as String // throws exception if cast fails
Vérifications non obligatoires¶
fun processString(str: String?) {
// Explicit null check
if (str != null) {
println(str.length) // Smart cast to non-null
}
// Safe call with let
str?.let { nonNullStr ->
println("Processing: $nonNullStr")
println("Length: ${nonNullStr.length}")
}
// Elvis operator with return
val length = str?.length ?: return
println("String length: $length")
// Elvis operator with throw
val upperCase = str?.uppercase() ?: throw IllegalArgumentException("String cannot be null")
}
// Multiple null checks
fun processPersonInfo(person: Person?) {
person?.let { p ->
p.name?.let { name ->
println("Person name: $name")
}
}
// Or using safe calls
person?.name?.let { name ->
println("Person name: $name")
}
}
Collections et sécurité Null¶
// Nullable collections vs collections of nullables
val nullableList: List<String>? = null
val listOfNullables: List<String?> = listOf("hello", null, "world")
val nullableListOfNullables: List<String?>? = null
// Safe operations on nullable collections
nullableList?.forEach { item ->
println(item)
}
val size = nullableList?.size ?: 0
// Filtering out nulls
val nonNullItems = listOfNullables.filterNotNull()
println(nonNullItems) // [hello, world]
// Map with null safety
val lengths = listOfNullables.mapNotNull { it?.length }
println(lengths) // [5, 5]
// Safe indexing
val firstItem = nullableList?.getOrNull(0)
val safeAccess = listOfNullables.getOrNull(10) // null
Types de plate-forme¶
// When interacting with Java code, types are platform types (T!)
// These can be treated as nullable or non-nullable
// Java method: public String getName() { return name; }
// In Kotlin, this becomes: fun getName(): String!
// You can treat it as nullable
val javaName = javaObject.getName()
val safeName = javaName?.uppercase()
// Or as non-nullable (your responsibility to ensure it's not null)
val definitelyName: String = javaObject.getName()
val upperName = definitelyName.uppercase()
// Best practice: explicitly declare nullability
val explicitlyNullable: String? = javaObject.getName()
val explicitlyNonNull: String = javaObject.getName() ?: ""
Fonctions d'extension¶
Fonctions d'extension de base¶
// Extension function for String
fun String.isPalindrome(): Boolean {
val cleaned = this.lowercase().replace(Regex("[^a-z0-9]"), "")
return cleaned == cleaned.reversed()
}
// Usage
val text = "A man a plan a canal Panama"
println(text.isPalindrome()) // true
// Extension function with parameters
fun String.truncate(maxLength: Int, suffix: String = "..."): String {
return if (this.length <= maxLength) {
this
} else {
this.take(maxLength - suffix.length) + suffix
}
}
val longText = "This is a very long text that needs to be truncated"
println(longText.truncate(20)) // This is a very lo...
// Extension function for collections
fun <T> List<T>.secondOrNull(): T? {
return if (this.size >= 2) this[1] else null
}
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.secondOrNull()) // 2
val emptyList = emptyList<Int>()
println(emptyList.secondOrNull()) // null
Propriétés de l'extension¶
// Extension property for String
val String.lastIndex: Int
get() = this.length - 1
val text = "Hello"
println(text.lastIndex) // 4
// Extension property for collections
val <T> List<T>.penultimate: T?
get() = if (this.size >= 2) this[this.size - 2] else null
val fruits = listOf("apple", "banana", "cherry")
println(fruits.penultimate) // banana
// Extension property with backing field (not allowed)
// var String.customProperty: String = "" // Compilation error
// But you can use other mechanisms
private val stringExtras = mutableMapOf<String, String>()
var String.extra: String
get() = stringExtras[this] ?: ""
set(value) {
stringExtras[this] = value
}
Fonctions d'extension pour les classes personnalisées¶
data class Point(val x: Double, val y: Double)
// Extension function for custom class
fun Point.distanceTo(other: Point): Double {
val dx = this.x - other.x
val dy = this.y - other.y
return kotlin.math.sqrt(dx * dx + dy * dy)
}
// Extension operator
operator fun Point.plus(other: Point): Point {
return Point(this.x + other.x, this.y + other.y)
}
operator fun Point.times(scalar: Double): Point {
return Point(this.x * scalar, this.y * scalar)
}
// Usage
val point1 = Point(1.0, 2.0)
val point2 = Point(4.0, 6.0)
println(point1.distanceTo(point2)) // 5.0
val sum = point1 + point2
val scaled = point1 * 2.0
// Extension function with receiver type parameter
fun <T> T.applyIf(condition: Boolean, block: T.() -> T): T {
return if (condition) this.block() else this
}
val result = "hello"
.applyIf(true) { uppercase() }
.applyIf(false) { reversed() }
println(result) // HELLO
Fonctions de portée comme extensions¶
// Custom scope functions
inline fun <T> T.alsoIf(condition: Boolean, block: (T) -> Unit): T {
if (condition) block(this)
return this
}
inline fun <T, R> T.letIf(condition: Boolean, block: (T) -> R): R? {
return if (condition) block(this) else null
}
// Usage
val number = 42
.alsoIf(true) { println("Number is $it") }
.alsoIf(false) { println("This won't print") }
val result = "hello"
.letIf(true) { it.uppercase() }
?.let { "Result: $it" }
// Extension for nullable types
fun <T> T?.ifNotNull(block: (T) -> Unit) {
if (this != null) block(this)
}
val nullableString: String? = "hello"
nullableString.ifNotNull { println("String: $it") }
Fonctions de commande supérieure¶
Types de fonctions¶
// Function type declarations
val add: (Int, Int) -> Int = { a, b -> a + b }
val greet: (String) -> Unit = { name -> println("Hello, $name!") }
val isEven: (Int) -> Boolean = { it % 2 == 0 }
// Function type with receiver
val stringBuilder: StringBuilder.() -> Unit = {
append("Hello, ")
append("World!")
}
val sb = StringBuilder()
sb.stringBuilder()
println(sb.toString()) // Hello, World!
// Nullable function types
val nullableFunction: ((Int) -> String)? = null
val result = nullableFunction?.invoke(42)
// Function type with multiple parameters
val calculator: (Int, Int, (Int, Int) -> Int) -> Int = { a, b, operation ->
operation(a, b)
}
val sum = calculator(5, 3) { x, y -> x + y }
val product = calculator(5, 3) { x, y -> x * y }
Exemples de fonctions de commande supérieure¶
// Function that takes a function as parameter
fun processNumbers(numbers: List<Int>, processor: (Int) -> Int): List<Int> {
return numbers.map(processor)
}
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers) { it * 2 }
val squared = processNumbers(numbers) { it * it }
// Function that returns a function
fun createMultiplier(factor: Int): (Int) -> Int {
return { number -> number * factor }
}
val doubler = createMultiplier(2)
val tripler = createMultiplier(3)
println(doubler(5)) // 10
println(tripler(5)) // 15
// Function with multiple function parameters
fun combineOperations(
a: Int,
b: Int,
operation1: (Int, Int) -> Int,
operation2: (Int) -> Int
): Int {
val intermediate = operation1(a, b)
return operation2(intermediate)
}
val result = combineOperations(3, 4, { x, y -> x + y }, { it * 2 })
println(result) // 14 (3 + 4 = 7, then 7 * 2 = 14)
Fonctions de commande supérieure intégrées¶
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// map - transform each element
val doubled = numbers.map { it * 2 }
val strings = numbers.map { "Number: $it" }
// filter - select elements based on condition
val evens = numbers.filter { it % 2 == 0 }
val greaterThanFive = numbers.filter { it > 5 }
// reduce - combine elements into single value
val sum = numbers.reduce { acc, n -> acc + n }
val max = numbers.reduce { acc, n -> if (n > acc) n else acc }
// fold - like reduce but with initial value
val product = numbers.fold(1) { acc, n -> acc * n }
val concatenated = numbers.fold("") { acc, n -> acc + n }
// forEach - perform action on each element
numbers.forEach { println(it) }
// any, all, none - check conditions
val hasEven = numbers.any { it % 2 == 0 }
val allPositive = numbers.all { it > 0 }
val noNegative = numbers.none { it < 0 }
// find - find first element matching condition
val firstEven = numbers.find { it % 2 == 0 }
val firstGreaterThanTen = numbers.find { it > 10 } // null
// partition - split into two lists based on condition
val (evens2, odds) = numbers.partition { it % 2 == 0 }
// groupBy - group elements by key
val words = listOf("apple", "banana", "cherry", "apricot")
val groupedByFirstLetter = words.groupBy { it.first() }
Fonctions de portée¶
// let - execute block and return result
val name: String? = "John"
val result = name?.let { nonNullName ->
"Hello, $nonNullName!"
} ?: "Hello, Guest!"
// run - execute block on object and return result
val message = StringBuilder().run {
append("Hello, ")
append("World!")
toString()
}
// with - execute block on object and return result
val greeting = with(StringBuilder()) {
append("Hello, ")
append("World!")
toString()
}
// apply - execute block on object and return object
val person = Person().apply {
name = "John"
age = 30
}
// also - execute block with object and return object
val numbers2 = mutableListOf(1, 2, 3).also { list ->
println("List size: ${list.size}")
list.add(4)
}
// takeIf - return object if condition is true, null otherwise
val positiveNumber = (-5).takeIf { it > 0 } // null
val evenNumber = 4.takeIf { it % 2 == 0 } // 4
// takeUnless - return object if condition is false, null otherwise
val notZero = 5.takeUnless { it == 0 } // 5
val notEmpty = "".takeUnless { it.isEmpty() } // null
Coroutines¶
Coroutines de base¶
import kotlinx.coroutines.*
// Basic coroutine
fun main() = runBlocking {
println("Start")
delay(1000) // Non-blocking delay
println("End")
}
// Launch coroutine
fun main() = runBlocking {
launch {
delay(1000)
println("World!")
}
println("Hello,")
}
// Async coroutine
fun main() = runBlocking {
val deferred = async {
delay(1000)
"Hello, World!"
}
println(deferred.await())
}
// Multiple coroutines
fun main() = runBlocking {
val job1 = launch {
repeat(5) { i ->
println("Job1: $i")
delay(500)
}
}
val job2 = launch {
repeat(3) { i ->
println("Job2: $i")
delay(800)
}
}
joinAll(job1, job2)
println("All jobs completed")
}
Fonctions de suspension¶
// Suspend function
suspend fun fetchUserData(userId: String): User {
delay(1000) // Simulate network call
return User(userId, "John Doe")
}
suspend fun fetchUserPosts(userId: String): List<Post> {
delay(800) // Simulate network call
return listOf(Post("Post 1"), Post("Post 2"))
}
// Using suspend functions
fun main() = runBlocking {
val userId = "123"
// Sequential execution
val user = fetchUserData(userId)
val posts = fetchUserPosts(userId)
println("User: $user, Posts: $posts")
// Concurrent execution
val userDeferred = async { fetchUserData(userId) }
val postsDeferred = async { fetchUserPosts(userId) }
val userResult = userDeferred.await()
val postsResult = postsDeferred.await()
println("User: $userResult, Posts: $postsResult")
}
// Suspend function with error handling
suspend fun fetchDataWithRetry(url: String, maxRetries: Int = 3): String {
repeat(maxRetries) { attempt ->
try {
return performNetworkCall(url)
} catch (e: Exception) {
if (attempt == maxRetries - 1) throw e
delay(1000 * (attempt + 1)) // Exponential backoff
}
}
throw IllegalStateException("Should not reach here")
}
suspend fun performNetworkCall(url: String): String {
delay(500) // Simulate network call
if (Math.random() < 0.7) throw Exception("Network error")
return "Data from $url"
}
Contextes coroutins et régulateurs¶
import kotlinx.coroutines.*
fun main() = runBlocking {
// Default dispatcher (optimized for CPU-intensive work)
launch(Dispatchers.Default) {
println("Default: ${Thread.currentThread().name}")
}
// IO dispatcher (optimized for IO operations)
launch(Dispatchers.IO) {
println("IO: ${Thread.currentThread().name}")
}
// Main dispatcher (for UI updates, Android/JavaFX)
// launch(Dispatchers.Main) {
// println("Main: ${Thread.currentThread().name}")
// }
// Unconfined dispatcher
launch(Dispatchers.Unconfined) {
println("Unconfined: ${Thread.currentThread().name}")
}
delay(100)
}
// Custom dispatcher
fun main() = runBlocking {
val customDispatcher = newSingleThreadContext("CustomThread")
launch(customDispatcher) {
println("Custom: ${Thread.currentThread().name}")
}
customDispatcher.close()
}
// Switching contexts
suspend fun processData() {
withContext(Dispatchers.IO) {
// Perform IO operation
println("IO operation on: ${Thread.currentThread().name}")
}
withContext(Dispatchers.Default) {
// Perform CPU-intensive operation
println("CPU operation on: ${Thread.currentThread().name}")
}
}
Annulation de la Coroutine et délais¶
// Cancellation
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("Job: I'm sleeping $i ...")
delay(500)
}
}
delay(1300) // Let it run for a bit
println("Cancelling job...")
job.cancel() // Cancel the job
job.join() // Wait for cancellation to complete
println("Job cancelled")
}
// Cooperative cancellation
suspend fun cooperativeTask() {
repeat(1000) { i ->
if (!isActive) return // Check if coroutine is still active
println("Working $i")
// Simulate work without delay
Thread.sleep(100)
}
}
// Timeout
fun main() = runBlocking {
try {
withTimeout(1300) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500)
}
}
} catch (e: TimeoutCancellationException) {
println("Timed out!")
}
}
// Timeout with null result
fun main() = runBlocking {
val result = withTimeoutOrNull(1300) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500)
}
"Done"
}
println("Result: $result") // null if timed out
}
Canaux et débit¶
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
// Channels
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
channel.send(x * x)
}
channel.close()
}
for (y in channel) {
println(y)
}
}
// Producer-consumer with channel
fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
for (x in 1..5) {
send(x * x)
}
}
fun main() = runBlocking {
val squares = produceSquares()
squares.consumeEach { println(it) }
}
// Flow
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i)
}
}
fun main() = runBlocking {
simpleFlow().collect { value ->
println(value)
}
}
// Flow transformations
fun main() = runBlocking {
(1..5).asFlow()
.filter { it % 2 == 0 }
.map { it * it }
.collect { println(it) }
}
// Flow with exception handling
fun main() = runBlocking {
flow {
for (i in 1..3) {
println("Emitting $i")
emit(i)
if (i == 2) throw RuntimeException("Error at $i")
}
}
.catch { e -> emit(-1) } // Handle exception
.collect { println("Collected $it") }
}
Développement Android¶
Bases d'activité¶
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.viewModels
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupUI()
observeViewModel()
}
private fun setupUI() {
// UI setup code
}
private fun observeViewModel() {
viewModel.userData.observe(this) { user ->
// Update UI with user data
}
}
override fun onStart() {
super.onStart()
// Activity is becoming visible
}
override fun onResume() {
super.onResume()
// Activity is in foreground
}
override fun onPause() {
super.onPause()
// Activity is partially obscured
}
override fun onStop() {
super.onStop()
// Activity is no longer visible
}
override fun onDestroy() {
super.onDestroy()
// Activity is being destroyed
}
}
Afficher la liaison¶
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupClickListeners()
}
private fun setupClickListeners() {
binding.submitButton.setOnClickListener {
val text = binding.editText.text.toString()
binding.textView.text = "Hello, $text!"
}
binding.clearButton.setOnClickListener {
binding.editText.text.clear()
binding.textView.text = ""
}
}
}
AffichageModèle et LiveData¶
import androidx.lifecycle.ViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
private val _userData = MutableLiveData<User>()
val userData: LiveData<User> = _userData
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error
fun loadUser(userId: String) {
viewModelScope.launch {
_loading.value = true
try {
val user = userRepository.getUser(userId)
_userData.value = user
} catch (e: Exception) {
_error.value = e.message
} finally {
_loading.value = false
}
}
}
fun updateUser(user: User) {
viewModelScope.launch {
try {
userRepository.updateUser(user)
_userData.value = user
} catch (e: Exception) {
_error.value = e.message
}
}
}
}
Recycleur Adaptateur de vue¶
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.databinding.ItemUserBinding
class UserAdapter(
private val onUserClick: (User) -> Unit
) : ListAdapter<User, UserAdapter.UserViewHolder>(UserDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val binding = ItemUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return UserViewHolder(binding)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class UserViewHolder(
private val binding: ItemUserBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(user: User) {
binding.nameTextView.text = user.name
binding.emailTextView.text = user.email
binding.ageTextView.text = user.age.toString()
binding.root.setOnClickListener {
onUserClick(user)
}
}
}
class UserDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
}
Modèle de dépôt¶
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class UserRepository(
private val apiService: ApiService,
private val userDao: UserDao
) {
suspend fun getUser(userId: String): User = withContext(Dispatchers.IO) {
try {
val user = apiService.getUser(userId)
userDao.insertUser(user)
user
} catch (e: Exception) {
// Fallback to cached data
userDao.getUser(userId) ?: throw e
}
}
suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
try {
val users = apiService.getUsers()
userDao.insertUsers(users)
users
} catch (e: Exception) {
userDao.getAllUsers()
}
}
suspend fun updateUser(user: User): User = withContext(Dispatchers.IO) {
val updatedUser = apiService.updateUser(user)
userDao.updateUser(updatedUser)
updatedUser
}
suspend fun deleteUser(userId: String) = withContext(Dispatchers.IO) {
apiService.deleteUser(userId)
userDao.deleteUser(userId)
}
}
Meilleures pratiques¶
Style de code¶
// Use meaningful names
class UserManager { // Good
fun getUserById(id: String): User // Good
fun isValidEmail(email: String): Boolean // Good
}
class UM { // Bad
fun get(i: String): User // Bad
fun check(e: String): Boolean // Bad
}
// Use type inference when possible
val name = "John" // Good
val name: String = "John" // Unnecessary
// Use data classes for simple data holders
data class User(val id: String, val name: String, val email: String)
// Use sealed classes for restricted hierarchies
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// Use extension functions for utility methods
fun String.isValidEmail(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}
// Use scope functions appropriately
val user = User("1", "John", "john@example.com").apply {
// Configure user
}
user?.let { nonNullUser ->
// Process non-null user
}
Pratiques exemplaires en matière de sécurité des null¶
// Prefer safe calls over explicit null checks
// Good
val length = name?.length
// Less preferred
val length = if (name != null) name.length else null
// Use Elvis operator for default values
val displayName = user.name ?: "Anonymous"
// Use let for null-safe operations
user?.let { nonNullUser ->
processUser(nonNullUser)
}
// Avoid not-null assertion operator unless absolutely necessary
val definitelyNotNull = value!! // Use sparingly
// Use lateinit for properties that will be initialized later
class MyActivity : Activity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
}
}
Meilleures pratiques en matière de rendement¶
// Use lazy initialization for expensive operations
class DataManager {
private val expensiveResource by lazy {
createExpensiveResource()
}
private fun createExpensiveResource(): ExpensiveResource {
// Expensive initialization
return ExpensiveResource()
}
}
// Use inline functions for higher-order functions
inline fun <T> measureTime(block: () -> T): Pair<T, Long> {
val start = System.currentTimeMillis()
val result = block()
val time = System.currentTimeMillis() - start
return result to time
}
// Use data classes for better performance with collections
data class Point(val x: Int, val y: Int)
val points = setOf(Point(1, 2), Point(3, 4), Point(1, 2))
// Automatic equals() and hashCode() implementation
// Use appropriate collection types
val uniqueItems = setOf(1, 2, 3) // For unique items
val orderedItems = listOf(1, 2, 3) // For ordered items
val keyValuePairs = mapOf("a" to 1) // For key-value pairs
// Use sequences for large collections with multiple operations
val result = (1..1000000)
.asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.take(10)
.toList()
Coroutines Meilleures pratiques¶
// Use appropriate dispatchers
class UserRepository {
suspend fun getUser(id: String): User = withContext(Dispatchers.IO) {
// IO operation
apiService.getUser(id)
}
suspend fun processUserData(data: List<User>): List<ProcessedUser> =
withContext(Dispatchers.Default) {
// CPU-intensive operation
data.map { processUser(it) }
}
}
// Handle exceptions properly
suspend fun fetchDataSafely(): Result<Data> {
return try {
val data = apiService.fetchData()
Result.Success(data)
} catch (e: Exception) {
Result.Error(e)
}
}
// Use structured concurrency
class DataLoader {
suspend fun loadAllData(): CombinedData = coroutineScope {
val userData = async { loadUserData() }
val settingsData = async { loadSettingsData() }
val preferencesData = async { loadPreferencesData() }
CombinedData(
users = userData.await(),
settings = settingsData.await(),
preferences = preferencesData.await()
)
}
}
// Cancel coroutines when no longer needed
class MyViewModel : ViewModel() {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Main + job)
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Résumé¶
Kotlin est un langage de programmation moderne, concis et sûr qui offre des avantages importants pour le développement Android et au-delà. Les principales caractéristiques sont les suivantes :
- Sécurité nucléaire: la sécurité null intégrée empêche NullPointerException au moment de la compilation
- Concès: Réduit le code de plaque de chaudière significativement par rapport à Java
- ** Interopérabilité**: 100% interopérable avec Java, permettant une migration progressive
- Coroutines: Support intégré pour la programmation asynchrone avec les coroutines
- Inférence de type: L'inférence de type intelligent réduit les déclarations de type explicite
- ** Fonctions d'extension**: Ajouter des fonctionnalités aux classes existantes sans héritage
- Classes de données: génération automatique d'egals(), hashCode(), toString() et copy()
- Classes scellées: Hiérarchies de classe restreintes pour une meilleure sécurité de type
- Smart Casts: Moulage automatique après vérification nulle ou vérification de type
- ** Programmation fonctionnelle**: Soutien de première classe aux concepts de programmation fonctionnelle
Kotlin combine des fonctions de programmation orientées objet et fonctionnelle, ce qui en fait un excellent choix pour le développement Android moderne, le développement côté serveur et les projets multiplateformes.