
val number = 123
val message = "Hello world !"
fun sayHello() = "Hello world !"
kotlin-compiler-1.4.21.zip./bin dans le path pour que cela soit plus pratique.hello.kt :notepad hello.kt
fun main() {
println("Hello, World!")
}
Compilons notre code :
kotlinc hello.kt -include-runtime -d hello.jar
Nous pouvons le lancer maintenant :
java -jar hello.jar
kotlin-native-windows-1.4.21.zip./bin dans le path pour que cela soit plus pratique.hello.kt :notepad hello.kt
fun main() {
println("Hello, World!")
}
Compilons notre code :
kotlinc hello.kt
Nous pouvons le lancer un executable maintenant :
program.exe
Le premier lancement est long, mais les suivants sont rapides.
kotlinc.
Testons quelques commandes, par exemple :
40+2
"Hello, World!"
println("Hello, World!")
1
1.0
org.example.kotlin, alors les fichiers seront placés
directement dans le répertoire racine contenant les sources.org.example.kotlin.network.socket
seront placés dans le sous répertoire : network/socket.
.kt.main, puis la touche TAB, pour lancer l'autocomplétion.
fun main() {
println("Bonjour le monde")
}
abstract class Foo { ... }
class FooImpl : Foo { ... }
fun Foo(): Foo { return FooImpl(...) }
`. (Note : cela ne fonctionnera pas
en Android)._ sont aussi autorisés :
class MyTestCase {
@Test fun `ensure everything works`() { ... }
@Test fun ensureEverythingWorks_onAndroid() { ... }
}
if, for ou catch.else.->....,.//.const, les propriétés top level ou propriétés val d'un objet sans fonction get
custom, qui contient une valeur profondément immuable), doit utiliser des majuscules, et _ comme séparateur :
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"
Les fonctions top level ou les propriétés d'un objet dont les valeurs peuvent évoluer, utiliserons la notation camel-case :
val mutableCollection: MutableSet<String> = HashSet()
Le nom des propriétés qui contiennent une référence à un objet Singleton, peuvent utiliser la même rêgle de nommage :
val PersonComparator: Comparator<Person> = /*...*/
Pour les valeurs des enums, il est possible d'utiliser les majuscules séparés par des _, ou l'écriture camel-case, en commençant par une majuscule, selon l'usage.
class C {
private val _elementList = mutableListOf<Element>()
val elementList: List<Element>
get() = _elementList
}
List, PersonReader.close, readPersons. Le nom doit aussi faire
comprendre s'il modifie l'objet ou s'il en retourne un nouveau. Par exemple : sort modifie la collection, alors que sorted retournera
une copie de la collection triée.Manager,
Wrapper, etc.IOStream), mais ne mettez que la première lettre
en majuscule s'il est plus long (XmlFormatter, HttpInputStream).
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data
Placer les annotations avant les modifieurs :
private val foo: Foo
val message = "Hello world !"
val message: String = "Hello world !"
var message: String = "Hello world !"
val name: String = "Tristan"
val age: Int = 41
val isDeveloper: Boolean = true
Est équivalent à :
val name = "Tristan"
val age = 41
val isDeveloper = true
import kotlin.random.Random
fun isUserHappy() = Random.nextInt(0, 100) % 2 == 0
fun main() {
val message: String
if (isUserHappy())
message = "That's great"
else
message = "What's going on?"
println(message)
}
var name: String = "Tristan"
name = null
Alors que le code suivant est correct.
var name: String? = "Tristan"
name = null
var name: String? = "Tristan"
name.toUpperCase()
Il faut donc prendre une précaution en utilisant cette variable, en utilisant ? :
var name: String? = "Tristan"
name?.toUpperCase()
Si la valeur de la variable contient null, alors la méthode ne sera pas appelée.
$, par exemple :
val name = "Tristan"
println("Hello $name")
Référence vers les chaînes de caractère.
const.
public static final String SERVER_URL = "http://my.api.com/";
Deviendra en Kotlin :
const val SERVER_URL = "http://my.api.com/"
| Type | Size (bits) | Min value | Max value |
|---|---|---|---|
| Byte | 8 | -128 | 127 |
| Short | 16 | -32768 | 32767 |
| Int | 32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
| Long | 64 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
| Type | Size (bits) | Significant bits | Exponent bits | Decimal digits |
|---|---|---|---|---|
| Float | 32 | 24 | 8 | 6-7 |
| Double | 64 | 53 | 11 | 15-16 |
val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
fun main() {
fun printDouble(d: Double) { println(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
printDouble (sans toucher au code de la
fonction).
fun main() {
fun printDouble(d: Double) { println(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
fun main() {
fun printDouble(d: Double) { println(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
printDouble(i.toDouble())
printDouble(f.toDouble())
}
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
fun check(c: Char) {
if (c == 1) { // ERROR: incompatible types
// ...
}
}
Pour écrire un caractère, on utilise la simple "quote" '.
Exemple : 't'.
Les caractères spéciaux sont préfixés par \.
Exemple : \t, \b, \n, \r, \', \", \\ and \$.
Pour les autres caractères, on peut utiliser la syntaxe Unicode : '\uFF00'.
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}
get et set[] grâce à la convention de surcharge des opérateurs),
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ...
}
arrayOf(1, 2, 3) permet de créer des tableaux, arrayOfNulls() va créer un tableau de valeurs
nulles.Array prend en paramètre le nombre d'éléments, et la fonction pour remplir le tableau.
var array = arrayOf(1, 2, 3)
array.forEach { println(it) }
var arrayOfNull: Array<Int?> = arrayOfNulls(5)
arrayOfNull.forEach { println(it) }
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }
[] est équivalent à l'appel des méthodes get() et set().
var array = arrayOf(1, 2, 3)
println(array[2])
array[0] = 4
array.forEach { println(it) }
val oddArray = TODO()
fun main() {
val oddArray = Array(11) { i -> (i * 2) }
for(i in oddArray) {
print("$i ")
}
}
val alphabetArray = TODO()
fun main() {
val alphabetArray = Array(26) { i -> (i+'a'.toInt()).toChar() }
for (i in alphabetArray) {
print("$i ")
}
}
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// Tableau d'int de taille 5 contenant les valeurs [0, 0, 0, 0, 0]
val arr = IntArray(5)
// Exemple : initialisation des valeurs contenues dans le tableau avec une constante
// Tableau d'int de taille 5 contenant les valeurs [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }
// Exemple : initialisation des valeurs contenues dans le tableau en utilisant une lambda (fonction)
// Tableau d'int de taille 5 contenant les valeurs [0, 1, 2, 3, 4] (valeurs égales à leur position dans le tableau)
var arr = IntArray(5, { it * 1 })
[].+, même si l'on préférera les templates ($ dans les chaînes de caractères).
val s = "abc" + 1
println(s + "def")
")""")
val stringWithEscape = "Hello, world!\n"
println(stringWithEscape)
val stringRaw = """
for (c in "foo")
print(c)
"""
println(stringRaw)
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
println(text)
$.
val i = 10
println("i = $i") // affiche "i = 10"
Il est possible d'inclure une expression entre ${}, et d'afficher le symbole $ lui-même. Cela fonctionne
aussi dans une chaîne brute (raw string).
println("$name.length is ${name.length}")
println("price : ${'$'}9.99")
val price = """
${'$'}9.99
"""
hello qui doit retourner : Hello, <NAME>
fun hello(name: String): String = TODO()
fun main() {
fun hello(name: String): String = "Hello, ${name.toUpperCase()}"
print(hello("Tristan"))
}
/*
Les commentaires sur plusieurs
lignes, pour pouvoir bien s'exprimer.
Vous pouvez écrire autant que vous voulez.
*/
// ceci est un commentaire uniligne.
if est une expression : il retourne une valeur. De ce fait l'opérateur ternaire ? n'existe
plus.
// Usage traditionnel
var max = a
if (a < b) max = b
// Avec else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// Comme une expression
val max = if (a > b) a else b
if, peuvent être des blocs, dont la dernière expression est la valeur du bloc :
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
Quand le if est utilisé comme expression (pour retourner une valeur ou pour assigner une valeur),
l'expression doit obligatoirement comporter la branche else.
when est l'équivalent du switch en Java.
var apiReponse = 404
when (apiReponse) {
200 -> "OK"
404 -> "NOT FOUND"
401 -> "UNAUTHORIZED"
403 -> "FORBIDDEN"
else -> "UNKNOWN"
}
Quand le when est utilisé comme expression (pour retourner une valeur ou pour assigner une valeur),
l'expression doit obligatoirement comporter la branche else, sauf si tous les cas sont couverts (d'un
enum par exemple).
var apiReponse = 404
fun printResponse(apiReponse: Int) = when (apiReponse) {
200 -> print("OK")
404 -> print("NOT FOUND")
401 -> print("UNAUTHORIZED")
403 -> print("FORBIDDEN")
else -> print("UNKNOWN")
}
printResponse(apiReponse)
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
Les conditions peuvent être des expressions :
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
On peut aussi tester le type du paramètre :
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
On peut l'utiliser sans paramètre, les conditions sont alors obligatoirement des booléens,
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
while est exactement la même qu'en Java :
var isRaining = true
while (isRaining){
println("I don't like rain.")
}
do {
println("I don't like rain.")
} while (isRaining)
for, peut itérer sur tout ce qui fournit un itérateur, sa syntaxe est la suivante :
for (item in collection) print(item)
Par exemple sur une liste de chaînes de caractères, cela donne :
val names = listOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
for(name in names) {
println("This developer rocks : $name")
}
rangeTo(),
correspondant à l'opérateur ...in ou !in. Exemple :
if (i in 1..4) { // equivalent à 1 <= i && i <= 4
println(i)
}
Pour définir un intervalle, souvent utilisés dans les boucles for, la syntaxe est la suivante :
for (i in 1..4) println(i)
Pour utiliser l'ordre décroissant, la syntaxe sera :
for (i in 4 downTo 1) println(i)
for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)
Pour ne pas inclure le dernier élément de la liste, utiliser la fonction until :
for (i in 1 until 10) { // i in [1, 10), 10 is excluded
print(i)
}
for (i in array.indices) {
println(array[i])
}
Ou alors en utilisant le format suivant qui met en oeuvre la déstructuration (destructuring) :
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
une chaîne de caractères.
var stringValue = "Une chaîne de caractères"
Le résultat attendu est :U n e c h a î n e d e c a r a c t è r e s
var stringValue = "Une chaîne de caractères"
for (c in stringValue) {
print("$c ")
}
Solution bis :
var stringValue = "Une chaîne de caractères"
stringValue.forEach { print("$it ")}
Solution ter :
var stringValue = "Une chaîne de caractères"
stringValue.toCharArray().joinToString(" ")
Référence joinToString.
fun main(args: Array<String>) {
for (x in 0 until 10) {
when {
(x % 3 == 0 && x % 5 == 0) -> println("FizzBuzz")
(x % 3 == 0) -> println("Fizz")
(x % 5 == 0) -> println("Buzz")
else -> println(x)
}
}
}
fun main(args: Array<String>) {
for (x in 0 until 10) {
println( when {
(x % 3 == 0 && x % 5 == 0) -> "FizzBuzz"
(x % 3 == 0) -> "Fizz"
(x % 5 == 0) -> "Buzz"
else -> x
})
}
}
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five") // this is OK
//numbers = mutableListOf("six", "seven") // compilation error
List et Set :
val listOfNames = listOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
listOfNames[0]
//listOfNames[0] = "Mathieu NEBRA" // Error: List is immutable
val mutableListOfNames = mutableListOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
mutableListOfNames[0]
mutableListOfNames[0] = "Mathieu NEBRA" // OK
val setOfNames = setOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
setOfNames.first()
//setOfNames.add("Mathieu NEBRA") // Error: Set is immutable
val mutableSetOfNames = mutableSetOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
mutableSetOfNames.first()
mutableSetOfNames.add("Mathieu NEBRA") // OK
Exemple de tableau et de Map :
var arrayOfNames = arrayOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
var mapOfNames = mapOf(0 to "Jake WHARTON", 1 to "Joe BIRCH", 2 to "Robert MARTIN")
package org.example
fun printMessage() { /*...*/ }
class Message { /*...*/ }
// ...
Tout le contenu (tels que les classes et les fonctions) du fichier de code source appartiendront au
package déclaré. Dans l'exemple ci-dessus, le nom complet de printMessage() est org.example.printMessage(),
de la même manière, le nom complet de
Message est org.example.Message.Utils pour nommer un fichier source, qui apporte peu d'informations sur son
contenu.
until au lieu d'un intervalle ouvert :
for (i in 0..n - 1) { ... } // bad
for (i in 0 until n) { ... } // good
fun double(x: Int): Int {
return 2 * x
}
val result = double(2)
Stream().read() // créé une instance de la class Stream et appelle la fonction read()
fun double(x: Int): Int = x * 2
Il n'est même pas nécessaire de préciser le type de retour, qui est inféré par le compilateur :
fun double(x: Int) = x * 2
fun powerOf(number: Int, exponent: Int) { /*...*/ }
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*...*/ }
open class A {
open fun foo(i: Int = 10) { /*...*/ }
}
class B : A() {
override fun foo(i: Int) { /*...*/ } // pas de valeur par défaut authorisée
}
"default" :
fun upper(): String = TODO()
fun upper(str:String? = "default") = str?.toUpperCase()
Dans ce cas, si l'on précise une valeur null, c'est cette valeur qui est utilisée, et la méthode n'affichera rien.
fun upper(str:String = "default") = str.toUpperCase()
add qui additionne 2 Int passés en paramètre :
fun add(a: Int, b: Int): Int = TODO()
fun add(a: Int, b: Int): Int = a + b
fun strEq(s1: String, s2: String): Boolean = TODO()
fun strEq(s1: String, s2: String, ignoreCase: Boolean): Boolean = TODO()
fun strEq(s1: String, s2: String): Boolean = s1.equals(s2)
fun strEq(s1: String, s2: String, ignoreCase: Boolean): Boolean = if(ignoreCase) s1.toUpperCase().equals(s2.toUpperCase()) else s1.equals(s2)
fun main() {
println(strEq("Tristan", "TRISTAN"))
println(strEq("Tristan", "TRISTAN", true))
}
Solution bis :
fun strEq(s1: String, s2: String, ignoreCase: Boolean): Boolean = s.equals(p, ignoreCase)
Référence : equals.
val languages = arrayOf("Java", "JavaScript", "Go", "Kotlin")
fun printLanguages(): Unit = TODO()
val languages = arrayOf("Java", "JavaScript", "Go", "Kotlin")
fun printLanguages(): Unit { for (language in languages) println(language) }
fun main() {
printLanguages()
}
Solution bis :
fun printLanguages() { languages.forEach { println(it) } }
fun tenFirstNumber(): Unit = TODO()
fun countdown(): Unit = TODO()
fun firstEvenNumbers(): Unit = TODO()
fun firstOddNumbers(): Unit = TODO()
fun tenFirstNumber(): Unit { for(i in 0 until 10) print("$i "); println()}
fun countdown() : Unit { for(i in 10 downTo 0) print("$i "); println()}
fun firstEvenNumbers(): Unit { for(i in 0 until 20 step 2) print("$i "); println()}
fun firstOddNumbers(): Unit { for(i in 1 .. 20 step 2) print("$i "); println()}
fun main() {
tenFirstNumber()
countdown()
firstEvenNumbers()
firstOddNumbers()
}
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
/*...*/
}
reformat(str)
Nous pouvons préciser tous les paramètres :
reformat(str, true, true, false, '_')
L'appel avec tous les paramètres nommés est bien plus clair :
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
fun foo(bar: Int = 0, baz: Int) { /*...*/ }
foo(baz = 1) // La valeur par défaut bar = 0 est utilisée
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*...*/ }
foo(1) { println("hello") } // Utilise la valeur par défaut baz = 1
foo(qux = { println("hello") }) // Utilise les valeurs par défaut bar = 0 et baz = 1
foo { println("hello") } // Utilise les valeurs par défaut bar = 0 et baz = 1
reformat(str, wordSeparator = '_')
Il est possible de mixer les paramètres de position et les paramètres nommés :
f(1, y = 2)
Mais l'ordre est important : il est impossible d'utiliser un paramètre nommé puis un paramètre de
position :
f(x = 1, 2)
... pour définir un nombre variable de paramètres.
En Kotlin, c'est le mot clé vararg qui est utilisé (pour le
dernier paramètre généralement, autrement il faudra nommer les paramètres).
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
Un seul paramètre peut être marqué variable.
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
infix fun Int.shl(x: Int): Int { ... }
// appel de la méthode en utilisant la notation infix
1 shl 2
// est la même chose que
1.shl(2)
fun(x: Int, y: Int): Int = x + y
Une fonction anonyme ressemble à une fonction classique, sauf le que le nom est omis. Le corps de la
fonction peut aussi être un bloc :
fun(x: Int, y: Int): Int {
return x + y
}
Si le type des paramètres peut être inféré par le compilateur, il n'est pas nécessaire de le préciser,
comme dans l'exemple :
ints.filter(fun(item) = item > 0)
tailrec et répond au format requis, le
compilateur optimise la récursion, en générant une boucle rapide et optimisée à la place :
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
Référence : "Tail recursive functions".
Math.cos plusieurs fois, en partant de 1.0 jusqu'à ce que le résultat ne
change plus, en arrivant au résultat de : 0.7390851331706995 pour la précision eps.
val eps = 1E-10 // "good enough", could be 10^-15
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (Math.abs(x - y) < eps) return x
x = Math.cos(x)
}
}
findFixPoint pour visualiser le nombre d'appels récursifs effectués (par exemple en affichant un . à chaque itération.
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else {
print('.')
findFixPoint(Math.cos(x))
}
Unit.
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// "return Unit" or "return" is optional
}
Il n'est même pas obligatoire de préciser ce type retour :
fun printHello(name: String?) {
...
}
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
visited peut être définie comme variable locale :
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
fun isMultiple(operand: Int): Boolean :
fun isEven(n: Int): Boolean = TODO()
fun isEven(n: Int): Boolean {
fun isMultiple(operand: Int): Boolean = n % operand == 0
return isMultiple(2)
}
fun main() {
println(isEven(2))
println(isEven(3))
}
class Invoice { /*...*/ }
class Empty
class Person constructor(firstName: String) { /*...*/ }
Si le constructeur n'a pas d'annotations, ou de modificateurs de visibilité, le mot clé constructor
n'est pas obligatoire :
class Person(firstName: String) { /*...*/ }
init. Les blocs sont exécutés dans
l'odre d'apparition dans le code :
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
Les paramètres du constructeur peuvent être utilisés dans les blocs d'initialisation ou pour
initialiser les propriétés déclarées dans le corps de la classe :
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
class Person(val firstName: String, val lastName: String, var age: Int) { /*...*/ }
valvar
public class User {
// PROPERTIES
private String email;
private String password;
private int age;
// CONSTRUCTOR
public User(String email, String password, int age) {
this.email = email;
this.password = password;
this.age = age;
}
// GETTERS
public String getEmail() { return email; }
public String getPassword() { return password; }
public int getAge() { return age; }
// SETTERS
public void setEmail(String email) { this.email = email; }
public void setPassword(String password) { this.password = password; }
public void setAge(int age) { this.age = age; }
}
En Kotlin son équivalent est :
class User(var email: String, var password: String, var age: Int)
constructor :
class Person {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(parent: Person) {
parent.children.add(this)
}
}
Si la classe comporte une constructeur primaire, chaque constructeur secondaire doit faire appel au
constructeur primaire, directement ou indirectement via un autre constructeur secondaire. La délégation
à un autre constructeur de la même classe est effectué en utilisant le mot clé this :
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
Note : le code des blocs d'init font partis du constructeur primaire. La délégation au constructeur
primaire est la première action effectuée dans un constructeur secondaire, donc le code de tous les
blocs d'initialiseurs est exécuté avant le corps du constructeur secondaire, même si la classe n'a pas
de constructeur primaire, la délégation est implicite :
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
Une classe non abstraire qui ne déclare pas de constructeur en aura un public, par défaut, ne comportant
pas de paramètre.
class DontCreateMe private constructor () { /*...*/ }
= dans le constructeur :
class Customer(val customerName: String = "")
class User(email: String, var password: String, var age: Int) {
var email: String = email
get() { println("User email read access done."); return field}
set(value) { println("User email write access done."); field = value}
}
class User(var email: String, private var password: String, var age: Int)
new, on utilise directement le nom de la classe :
val user = User("hello@gmail.com", "azerty", 41)
Pour accéder aux champs, la notation à . est utilisée :
val user = User("hello@gmail.com", "azerty", 41)
println(user.email) // Getter
user.email = "my_new_email@gmail.com"
println(user.email) // Getter
Person avec les propriétés firstName et lastName dont les valeurs par défaut sont la chaîne
vide :"". La propriété lastName est en lecture seule. Vous afficherez le nom complet (prénom nom) après avoir modifié le prénom
(affectation d'une nouvelle valeur pour le prénom de notre objet créé) et vérifiée que le nom est bien en lecture seule (en essayant de modifier sa valeur).
class Person
class Person (var firstName: String = "", val lastName: String = "")
fun main() {
var father = Person("Anakin", "Skywalker")
println(father)
println( "${father.firstName} ${father.lastName}")
me.firstName = "Luke"
// me.lastName = "Organa d'Alderaan"
println( "${me.firstName} ${me.lastName}")
}
User, création d'une instance, changement de la valeur de l'email, et affichage de cette valeur.
class User(email: String, var password: String, var age: Int) {
var email: String = email
get() { println("User email read access done."); return field}
set(value) { println("User email write access done."); field = value}
}
fun main() {
var currentUser = User("tristan.salaun.pro@gmail.com", "azerty", 41)
currentUser.email = "test@test.com"
println(currentUser.email)
}
var,
ou en lecture seule (immutable) en utilisant le mot clé val :
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "221b Baker Street"
var city: String = "London"
var state: String? = null
var zip: String = "NW1 6XE"
}
Pour accéder aux propriétés, il suffit d'utiliser son nom :
fun copyAddress(address: Address): Address {
val result = Address() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
class Sample() {
fun foo() { print("Foo") }
}
Comme déjà vu, pour appeler une telle méthode il suffit d'utiliser la notation à point :
Sample().foo() // creates instance of class Sample and calls foo
public, les modifieurs de visibilité disponibles sont :
private : Un membre déclaré comme private sera visible uniquement dans la
classe où il est déclaré.
protected : Un membre déclaré comme protected sera visible uniquement dans
la classe où il est déclaré ET dans ses sous-classes (via l’héritage).
internal : Un membre déclaré comme internal sera visible par tous ceux du
même module. Un module est un ensemble de fichiers compilés ensemble (comme une librairie Gradle ou
Maven, par exemple).
public : Un membre déclaré comme public sera visible partout et par tout
le monde.
Any (équivalent à
Object en Java), qui est la superclasse par défaut de toutes les classes qui n'ont pas
déclaré de super type :
class Example // Implicitly inherits from Any
La classe Any à 3 méthodes : equals(), hashCode() et toString().
open class Base(p: Int)
class Derived(p: Int) : Base(p)
super, ou déléguér à un constructeur
secondaire qui le fera :::
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
open :
open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}
class Circle() : Shape() {
override fun draw() { /*...*/ }
}
Une méthode qui redéfinit une autre de la classe parente est elle même redéfinissable, à moins de la
marquer comme final :
open class Rectangle() : Shape() {
final override fun draw() { /*...*/ }
}
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
Il est possible de redéfinir une propriété de type val avec une autre de type
var, mais pas l'inverse.val déclare une méthode get, en la redéfinissant par une var, on déclare une méthode set
de plus dans la
classe dérivée.
interface Shape {
val vertexCount: Int
}
class Polygon : Shape {
override var vertexCount: Int = 0 // Can be set to any number later
}
override :
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // Always has 4 vertices
class Polygon : Shape {
override var vertexCount: Int = 0 // Can be set to any number later
}
open class Base(val name: String) {
init { println("Initializing Base") }
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}
Cela signifie que lorsque la classe primaire est créée, les propriétés déclarées ou redéfinies dans la
classe dérivée, ne sont pas encore initialisées.
super :
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
super qualifié par le nom de cette classe : super:@Outer :
class FilledRectangle: Rectangle() {
fun draw() { /* ... */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { /* ... */ }
fun drawAndFill() {
super@FilledRectangle.draw() // Calls Rectangle's implementation of draw()
fill()
println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // Uses Rectangle's implementation of borderColor's get()
}
}
}
super qualifié du nom de la classe parente avec des chevrons super<Base>
:
open class Rectangle {
open fun draw() { /* ... */ }
}
interface Polygon {
fun draw() { /* ... */ } // interface members are 'open' by default
}
class Square() : Rectangle(), Polygon {
// The compiler requires draw() to be overridden:
override fun draw() {
super<Rectangle>.draw() // call to Rectangle.draw()
super<Polygon>.draw() // call to Polygon.draw()
}
}
abstract. Un membre abstrait n'a pas
d'implémentation dans la classe. Le mot clé open n'est pas nécessaire, dans le cas d'une classe
ou méthode abstraite, car cela est évident.
open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
override abstract fun draw()
}
interface :
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
Implémenter une interface :
class Child : MyInterface {
override fun bar() {
// body
}
}
interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
interface Named {
val name: String
}
interface Person : Named {
val firstName: String
val lastName: String
override val name: String get() = "$firstName $lastName"
}
data class Employee(
// implementing 'name' is not required
override val firstName: String,
override val lastName: String,
val position: Position
) : Person
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
supe<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
Les interfaces A et B déclarent toutes les deux les fonctions
foo() et bar(). Note : par défaut les méthodes dans une interface, sans corps,
sont marquées comme abstraites, c'est pour cela que la méthode bar(')) de la classe A,
n'est pas explicitement marquée.Cdoit surcharger la méthode bar() et l'implémenter.D doit les implémenter. Cette règle s'applique aux méthodes
héritées avec une implémentation simple ( tel que bar() ) ou multimples (
foo() ).
open class User (var name: String, var firstName: String, var age: Int) {
open fun displayInformations() = println("$firstName $name is $age old")
}
class Admin(name: String, firstName: String, age: Int, var phoneNumber: String) : User(name, firstName, age) {
override fun displayInformations() = println("$firstName $name is $age old, phone number : $phoneNumber")
}
fun main() {
var currentUser = User(firstName = "Tristan", name = "SALAUN", age = 41)
currentUser.displayInformations()
currentUser = Admin(firstName = "Tristan", name = "SALAUN", age = 41, phoneNumber = "0123456789")
currentUser.displayInformations()
}
Dog, Bird, Duck et Snake avec les cris respectifs : "Waf", "Cui cui", "Coin coin" et
"Ssssssss". Validez en appellant la méthode speak sur les différentes instances d'animaux référencées par un même objet de type Animal.
interface Animal {
fun speak()
}
class Dog: Animal
class Bird: Animal
class Duck: Animal
class Snake: Animal
interface Animal {
fun speak()
}
class Dog: Animal { override fun speak() = println("Waf") }
class Bird: Animal { override fun speak() = println("Cui cui") }
class Duck: Animal { override fun speak() = println("Coin coin") }
class Snake: Animal { override fun speak() = println("Ssssssss") }
fun main() {
var myPet: Animal = Dog()
myPet.speak()
myPet = Bird()
myPet.speak()
myPet = Duck()
myPet.speak()
myPet = Snake()
myPet.speak()
}
fun printNumber(n : Number){
println("Using printNumber(n : Number)")
println(n.toString() + "\n")
}
fun printNumber(n : Int){
println("Using printNumber(n : Int)")
println(n.toString() + "\n")
}
fun printNumber(n : Double){
println("Using printNumber(n : Double)")
println(n.toString() + "\n")
}
fun main() {
val a : Number = 99
val b = 1
val c = 3.1
printNumber(a) //Which version of printNumber is getting used?
printNumber(b) //Which version of printNumber is getting used?
printNumber(c) //Which version of printNumber is getting used?
}
data :
data class User(val name: String, val age: Int)
Le compilateur va générer les membres suivant, en utilisant toutes les propriétés déclarées dans le
constructeur principal :equals()/hashCode().toString() sous la forme "User(name=Tristan, age=40)".componentN() correspondant aux propriétés dans leur ordre de déclaration.copy().var ou
val.
equals(),
hashCode() ou toString() dans le corps de la classe, ou une
implémentation marquée final dans la classe parente, alors ces fonctions ne sont
pas générés : c'est le code existant qui est utilisé.
componentN() qui sont open et retournent
des types compatibles, la fonction correspondante est générée pour la classe data et surcharge
celles du type parent. Si les fonctions du parent ne peuvent pas êtres surchargées, à cause
d'une signature incompatible, ou si elles sont finales, alors une erreur est signalée.
copy(...) avec une signature
correspondante est déprécié en Kotlin 1.2 et interdit en Kotlin 1.3.
componentN() et copy()
n'est pas autorisé.
data class User(val name: String = "", val age: Int = 0)
data class Person(val name: String) {
var age: Int = 0
}
Seule la propriété name sera utilisée dans les fonctions toString(), equals(),
hashCode() et copy()n et il n'y aura qu'une seule fonction
component1(). De ce fait, deux objets de type Person qui auraient des valeurs
pour la propriété age différentes, seront considérés comme égales.
data class Person(val name: String) {
var age: Int = 0
}
fun main() {
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
println("person1 == person2: ${person1 == person2}")
println("person1 with age ${person1.age}: ${person1}")
println("person2 with age ${person2.age}: ${person2}")
}
copy(). Pour la classe
User ci-dessous, l'implémentation serait la suivante :
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
Ce qui nous permet d'écrire :
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
Pair et Triple. Dans la
majorité des cas, les classes data sont plus appropriées, car elles permettent de nommer les propriétés,
ce qui rend le code beaucoup plus compréhensible.
data et utiliser la copie pour changer uniquement le prénom (pour déclarer un parent par
exemple) :
data class Person (var firstName: String = "", var lastName: String = "")
data class Person (var firstName: String = "", var lastName: String = "")
fun main() {
var personTristan = Person("Tristan", "SALAUN")
var personMelody = personTristan.copy(firstName = "Mélody")
println(personTristan)
println(personMelody)
}
List, et regardons comment l'affichage va se comporter.
data class Person (var firstName: String = "", var lastName: String = "", var list: List<Int>)
fun main() {
val listValue = listOf(1,2,3,4,5,6)
val personTristan = Person("Tristan", "SALAUN", listValue)
val personMelody = personTristan.copy(firstName = "Mélody")
println(personTristan)
println(personMelody)
}
Person(firstName=Tristan, lastName=SALAUN, list=[1, 2, 3, 4, 5, 6])
Person(firstName=Mélody, lastName=SALAUN, list=[1, 2, 3, 4, 5, 6])
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
Chaque constante de l'énumération est un objet. Les constantes sont séparées par une virgule.
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
Si une classe enum définie plusieurs membres, il faut séparer la définition des constantes de la
définition des membres, par un point virgule.
import java.util.function.BinaryOperator
import java.util.function.IntBinaryOperator
enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
PLUS {
override fun apply(t: Int, u: Int): Int = t + u
},
TIMES {
override fun apply(t: Int, u: Int): Int = t * u
};
override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}
fun main() {
val a = 13
val b = 31
for (f in IntArithmetics.values()) {
println("$f($a, $b) = ${f.apply(a, b)}")
}
}
EnumClass) :
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
La méthode valueOf() lève une IllegalArgumentException si le nom passé en
paramètre ne correspond pas à une valeur définie dans la classe.enumValues<T>() et
enumValueOf<T>() :
enum class RGB { RED, GREEN, BLUE }
inline fun <reified T : Enum<T>> printAllValues() {
print(enumValues<T>().joinToString { it.name })
}
printAllValues<RGB>() // prints RED, GREEN, BLUE
Chaque constante de la classe enum possède des propriétés pour obtenir son nom et sa position dans la
déclaration de la classe :
val name: String
val ordinal: Int
Les constantes de la classe enum implémentes aussi l'interface Comparable, avec un ordre
naturel qui est l'ordre de déclaration dans la classe.
Direction précédente, et utilisez un "switch" (when) pour afficher en clair la direction prise.
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
val direction = Direction.NORTH
when{
true -> TODO()
}
}
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
val direction = Direction.NORTH;
when(direction){
Direction.NORTH -> println("On va au Nord.")
Direction.SOUTH -> println("On va au Sud.")
Direction.EAST -> println("On va à l'Est.")
Direction.WEST -> println("On va à l'Ouest.")
}
}
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
inner pour avoir la possibilité d'accéder aux membres de
la classe englobante (outer class). Une classe imbriquée peut référencer un objet de la classe
englobante :
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
})
Note : dans la JVM, si l'objet est une instance d'une interface fonctionnelle Java (c'est-à-dire, une
interface avec une seule méthode abstraite), vous pouvez la créer en utilisant une expression lambda,
préfixée du type de l'interface :
val listener = ActionListener { println("clicked") }
sealed avant le nom de
la
classe. Une classe scellée, peut avoir des sous classes, mais toutes doivent être déclarées dans le même
fichier où celle-ci est déclarée. (Avant Kotlin 1.1, la règle était encore plus stricte : les classes
devaient être des classes imbriquées dans la déclaration de la classe scellée).
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
(L'exemple ci-dessus utilise la possibilité d'une fonctionnalité de Kotlin 1.1 : la possibilité pour les
classes data d'étendre une autre classe, incluant les classes scellées.)
when. Si il est possible de vérifier que les cas, couvrent toutes les possibilités, alors
il n'est pas nécessaire d'ajouter la clause else. toutefois, cela ne fonctionne que si vous
utilisez when en tant qu'expression et non comme une déclaration :
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
data class Substract(val e1: Expr, val e2: Expr) : Expr
data class Multiply(val e1: Expr, val e2: Expr) : Expr
data class Divide(val e1: Expr, val e2: Expr) : Expr
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
is Substract -> eval(expr.e1) - eval(expr.e2)
is Multiply -> eval(expr.e1) * eval(expr.e2)
is Divide -> eval(expr.e1) / eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
+ ou *) et des règles de
priorité
figées. Pour définir un opérateur, il suffit de coder la méthode membre, ou une fonction d'extension,
correspondant au nom de l'opérateur à définir. Les fonctions doivent être marquées avec le modifieur
operator.
| Expression | Traduites en |
|---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
+a, il
suit les étapes suivante :
a, par exemple le type T.unaryPlus()marquée avec le modifieur operator
sans paramètre, pour le type T.
R, alors
l'expression +a sera de type R.
Point :
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
fun main() {
println(-point) // prints "Point(x=-10, y=-20)"
}
| Expression | Traduites en |
|---|---|
a++ |
a.inc() |
a-- |
a.dec() |
a.inc() et a.dec() doivent retourner une valeur, qui sera
assignée à la variable sur laquelle l'opérateur ++ ou -- à été utilisé. Il ne
faut pas changer la valeur de l'objet original.
a++ :
a, par exemple le type T.inc()marquée avec le modifieur operator
sans paramètre, pour le type T.
T.a dans une variable temporaire a0.a.inc() à aa0 comme résultat de l'expression.a++ les étapes sont identiques.++a et --a, la résolution fonctionne de la même
manière, et l'effet est le suivant :
a.inc() à aa comme résultat de l'expression.Point.
data class Point(var x: Int, var y: Int)
Testez la différence entre point++ et ++point.
data class Point(var x: Int, var y: Int) {
operator fun inc() : Point {
return Point(this.x + 1, this.y + 1)
}
}
fun main() {
var point1 = Point(1,1)
println(point1)
point1++
println(point1)
}
| Expression | Traduites en |
|---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b), a.mod(b) (deprecated) |
a..b |
a.rangeTo(b) |
+ pour la classe Counter.
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
Exercice : proposer un exemple d'utilisation.
fun main() {
var counter: Counter = Counter(0)
println(counter)
println(counter + 3)
}
Point qui permet d'additionner 2 points (on additionne le x1 avec le x2 et le y1 avec le y2).
data class Point(var x: Int, var y: Int)
data class Point(var x: Int, var y: Int) {
operator fun plus(other: Point) : Point {
return Point(this.x + other.x, this.y + other.y)
}
}
Ou de manière équivalente :
data class Point(var x: Int, var y: Int)
operator fun Point.plus(other: Point) : Point { return Point(this.x + other.x, this.y + other.y) }
| Expression | Traduites en |
|---|---|
a in b |
b.contains(a) |
a !in b |
!b.contains(a) |
in et !in par rapport au précédent opérateurs.
| Expression | Traduites en |
|---|---|
a[i] |
a.get(i) |
a[i, j] |
a.get(i, j) |
a[i_1, ..., i_n] |
a.get(i_1, ..., i_n) |
a[i] = b |
a.set(i, b) |
a[i, j] = b |
a.set(i, j, b) |
a[i_1, ..., i_n] = b |
a.set(i_1, ..., i_n, b) |
get et set avec le nombre
d'arguments correspondant.
| Expression | Traduites en |
|---|---|
a() |
a.invoke() |
a(i) |
a.invoke(i) |
a(i, j) |
a.invoke(i, j) |
a(i_1, ..., i_n) |
a.invoke(i_1, ..., i_n) |
invoke avec le nombre de paramètres approprié.
| Expression | Traduites en |
|---|---|
a += b |
a.plusAssign(b) |
a -= b |
a.minusAssign(b) |
a *= b |
a.timesAssign(b) |
a /= b |
a.divAssign(b) |
a %= b |
a.remAssign(b), a.modAssign(b) (deprecated) |
a += b, le compilateur effectue les
étapes suivantes :
plus() pour plusAssign(), alors remonter une erreur (car il y
a une ambiguïté).
Unit.a.plusAssign(b).a = a + b (avec une vérification du type de
a + b qui doit retourner un sous type de a.
| Expression | Traduites en |
|---|---|
a == b |
a?.equals(b) ?: (b === null) |
a != b |
!(a?.equals(b) ?: (b === null)) |
equals(other: Any?): Boolean qui peut
être surchargée pour fournir une fonction personnalisée de comparaison d'égalité. Toute autre fonction
avec le même nom (par exemple equals(other: Foo)) ne sera pas appelée.=== et !== (tests
d'identité), donc aucune convention n'existe pour ces opérateurs.== est spéciale : elle est traduite en une expression complexe qui recherche les
valeurs nulles. null == null est toujours vrai, et x == null est toujours
faux, et n'invoque pas x.equals().
fun main() {
val first = Integer(10)
val second = Integer(10)
println(first == second) // 1
println(first.equals(second)) // 2
println(first === second) // 3
}
A votre avis, quel sera l'affichage du code ci-dessus ?
| Expression | Traduites en |
|---|---|
a > b |
a.compareTo(b) > 0 |
a < b |
a.compareTo(b) < 0 |
a >= b |
a.compareTo(b) >= 0 |
a <= b |
a.compareTo(b) <= 0 |
compareTo, qui doit
nécessairement retourner un Int.
fold pour les collections, qui prend une valeur initiale pour l'accumulateur, et une fonction pour combiner
les items. Le
résultat est obtenu en appliquant la fonction sur l'item courant, et l'accumulateur, pour en faire changer la valeur. Exemple :
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
Dans le code ci-dessus, le paramètre combine à un type de fonction (R, T) -> R, donc il accepte une fonction qui prend 2 arguments, de
types R
et T et retourne une valeur de type R. Cette fonction est appelée dans une boucle for, et la valeur de retour est ensuite
assignée à l'accumulateur.
fold, nous devons passer une instance de type fonction en argument, et les expression lambdas sont couramment utilisées à cet
usage :
val items = listOf(1, 2, 3, 4, 5)
// Lambdas are code blocks enclosed in curly braces.
items.fold(0, {
// When a lambda has parameters, they go first, followed by '->'
acc: Int, i: Int ->
println("acc = $acc, i = $i, ")
val result = acc + i
println("result = $result")
// The last expression in a lambda is considered the return value:
result
})
// Parameter types in a lambda are optional if they can be inferred:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
// Function references can also be used for higher-order function calls:
val product = items.fold(1, Int::times)
invoke(...) : par exemple f.invoke(x) ou
simplement
f(x).1.foo(2).
fun main() {
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // extension-like call
}
max(strings, { a, b -> a.length < b.length })
La fonction max est une fonction d'ordre supérieur, qui prend une fonction en second paramètre. Cette argument est une expression, qui est elle même
une fonction : une
fonction littérale qui correspond à la fonction nommée suivante :
fun compare(a: String, b: String): Boolean = a.length < b.length
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
Une expression lambda est toujours entourée d'accolades. Les paramètres dans la syntaxe complète, sont déclarés dans ces accolades, et leur typage est optionnel. Le
corp est situé
après la ->. Si le type de retour inféré de la lambda n'est pas Unit, la dernière (et possiblement seule) expression dans le corps de la
lambda est
considéré comme la valeur de retour.
val sum = { x: Int, y: Int -> x + y }
val product = items.fold(1) { acc, e -> acc * e }
Cette notation est appelée lambda de fin (trailing lambda).
run { println("...") }
->. Le paramètre sera implicitement déclaré avec le mon it :
var ints = listOf(0, 1,-2,3,-1)
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
_ à la place du paramètre :
map.forEach { _, value -> println("$value!") }
Int et stockez la dans une variable. Appelez cette lambda stockée à partir de la variable avec la
fonction invoke().
val sum = TODO()
fun main() {
val sum = { x: Int, y: Int -> x + y }
println(sum.invoke(3, 5))
}
Ou de manière plus concise :
fun main() {
val sum = { x: Int, y: Int -> x + y }
println(sum(3, 5))
}
executeOperation écrite ci-dessous.
inline fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int) = operation(x, y)
inline permet d'optimiser l'appel de la méthode (il est optionnel).
val addition: (Int, Int) -> Int = TODO()
val subtraction: (Int, Int) -> Int = TODO()
val multiplication: (Int, Int) -> Int = TODO()
val division: (Int, Int) -> Int = TODO()
inline fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int) = operation(x, y)
fun main() {
println(executeOperation(10, 5, addition))
println(executeOperation(10, 5, subtraction))
println(executeOperation(10, 5, multiplication))
println(executeOperation(10, 5, division))
}
val addition = { x: Int, y: Int -> x + y }
val subtraction = { x: Int, y: Int -> x - y }
val multiplication = { x: Int, y: Int -> x * y }
val division = { x: Int, y: Int -> x / y }
inline fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int) = operation(x, y)
fun main() {
println(executeOperation(10, 5, addition))
println(executeOperation(10, 5, subtraction))
println(executeOperation(10, 5, multiplication))
println(executeOperation(10, 5, division))
}
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "User clicked button");
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "User clicked button");
}
});
button.setOnClickListener ( v -> {
Log.d(TAG, "User clicked button");
});
button.setOnClickListener( { v: View -> Log.d(TAG, "User clicked button"); })
button.setOnClickListener( { v: View -> Log.d(TAG, "User clicked button"); } )
button.setOnClickListener( { v: View -> Log.d(TAG, "User clicked button") } )
button.setOnClickListener() { v: View -> Log.d(TAG, "User clicked button") }
button.setOnClickListener { v: View -> Log.d(TAG, "User clicked button") }
button.setOnClickListener { v -> Log.d(TAG, "User clicked button") }
button.setOnClickListener { Log.d(TAG, "User clicked button") }
button.setOnClickListener{ Log.d( TAG, "User clicked button" ) }
Qui correspond au code Java :
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "User clicked button");
}
});
Ou encore :
button.setOnClickListener ( v -> {
Log.d(TAG, "User clicked button");
});
swap à MutableList<Int>
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Le mot clé this, dans la fonction d'extension, fait référence à l'objet receveur (qui est
juste avant le point). Maintenant, nous pouvons appeler cette fonction sur tous les objets de
type MutableList<Int>.
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'
Testez ce code pour en comprendre la puissance.
String qui permet de mettre la première lettre d'une chaîne de caractère en majuscule et le reste en minuscule
(pour afficher par exemple un prénom, non composé).
fun String.firstUpper(): Any = TODO()
// Exemple d'appel :
fun main() {
println("tristan".firstUpper()) // => Tristan
println("TRISTAN".firstUpper()) // => Tristan
}
fun String.firstUpper() = this.first().toUpperCase() + this.substring(1).toLowerCase()
fun main() {
println("tristan".firstUpper())
println("TRISTAN".firstUpper())
}
Autre solution :
fun String.firstUpper(): Any = this.toLowerCase().capitalize()
Encore plus concis (sans utiliser le this) :
fun String.firstUpper() = toLowerCase().capitalize()
MutableListMutableList <T>, et
peut donc être générique :
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Le type du receveur est déclaré avant le nom de la fonction, pour qu'il soit disponible à ce moment la.
fun main() {
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
}
s, qui est de type Shape.
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
Le code affiche "Class method".
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
Le code affiche "Extension function".
this ==
null dans le corps de la fonction. C'est ce qui nous permet de toujours pouvoir faire appel à la
méthode toString() en Kotlin, sans avoir besoin de tester la nullité. La vérification
s'effectue dans la fonction d'extension.
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
Le code affiche "Extension function".
val <T> List<T>.lastIndex: Int
get() = size - 1
firstLetter pour la classe StringBuilder qui permet d'accéder à la première lettre contenue dans
l'objet.
var StringBuilder.firstLetter: Char
get() = TODO()
set(value) = TODO()
import java.lang.StringBuilder
var StringBuilder.firstLetter: Char
get() = get(0)
set(value) = this.setCharAt(0, value)
fun main() {
val message = StringBuilder("hello world !")
println("${message.firstLetter} is the first letter of $message")
message.firstLetter = 'H'
println("${message.firstLetter} is the first letter of $message")
}
A noter, que le this dans this.setCharAt(0, value) est optionnel.
var ints = listOf(1, 2, -2, 3, 4, -15, 5, 6, -1)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
println(sum)
sum située en dehors des accolades.Ints contenus dans la variable ints.
var ints = listOf(1, 2, -2, 3, 4, -15, 5, 6, -1)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
println(sum)
fun main() {
var ints = listOf(1, 2, -2, 3, 4, -15, 5, 6, -1)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
println(sum)
}
B ait les même fonctionnalités que la classe A et que la classe B
soit une classe A. Dans ce cas là, vous pouvez utiliser l'héritage. Cela donne une relation permanente entre les
classes. En utilisant
la délégation, vous pouvez passer en paramètre un autre objet d'un autre type, un sous type de la classe A, par exemple, à l'instance de B. Ce qui rend le mécanisme
de délégation
extrêmement puissant.
interface Showable {
void show();
}
class View implements Showable {
@Override
public void show() {
System.out.println("View.show()");
}
}
class CustomView implements View {
@Override
public void show() {
System.out.println("CustomView.show()");
}
}
class Screen implements Showable {
private Showable showable;
Screen(Showable showable) {
this.showable = showable;
}
@Override
public void show() {
showable.show();
}
}
Showable view = new View();
Showable customView = new CustomView();
new Screen(view).show(); //View.show()
new Screen(customView).show(); //CustomView.show()
interface Nameable {
var name: String
}
class JackName : Nameable {
override var name: String = "Jack"
}
class LongDistanceRunner: Runnable {
override fun run() {
println("long")
}
}
class Person(name: Nameable, runner: Runnable): Nameable by name, Runnable by runner
fun main(args: Array<String>) {
val person = Person(JackName(), LongDistanceRunner())
println(person.name) //Jack
person.run() //long
}
by.
class TristanName : Nameable et class ShortDistanceRunner: Runnable qui
seront utilisées par la classe Person :
fun displayPerson(person: Person){
println(person.name)
person.run()
}
interface Nameable {
var name: String
}
class JackName : Nameable {
override var name: String = "Jack"
}
class TristanName : Nameable {
override var name: String = "Tristan"
}
class LongDistanceRunner : Runnable {
override fun run() {
println("long")
}
}
class ShortDistanceRunner : Runnable {
override fun run() {
println("short")
}
}
class Person(name: Nameable, runner: Runnable) : Nameable by name, Runnable by runner
fun displayPerson(person: Person){
println(person.name)
person.run()
}
fun main(args: Array<String>) {
val person = Person(JackName(), LongDistanceRunner())
displayPerson(person)
val person2 = Person(TristanName(), ShortDistanceRunner())
displayPerson(person2)
}
Nameable, par exemple firstName, companyName et
cityName, il faut implémenter ces nouveaux attributs dans les classes dérivées, et tester que notre objet Person à bien hérité de
ces nouvelles propriétés .
interface Nameable {
var name: String
var firstName: String
var companyName: String
var cityName: String
}
class JackName : Nameable {
override var name: String = "Jack"
override var firstName: String = "Terence"
override var companyName: String = "IBM"
override var cityName: String = "London"
}
class TristanName : Nameable {
override var name: String = "SALAUN"
override var firstName: String = "Tristan"
override var companyName: String = "STDev"
override var cityName: String = "Marseille"
}
class LongDistanceRunner : Runnable {
override fun run() {
println("long")
}
}
class ShortDistanceRunner : Runnable {
override fun run() {
println("short")
}
}
class Person(name: Nameable, runner: Runnable) : Nameable by name, Runnable by runner
fun displayPerson(person: Person){
println("${person.firstName} ${person.name} of ${person.companyName} from ${person.cityName}")
person.run()
}
fun main(args: Array<String>) {
val person = Person(JackName(), LongDistanceRunner())
displayPerson(person)
val person2 = Person(TristanName(), ShortDistanceRunner())
displayPerson(person2)
}
. :
class Foo {
var prop: String? = null
}
val foo = Foo()
foo.prop = "something"
val another = foo.prop
Vous pouvez aussi écrire des getters et des setters sur mesure :
var str: String
get() = this.toString()
set(value) {
println(value)
field = value
}
import kotlin.reflect.KProperty
class Example {
val someName by NameDelegate()
val otherName by NameDelegate()
}
class NameDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return property.name
}
}
fun main() {
val example = Example()
println(example.someName)
println(example.otherName)
}
Que va afficher le code ci-dessus ?someName, et pourquoi ?
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
Reprenez l'exemple précédent, sans changer le code de la fonction, main, et remplacer la délégation NameDelegate par la délégation
Delegate ci-dessus. Que constatez vous ?
lazylazy :
val myVar: String by lazy {
println("Lazy init")
"Hello"
}
println("myVar is not initialized yet")
println(myVar + " My dear friend")
De manière plus classique/concise, nous utiliserons :
val myString by lazy { "Some Value" }
Que va afficher le code ci-dessus ?
observableobservable se trouve dans la classe Delegates. Elle prend en paramètre une
valeur initiale
et une lambda qui sera exécutée à chaque fois que la valeur du champ sera modifiée, sa signature est la suivante :
inline fun <T> observable(
initialValue: T,
crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit
): ReadWriteProperty<Any?, T>
var updated = false
var max: Int by Delegates.observable(0) { property, oldValue, newValue ->
updated = true
}
println(max) // 0
println("updated is ${updated}")
max = 10
println(max) // 10
println("updated is ${updated}")
Que va afficher le code ci-dessus ?
by observable pour la variable maxCount qui affichera le nom de la propriété modifiée, son ancienne valeur
et la nouvelle.
fun main() {
var maxCount: Int = 0
maxCount = 5
maxCount = 10
maxCount = 20
}
Qu'affiche le code ci-dessus ?
import kotlin.properties.Delegates
fun main() {
var maxCount: Int by Delegates.observable(initialValue = 0) { property, oldValue, newValue ->
println("${property.name} is being changed from $oldValue to $newValue")
}
maxCount = 5
maxCount = 10
maxCount = 20
}
vetoableobservable, sauf que la lambda, doit retourner un Boolean, qui indique si la valeur doit être
modifiée, on non.
var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
newValue > 0
}
age déclarée comme vu précédemment :
var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
newValue > 0
}
import kotlin.properties.Delegates.vetoable
fun main() {
var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
newValue > 0
}
age = 10
println(age)
age = -1
println(age)
age = 30
println(age)
age = 0
println(age)
}
import kotlin.properties.Delegates
fun main() {
var age: Int by Delegates.vetoable(initialValue = 0) { property, oldValue, newValue ->
if (newValue > 0) true else {println("${property.name} rejected value $newValue staying at value $oldValue"); false}
}
age = 10
println(age)
age = -1
println(age)
age = 30
println(age)
age = 0
println(age)
}
notNulllateinit, dans le sens ou il lève une IllegalStateException si la
variable est accédé avant d'être initialisée.
var age by notNull<Int>()
fun main() = println(age)
var person1: Person // Erreur sur cette ligne. à corriger.
fun main(args: Array<String>) {
// initializing variable lately
person1 = Person("Ted",28)
print(person1.name + " is " + person1.age.toString())
}
data class Person(var name:String, var age:Int)
var person1 by notNull<Person>()
fun main(args: Array<String>) {
// initializing variable lately
person1 = Person("Ted",28)
print(person1.name + " is " + person1.age.toString())
}
data class Person(var name:String, var age:Int)
Une remarque sur les bonnes pratiques utilisées (ou pas) dans cet exemple ?
ReadWriteProperty.
Pour tester son usage nous écrirons une classe Demo contenant un attribut var name qui sera déléguée à notre
class LogDelegate<T> :
class LogDelegate<T> : ReadWriteProperty<Any, T?> {}
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class LogDelegate<T> : ReadWriteProperty<Any, T?> {
private var value: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T? {
println("LOG get $value")
return value
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
println("LOG set : $value")
this.value = value
}
}
class Demo {
var name by LogDelegate<String>()
}
val d = Demo()
fun main() {
d.name = "Tristan"
println(d.name)
}
class Box<T>(t: T) {
var value = t
}
En général pour créer une instance de ce genre de classe, nous devons fournir le type de l'argument :
val box: Box<Int> = Box<Int>(1)
Mais si le type du paramètre peut être inféré, par exemple depuis le type des paramètres du constructeur, ou
par un autre moyen, alors il est possible d'omètre le type des arguments :
val box = Box(1) // 1 est de type Int, donc le compilateur peut en déduire que nous utilisons un type : Box<Int>
open class A
open class B : A()
Considérons les types suivants :
MutableList<A>
MutableList<B>
MutableList ?
open class A
open class B : A()
fun main() {
var objectA = A()
var objectB = B()
var listofA: MutableList<A> = mutableListOf<A>()
var listofB: MutableList<B> = mutableListOf<B>()
println("objectA is A " + (objectA is A))
println("objectB is B " + (objectB is B))
println("objectA is B " + (objectA is B))
println("objectB is A " + (objectB is A))
println("listofA is MutableList<A> " + (listofA is MutableList<A>))
println("listofB is MutableList<B> " + (listofB is MutableList<B>))
println("listofA is MutableList<B> " + (listofA is MutableList<B>))
println("listofB is MutableList<A> " + (listofB is MutableList<A>))
}
MutableList est invariante.
open class A
open class B : A()
Considérons les types en lecture seule suivants :
List<A>
List<B>
List ?
open class A
open class B : A()
fun main() {
var objectA = A()
var objectB = B()
var listofA: List<A> = listOf<A>()
var listofB: List<B> = listOf<B>()
println("objectA is A " + (objectA is A))
println("objectB is B " + (objectB is B))
println("objectA is B " + (objectA is B))
println("objectB is A " + (objectB is A))
println("listofA is List<A> " + (listofA is List<A>))
println("listofB is List<B> " + (listofB is List<B>))
println("listofA is List<B> " + (listofA is List<B>))
println("listofB is List<A> " + (listofB is List<A>))
}
List est covariant : List<B> est un sous-type de List<A>.List<T> étant immutable, lors de l'affectation de List<B> dans une variable de type List<A>,
une copie de la liste est effectuée.
abstract class Animal(val size: Int)
class Dog(val cuteness: Int): Animal(100)
class Spider(val terrorFactor: Int): Animal(1)
Dog et Spider sont bien des sous types de Animal :
val dog: Dog = Dog(10)
val spider: Spider = Spider(9000)
var animal: Animal = dog
animal = spider
Tout est OK.
ListList :
val dogList: List<Dog> = listOf(Dog(10), Dog(20))
val animalList: List<Animal> = dogList
La liste étant immuable (impossible de changer sa valeur après initialisation), cela fonctionne bien, car une copie de la liste est réalisée.interface Compare<T> avec une méthode
compare(T item1, T item2), qui permet de comparer deux items.
val dogCompare: Compare<Dog> = object: Compare<Dog> {
override fun compare(first: Dog, second: Dog): Int {
return first.cuteness - second.cuteness
}
}
Essayons de l'affecter à une variable qui permet de comparer les Animal :
val animalCompare: Compare<Animal> = dogCompare // Compiler error
val animalCompare: Compare<Animal> = object: Compare<Animal> {
override fun compare(first: Animal, second: Animal): Int {
return first.size - second.size
}
}
val spiderCompare: Compare<Spider> = animalCompare // Works nicely!
open class Fruit
open class Apple: Fruit() // Apple extends Fruit
class Gala: Apple() // Gala extends Apple
class Variance{
val fruitProducer: () -> Fruit = ::Fruit
val appleProducer: () -> Apple = ::Apple
val galaProducer: () -> Gala = ::Gala
val fruitConsumer: (Fruit) -> Unit = ::eatFruit
val appleConsumer: (Apple) -> Unit = ::eatApple
val galaConsumer: (Gala) -> Unit = ::eatGala
val newFruitProducer1: () -> Fruit = appleProducer
val newFruitProducer2: () -> Fruit = galaProducer
val newAppleProducer: () -> Apple = galaProducer
val newGalaConsumer1: (Gala) -> Unit = appleConsumer
val newGalaConsumer2: (Gala) -> Unit = fruitConsumer
val newAppleConsumer: (Fruit) -> Unit = fruitConsumer
}
fun eatFruit(fruit: Fruit) {}
fun eatApple(apple: Apple) {}
fun eatGala(fruit: Gala) {}
fun foo(x: Any) {
if (x is Person) {
println("${x.name}") // This wouldn't compile outside the if
}
}
Notez que nous n'avons pas besoin de caster l'objet x, pour quelle raison, à votre avis ?
val p = x as Person
Si l'objet n'est pas vraiment de type Person (ou d'une sous classe), alors l'exception ClassCastException sera levée.Person, vous pouvez utiliser
as?. Notez que le
type de retour sera Person? :
val p = x as? Person
x as Person? pour caster un type qui peut être null. La différence entre cette solution et la précédente as? est que
celle-ci échouera si
x est une instance non nulle d'un autre type que Person :
val p = x as Person?
x avec les 2 méthodes (as et as?).x de type Any contenant une instance de Person, et ensuite contenant une instance de
Other.
class Person(var name: String)
class Other()
Vous testerez ensuite la différence entre les 2 autres types de cast (String en Int) :
val value = "Message"
println(value as? Int)
println(value as Int?)
class Person(var name: String)
class Other()
fun main() {
fun foo(x: Any) {
if (x is Person) {
println("${x.name}") // This wouldn't compile outside the if
}
}
var x: Any = Person("SALAUN")
var p = x as Person
var pNull = x as? Person
x = Other()
p = x as Person
pNull = x as? Person
val value = "Message"
println(value as? Int)
println(value as Int?)
}
import java.util.ArrayList;
import java.util.List;
public class Test {
private static int getDefaultSize(Object object){
if(object instanceof String) {
return ((String) object).length();
} else if(object instanceof List) {
return ((List) object).size();
}
return 0;
}
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
System.out.println(getDefaultSize(list));
System.out.println(getDefaultSize("list"));
}
}
private fun getDefaultSize(anyObject: Any): Int {
if (anyObject is String) {
return anyObject.length
} else if (anyObject is List<*>) {
return anyObject.size
}
return 0
}
Ou encore plus concis :
private fun getDefaultSize(anyObject: Any) = when (anyObject) {
is String -> anyObject.length
is List<*> -> anyObject.size
else -> 0
}
Pair : qui contient 2 éléments.Triple : qui contient 3 éléments.Pairfirst contient la première valeur.second contient la seconde valeur.Triplefirst contient la première valeur.second contient la seconde valeur.third contient la troisième valeur.Pair de valeur, et les afficher séparément.Triple.
data class Pair<out A, out B> : Serializable
data class Triple<out A, out B, out C> : Serializable
fun main() {
val pairValue= Pair(1, "x")
val tripleValue = Triple("Tristan","SALAUN", 40)
println(pairValue.first)
println(pairValue.second)
println(tripleValue.first)
println(tripleValue.second)
println(tripleValue.third)
}
data class Person(var name:String, var age: Int)
val tristan = Person("Tristan", 40)
val (name, age) = tristan
Cette syntaxe est appelée "destructuring declaration". Cette déclaration crée plusieurs variables en une seule fois. Dans notre exemple, les 2 variables déclarées,
peuvent être utilisées de manière indépendantes :
println(name)
println(age)
val name = person.component1()
val age = person.component2()
Les fonctions component1() et component2() est un nouvel exemple des conventions utilisées dans Kotlin ( tout comme +,
*,
...).
for ((a, b) in collection) { ... }
component3(), component4(), etc.
result et un status. Une façon pratique de le faire, en
Kotlin, est de
déclarer une classe data, et retourner une instance de cet objet.
data class Result(val result: Int, val status: Status)
fun function(...): Result {
// computations
return Result(result, status)
}
// Now, to use this function:
val (result, status) = function(...)
Les classes data déclarent automatiquement les fonctions componentN(), donc la déstructuration fonctionne directement dans notre cas.
Pair dans des variables a et b.Triple avec c, d et e.
fun main() {
val (a, b) = Pair(1, "x")
val (c, d, e) = Triple("Tristan","SALAUN", 40)
println(a)
println(b)
println(c)
println(d)
println(e)
println("a = $a, b = $b, c = $c, d = $d and e = $e")
}
Map_Throwable. Toutes les exceptions ont un message, une pile d'exécution (stack
trace), et une cause optionnelle.throw :
throw Exception("Hi There!")
Pour attraper une exception, il faut utiliser l'expression try :
try {
// some code
}
catch (e: SomeException) {
// handler
}
finally {
// optional finally block
}
Il peut y avoir 0 ou plus blocs de catch, le block finally est optionnel. Toutefois il doit y avoir au moins un block catch ou un block
finally.
val numValue: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
La valeur retournée par l'expression est, soit la dernière expression du block try, ou la dernière expression du bloc catch. Le contenu du block finally
n'affecte pas le résultat de l'expression.
input ( "2005" et "azerty") pour affecter la valeur à numValue :
val numValue: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
import java.lang.Integer.parseInt
fun main() {
var input = "2005"
var numValue: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
println(numValue)
input = "azerty"
numValue = try { parseInt(input) } catch (e: NumberFormatException) { null }
println(numValue)
}
const.
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
companion object {
private const val TAG = "ClassName"
}
setup dans un test unitaire. Nous ne voulons
toutefois pas que la variable puisse avoir une valeur nulle, pour ce faire, nous pouvons utiliser le
modifieur lateinit :
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
var, déclarées dans le corps de la
classe (pas dans le constructeur primaire, et seulement si la propriété n'a pas d'accesseurs sur mesures
(custom getter or setter).
lateinit var allByDefault: String // error: explicit initializer required, default getter and setter implied
print(allByDefault) // KO, Exception : kotlin.UninitializedPropertyAccessException: lateinit property allByDefault has not been initialized
lateinit var à bien été initialisée, on peut
utiliser .isInitialized :
if (foo::bar.isInitialized) {
println(foo.bar)
}
class MyTest() {
lateinit var subject: String
fun displaySubject() {
println(subject)
}
}
fun main() {
var myTest = MyTest()
myTest.displaySubject()
}
fun main() {
var myTest = MyTest()
myTest.subject = "The best subject"
myTest.displaySubject()
}
| Nom de l'annotation | Usage |
|---|---|
| @Target | Liste de tous les types d'éléments possibles qui peuvent être annotés avec cette annotation. |
| @Retention | Définie si l'annotation est stockée dans la fichier de la classe compilée, et s'il est visible via la réflexion lors de l'éxécution du programme. |
| @Repeatable | Définie si l'annotation est applicable plusieurs fois sur un même bloc de code. |
| @MustBeDocumented | Spécifie que l'annotation fait partis d'une API publique et doit donc être inclue dans la classe ou méthode. |
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class MyClass
annotation avant la class.
annotation class MyClass
constructor lors de la déclaration, et placer
l'annotation avant ce mot clé.
class MyClass @Inject constructor( dependency: MyDependency){
//. . .
}
import java.lang.annotation.ElementType
import java.lang.annotation.RetentionPolicy
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Ann(val value: Int)
Utilisation de l'annotation :
@Ann(value = 10)
class MyClass
fun main(args: Array<String>) {
val c = MyClass()
val x = c.javaClass.getAnnotation(Ann::class.java)
println("Value:${x?.value}")
}
Que va afficher le code ci-dessus ?
Person :
firstName de type chaîne de caractère en lecture/écriture.lastName de type chaîne de caractère en lecture.Friend qui hérite de Person qui contiens en plus :
Short : age en lecture/écriture.Colleague qui hérite de Person qui contiens en plus :
role qui ne peux prendre que les valeurs suivantes : MANAGER, CEO, WORKER.Contact qui hérite de Person qui contiens en plus :
phoneNumber de type chaîne de caractère en lecture.email de type chaîne de caractère en lecture.
open class Person(var firstName: String, var lastName: String)
class Friend(firstName: String, lastName: String, var age: Short) : Person(firstName, lastName)
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName)
class Contact(firstName: String, lastName: String, var phoneNumber: String, var email: String) : Person(firstName, lastName)
enum class Role {
MANAGER, CEO, WORKER
}
type du numéro, qui ne peux prendre que les valeurs suivantes : MOBILE, FIX, FAX.value de type chaîne de caractère, avec une valeur initiale de "0000000000", qui ne peut prendre comme valeur qu'une
chaîne de caractère, composée de 10 chiffres (utilisation de vetoable).
import kotlin.properties.Delegates
open class Person(var firstName: String, var lastName: String)
class Friend(firstName: String, lastName: String, var age: Short) : Person(firstName, lastName)
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName)
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String) : Person(firstName, lastName)
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(type: PhoneType, value: String) {
var value: String by Delegates.vetoable("0000000000") { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
Friend afin de ne pouvoir définir un age qu'avec une valeur valide :
MAX_AGE définie avec la valeur : 150.
import kotlin.properties.Delegates
open class Person(var firstName: String, var lastName: String)
class Friend(firstName: String, lastName: String, age: Short) : Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
}
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName)
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, varemail: String) : Person(firstName, lastName)
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(type: PhoneType, value: String) {
var value: String by Delegates.vetoable("0000000000") { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
toString abstraite à notre classe Person.
import kotlin.properties.Delegates
abstract class Person(var firstName: String, var lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short) : Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}"
}
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String) :
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(type: PhoneType, value: String) {
var value: String by Delegates.vetoable("0000000000") { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
Person.
import kotlin.properties.Delegates
abstract class Person(var firstName: String, var lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short) : Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}"
}
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String) :
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person:Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
else -> "Inconnu"
}
fun main() {
val person: Person = Friend("Tristan", "SALAUN", 42.toShort())
val person2: Person = Friend("Melody", "SALAUN", 25.toShort())
val person3: Person = Colleague("Nicolas", "DUPOND", Role.MANAGER)
val person4: Person = Colleague("Jean-Christophe", "ANDRE", Role.WORKER)
val person5: Person = Contact("John", "DOE", PhoneNumber(PhoneType.MOBILE, "0612345678"), "john.doe@test.com")
val personList = listOf(person, person2, person3, person4, person5)
var counter = 0
personList.forEach { println("${counter++} ${getType(it)} $it") }
}
getType, qui nous permet d'afficher le type d'objet en français, nous avons dû utiliser le else.
Si nous avons besoin d'implémenter une nouvelle classe fille, il est fort probable, que nous oublions d'ajouter ce cas dans cette méthode.
Pour fiabiliser le code, nous allons mettre en place une classe scellée pour la classe Person.
import kotlin.properties.Delegates
sealed class Person(var firstName: String, var lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short) : Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}"
}
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String) :
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person:Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val person: Person = Friend("Tristan", "SALAUN", 42.toShort())
val person2: Person = Friend("Melody", "SALAUN", 25.toShort())
val person3: Person = Colleague("Nicolas", "DUPOND", Role.MANAGER)
val person4: Person = Colleague("Jean-Christophe", "ANDRE", Role.WORKER)
val person5: Person = Contact("John", "DOE", PhoneNumber(PhoneType.MOBILE, "0612345678"), "john.doe@test.com")
val personList = listOf(person, person2, person3, person4, person5)
var counter = 0
personList.forEach { println("${counter++} ${getType(it)} $it") }
}
Address qui sera une propriété optionnelle de la classe Friend, avec les propriétés suivantes :
line1 de type chaîne de caractère, obligatoire.line2 de type chaîne de caractère, qui peut prendre une valeur nulle.cp de type chaîne de caractère, au format similaire au numéro de téléphone : longueur de 5, ne contenant que des chiffres.city de type chaîne de caractère, obligatoire.country de type chaîne de caractère, prenant la valeur "France", par défaut.
import kotlin.properties.Delegates
sealed class Person(var firstName: String, var lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short, var address: Address? = null) : Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}${if (address != null) "\n$address" else ""}"
}
class Colleague(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String) :
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
class Address(val line1: String, val line2: String? = null, cp: String, val city: String, val country: String = "France") {
var cp: String by Delegates.vetoable(cp) { property, oldValue, newValue ->
newValue.length == 5 && isNumber(newValue)
}
override fun toString() = "$line1\n${if (!line2.isNullOrEmpty()) "$line2\n" else ""}$cp $city\n$country"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person:Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val person: Person = Friend("Tristan", "SALAUN", 42.toShort())
val person2: Person = Friend("Melody", "SALAUN", 25.toShort())
val person2b: Person = Friend("Jean", "DUPONT", 55.toShort(), Address("36, quai des Orfèvres", cp = "75001", city = "Paris"))
val person3: Person = Colleague("Nicolas", "DUPOND", Role.MANAGER)
val person4: Person = Colleague("Jean-Christophe", "ANDRE", Role.WORKER)
val person5: Person = Contact("John", "DOE", PhoneNumber(PhoneType.MOBILE, "0612345678"), "john.doe@test.com")
val personList = listOf(person, person2, person3, person4, person5, person2b)
var counter = 0
personList.forEach { println("${counter++} ${getType(it)} $it") }
}
Person, qui retourne une instance d'une des classes fille.
import kotlin.random.Random
class RandomValues {
companion object {
val firstNameList = listOf("Jean", "Pierre", "Clément")
val lastNameList = listOf("DUPOND", "DUPONT", "MARTIN")
fun getFirstName(): String {
return firstNameList[Random.nextInt(0, firstNameList.size)]
}
fun getLasrName(): String {
return lastNameList[Random.nextInt(0, lastNameList.size)]
}
fun getRole(): Role {
return Role.values()[Random.nextInt(0, Role.values().size)]
}
fun getPhoneNumber(): PhoneNumber {
return PhoneNumber(PhoneType.MOBILE, "0000000000")
}
fun getEmail(): String {
return "test@test.com"
}
}
}
import kotlin.properties.Delegates
import kotlin.random.Random
sealed class Person(var firstName: String, var lastName: String) {
abstract override fun toString(): String
companion object {
fun getPerson(): Person {
val typeId = Random.nextInt(0, 3)
return when (typeId) {
0 -> Friend.getFriend()
1 -> Colleague.geColleague()
2 -> Contact.geContact()
else -> Friend.getFriend()
}
}
}
}
class Friend private constructor(firstName: String, lastName: String, age: Short, var address: Address? = null) :
Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}${if (address != null) "\n$address" else ""}"
companion object {
fun getFriend() =
Friend(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLasrName(),
age = Random.nextInt(0, MAX_AGE).toShort()
)
}
}
class Colleague private constructor(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
companion object {
fun geColleague() =
Colleague(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLasrName(),
role = RandomValues.getRole()
)
}
}
class Contact private constructor(
firstName: String,
lastName: String,
var phoneNumber: PhoneNumber,
var email: String
) :
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
companion object {
fun geContact() =
Contact(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLasrName(),
phoneNumber = RandomValues.getPhoneNumber(),
email = RandomValues.getEmail()
)
}
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
class Address(
val line1: String,
val line2: String? = null,
cp: String,
val city: String,
val country: String = "France"
) {
var cp: String by Delegates.vetoable(cp) { property, oldValue, newValue ->
newValue.length == 5 && isNumber(newValue)
}
override fun toString() = "$line1\n${if (!line2.isNullOrEmpty()) "$line2\n" else ""}$cp $city\n$country"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person: Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val randomPersonList = mutableListOf<Person>()
for (i in 0..10) {
randomPersonList.add(Person.getPerson())
}
var counter = 0
randomPersonList.forEach { println("${counter++} ${getType(it)} $it") }
}
JsonExport qui comporte une méthode toJson qui va nous permettre d'exporter nos contacts en JSON.
interface JsonExport {
fun toJson(): String
}
sealed class Person(var firstName: String, var lastName: String) : JsonExport {
....
}
import kotlin.properties.Delegates
import kotlin.random.Random
interface JsonExport {
fun toJson(): String
}
sealed class Person(var firstName: String, var lastName: String) : JsonExport {
abstract override fun toString(): String
companion object {
fun getPerson(): Person {
val typeId = Random.nextInt(0, 3)
return when (typeId) {
0 -> Friend.getFriend()
1 -> Colleague.geColleague()
2 -> Contact.geContact()
else -> Friend.getFriend()
}
}
}
}
class Friend private constructor(firstName: String, lastName: String, age: Short, var address: Address? = null) :
Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}${if (address != null) "\n$address" else ""}"
companion object {
fun getFriend() =
Friend(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLasrName(),
age = Random.nextInt(0, MAX_AGE).toShort()
)
}
override fun toJson() = """
{
"type": "Friend",
"firstName": "$firstName",
"lastName": "$lastName",
"age": "$age"
${if (address != null) """, "line2": "${address?.toJson()}\n" """ else "" }
}
""".trimIndent()
}
class Colleague private constructor(firstName: String, lastName: String, var role: Role) : Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
companion object {
fun geColleague() =
Colleague(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLasrName(),
role = RandomValues.getRole()
)
}
override fun toJson() = """
{
"type": "Colleague",
"firstName": "$firstName",
"lastName": "$lastName",
"role": "$role"
}
""".trimIndent()
}
class Contact private constructor(
firstName: String,
lastName: String,
var phoneNumber: PhoneNumber,
var email: String
) :
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
companion object {
fun geContact() =
Contact(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLasrName(),
phoneNumber = RandomValues.getPhoneNumber(),
email = RandomValues.getEmail()
)
}
override fun toJson() = """
{
"type": "Contact",
"firstName": "$firstName",
"lastName": "$lastName",
"phoneNumber": "$phoneNumber",
"email": "$email"
}
""".trimIndent()
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) : JsonExport {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
override fun toJson() = """
"phone": {
"type": "$type",
"value": "$value"
}
""".trimIndent()
}
class Address(
val line1: String,
val line2: String? = null,
cp: String,
val city: String,
val country: String = "France"
) : JsonExport {
var cp: String by Delegates.vetoable(cp) { property, oldValue, newValue ->
newValue.length == 5 && isNumber(newValue)
}
override fun toString() = "$line1\n${if (!line2.isNullOrEmpty()) "$line2\n" else ""}$cp $city\n$country"
override fun toJson() = """
"address": {
"line1": "$line1",
${if (!line2.isNullOrEmpty()) """ "line2": "$line2\n" """ else "" }
"cp": "$cp",
"city": "$city",
"country": "$country"
}
""".trimIndent()
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person: Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val randomPersonList = mutableListOf<Person>()
for (i in 0..10) {
randomPersonList.add(Person.getPerson())
}
var counter = 0
randomPersonList.forEach { println("${counter++} ${getType(it)} $it") }
println("""{ "personList" : [ """)
randomPersonList.forEach { println("${it.toJson()},") }
println(""" ] }""")
}
fold,
pour obtenir des statistiques sur notre liste d'éléments :
Friend.
val firstNameCumulatedSize = randomPersonList.fold(0) { acc, person ->
acc + person.firstName.length
}
println("$firstNameCumulatedSize / ${randomPersonList.size} = ${firstNameCumulatedSize.toFloat() / randomPersonList.size.toFloat()}")
val ageCumulated = randomPersonList.fold(0) { acc, person ->
if(person is Friend) {
acc + person.age
} else {
acc
}
}
println("$ageCumulated")
Person qui :
Friend.
operator fun Person.plus(other:Person) = Friend(RandomValues.getFirstName(), "${this.lastName}-${other.lastName}", 0 )
import java.util.*
fun demo(source: List<Int>) {
val list = ArrayList<Int>()
// 'for'-loops work for Java collections:
for (item in source) {
list.add(item)
}
print(list)
// Operator conventions work as well:
for (i in 0..source.size - 1) {
list[i] = source[i] // get and set are called
}
print(list)
}
fun main() {
demo(listOf(1,3,5,74,1,-2,5,98))
}
get et un seul
argument avec un nom
commençant par set) sont représentées comme des propriétés en Kotlin. Les accesseurs pour les valeurs de type Boolean (le nom du getter
commence par
is et le nom du setter commence par set) sont représentés aussi comme des propriétés. Par exemple :
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
}
if (!calendar.isLenient) { // call isLenient()
calendar.isLenient = true // call setLenient()
}
}
fun main() {
calendarDemo()
}
public class Customer {
private String firstName;
private String lastName;
private int age;
//standard setters and getters
}
Customer directement dans notre code en Kotlin :
fun main() {
val customer = Customer()
customer.firstName = "Frodo"
customer.lastName = "Baggins"
println("${customer.firstName} ${customer.lastName}")
}
void, alors quand elle est appelée depuis Kotlin elle retournera Unit.
get.set(pour les propriétés de type var).var firstName: String sera compilée en Java de la manière suivante :
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
is, alors la règle diffère : le nom du getter sera le même que la propriété, et le nom du setter sera obtenu en
replaçant le
is par set. Par exemple, une propriété isOpen, le getter sera isOpen() et le setter : setOpen.
Cette règle est
valable pour toutes les propriétés, peu importe leur type, pas seulement pour les propriétés de type Boolean.
var firstName = ""
var isOpen = false
Obtenez le bytecode Kotlin avec le menu : Tools, Kotlin, Show Kotlin Bytecode.Decompile, pour obtenir le code Java équivalent.app.kt dans un package org.example, incluant toutes les fonctions
d'extention, sont compilées
dans des méthodes statiques d'une classe nomée org.example.AppKt. Exemple :
// app.kt
package org.example
class Util
fun getTime(): Int { println("10h21"); return 10 }
// Java
import org.example.AppKt;
import org.example.Util;
public class Test {
Util utilVar = new Util();
int timeValue = AppKt.getTime();
}
@JvmName :
@file:JvmName("DemoUtils")
package org.example
class Util
fun getTime() { /*...*/ }
// Java
new org.example.Util();
org.example.DemoUtils.getTime();
@JvmName) est normalement une erreur.
Toutefois, nous
pouvons indiquer au compilateur de générer une classe facade qui aura le nom correspondant, et contiendra toutes les déclarations dans une même fichier. Pour cela,
nous devons
utiliser l'annotation @JvmMultifileClass dans tous les fichiers.
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getTime() { /*...*/ }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getDate() { /*...*/ }
// Java
org.example.Utils.getTime();
org.example.Utils.getDate();
@JvmField. Le champ aura alors la même
visibilité que la
propriété d'origine.
class User(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(User user) {
return user.ID;
}
}
Une propriété modifiée avec lateinit sera aussi exposée en tant que champs d'instance. La visibilité du champ sera la même que la visibilité du setter
de la propriété.
@JvmFieldlateinitconst@JvmField@JvmField, cela donnera un champ "static" avec la même visibilité que la propriété elle même :
class Key(val value: Int) {
companion object {
@JvmField
val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
}
}
// Java
Key.COMPARATOR.compare(key1, key2);
// public static final field in Key class
lateinitlateinit, cela donnera un champ "static" avec la même visibilité que la propriété elle même :
object Singleton {
lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class
constconst (dans une classe ou au top niveau (top level) ), seront traduites en champs statiques en Java :
// file example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239
// Java
int const = Obj.CONST;
int max = ExampleKt.MAX;
int version = C.VERSION;
default dans les interfaces| Visibilité en Kotlin | Visibilité en Java | Commentaire |
|---|---|---|
| private (members) | ||
| private (top level) | ||
| protected | ||
| internal | ||
| public | public | RAS |
@JvmOverloads.
class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
@JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*...*/ }
}
Pour chaque paramètre avec une valeur par défaut, une méthode de surcharge sera générée. Dans notre exemple, cela donnera :
// Constructors:
Circle(int centerX, int centerY, double radius)
Circle(int centerX, int centerY)
// Methods
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }
null, mais comme nous le savons, ce n’est pas le cas pour Java, ce qui le rend peu pratique
pour les objets qui en proviennent. Un exemple très simple permet de mettre cela en relief. Prenez le code suivant :
// Java
public class Nullable {
public String get(){
return null;
}
}
fun main() {
Nullable().get().length
}
Que se passe t'il quand on lance le code ?
fun main() {
Nullable().get()?.length
}
Shape, en Kotlin, avec des propriétés : height,
width et
area, et deux fonctions : shapeMessage et draw :
// Shape.kt
class Shape(var width: Int, var height: Int, val shape: String) {
var area: Int = 0
fun shapeMessage() {
println("Hi i am $shape, how are you doing")
}
fun draw() {
println("$shape is drawn")
}
fun calculateArea(): Int {
area = width * height
return area
}
}
public class FromKotlinClass {
public static void callShapeInstance() {
Shape shape = new Shape(5,5,"Square");
shape.shapeMessage();
shape.setHeight(10);
System.out.println(shape.getShape() + " width " + shape.getWidth());
System.out.println(shape.getShape() + " height " + shape.getHeight());
System.out.println(shape.getShape() + " area " + shape.calculateArea());
shape.draw();
}
public static void main(String[] args) {
callShapeInstance();
}
}
object :
// Kotlin
object Singleton {
fun happy() {
println("I am Happy")
}
}
Pour appeler le singleton en Java, il faudra utiliser le mot clé INSTANCE :
// Java
public static void main(String args[]) {
Singleton.INSTANCE.happy();
}
INSTANCE en utilisant l'annotation @JvmStatic :
object Singleton {
fun happy() {
println("I am Happy")
}
@JvmStatic fun excited() {
println("I am very Excited")
}
}
Ce qui donnera l'appel en Java :
public static void main(String args[]) {
Singleton.INSTANCE.happy();
Singleton.excited();
}
utils.kt :
fun logD(message: String) {
Log.d("", message)
}
fun logE(message: String) {
Log.e("", message)
}
En Java, il sera possible d'appeler simplement ces fonctions comme suit :
UtilsKt.logD("Debug");
UtilsKt.logE("Error");
fun String.firstUpper() = this.first().toUpperCase() + this.substring(1).toLowerCase()
Que l'on pourrait appeler en Kotlin :
println("tristan".firstUpper())
println("TRISTAN".firstUpper())
En Java cela deviendra :
System.out.println(ExtensionKt.firstUpper("Tristan"));
Comme vous pouvez le voir, l'objet sur lequel est appliqué la fonction (le receveur/"reveiver") est ajouté en paramètre de la fonction. De plus les paramètres
optionnels deviennent obligatoires, car Java ne gère pas cette fonctionnalité.
default dans les interfaces, ne sont disponibles que pour la JVM 1.8, et l'annotation
@JvmDefault correspondante
est expérimentale en Kotlin 1.3.
Customer :
public class Customer {
private String firstName;
private String lastName;
private int age;
//standard setters and getters
}
La reflexion fonctionne à la fois sur les classes Kotlin et Java.Customer avec les méthodes de réflexion Kotlin :
constructors de Class<T> contient le tableau des constructeurs.name du Constructor contient le non du constructeur.
fun main() {
val type = Customer::class.java
val constructors = type.constructors
println(constructors.size)
println(constructors[0].name)
}
data classsealed class
MyClass::class.java.methods
Qui permet de lister les méthodes d'une classe. Décomposons cette construction :
MyClass::class nous donne une représentation de la class MyClass.java nous donne l'équivalent de java.lang.Class.methods appelle la méthode java.lang.Class.getMethods()
data class ExampleDataClass(
val name: String, var enabled: Boolean)
fun main() {
ExampleDataClass::class.java.methods.forEach(::println)
}
| Java type | Kotlin type |
|---|---|
byte |
kotlin.Byte |
short |
kotlin.Short |
int |
kotlin.Int |
long |
kotlin.Long |
char |
kotlin.Char |
float |
kotlin.Float |
double |
kotlin.Double |
boolean |
kotlin.Boolean |
| Java type | Kotlin type |
|---|---|
java.lang.Object |
kotlin.Any! |
java.lang.Cloneable |
kotlin.Cloneable! |
java.lang.Comparable |
kotlin.Comparable! |
java.lang.Enum |
kotlin.Enum! |
java.lang.Annotation |
kotlin.Annotation! |
java.lang.CharSequence |
kotlin.CharSequence! |
java.lang.String |
kotlin.String! |
java.lang.Number |
kotlin.Number! |
java.lang.Throwable |
kotlin.Throwable! |
| Java type | Kotlin type |
|---|---|
java.lang.Byte |
kotlin.Byte? |
java.lang.Short |
kotlin.Short? |
java.lang.Integer |
kotlin.Int? |
java.lang.Long |
kotlin.Long? |
java.lang.Character |
kotlin.Char? |
java.lang.Float |
kotlin.Float? |
java.lang.Double |
kotlin.Double? |
java.lang.Boolean |
kotlin.Boolean? |
Collection<T> est parente de toute la hiérarchie des collections. Elle définie le
comportement commun d'une collection en lecture seule : la récupération de la taille de la liste, la
vérification d'appartenance d'un objet à la collection, etc.Collection hérite d'Iterable<T> qui
définie les opérations pour itérer sur les éléments. C'est le type à utiliser pour gérer les différents
types de collections. Dans les cas plus précis, préférer List ou Set.
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) {
this.filterTo(shortWords) { it.length <= maxLength }
// throwing away the articles
val articles = setOf("a", "A", "an", "An", "the", "The")
shortWords -= articles
}
fun main() {
val words = "A long time ago in a galaxy far far away".split(" ")
val shortWords = mutableListOf<String>()
words.getShortWordsTo(shortWords, 3)
println(shortWords)
}
List<T> et Array<T>
// Initializing array and list
val array = arrayOf(1, 2, 3)
val list = listOf("apple", "ball", "cow")
val mixedArray = arrayOf(true, 2.5, 1, 1.3f, 12000L, 'a') // mixed Array
val mixedList = listOf(false, 3.5, 2, 1.4f, 13000L, 'b') // mixed List
List<T>MutableList.
Array<T>
val array1 = arrayOf(1, 2, 3, 4, 5, 6)
val array2 = arrayOf(7, 8, 9, 10, 11, 12)
val newArray = array1 + array2
for(element in newArray) {
println("$element - ")
} // 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12
array.add(1) // cannot be expanded or shrank
list.add("dog") // cannot be expanded or shrank
Notons que les listes mutables peuvent changer de taille.
val array = arrayOf(1, 2, 3)
val list = listOf("apple", "ball", "cow")
Array sont modifiables (mutable), leur contenu peut changer de valeur,
contrairement aux List qui sont en lecture seule (immutable) :
val array = arrayOf(1, 2, 3)
array[2] = 4 // OK
for (element in array){
println("$element, ") // 1, 2, 4
}
val list = listOf("apple", "ball", "cow")
list[2] = "cat" //will not compile, lists are immutable
// println(list) = {apple, ball, cow}
val m = mutableListOf(1, 2, 3)
m[0] = m[1] // OK
IntArray,
DoubleArray, CharArray, etc) : qui utilisent directement les tableaux
Java primitifs (int[], double[], char[], etc.)
val optimisedIntegerArray = intArrayOf(1, 2, 3, 4, 5, 6) // optimised for Integer Array
val optimisedDoubleArray = doubleArrayOf(1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8) // Optimised for Double Array
val optimisedCharacterArray = charArrayOf('a', 'b', 'c', 'd') // Optimised for Character Array
//List does not have intListOf, charListOf as such it is not optimised for primitive arrays
Array<T> est une classe dont l'implémentation est connue : c'est une région de
mémoire séquentielle de taille fixe qui stocke les éléments. Dans la JVM elle est représenté par
Java arrayList<T> et MutableList<T> sont des interfaces qui peuvent avoir
plusieurs implémentations : ArrayList<T>, LinkedList<T>, etc.
Array<T> est invariant (Array<Int> n'est pas un Array<Number>,
idem pour MutableList<T>List<T> est covariant (List<Int> est une List<Number>).
val a: Array<Number> = Array<Int>(0) { 0 } // won't compile
val l: List<Number> = listOf(1, 2, 3) // OK
fun main() {
printValue(getValue())
}
private fun printValue(value: Lazy<Int>) = println("Nothing")
private fun getValue() = lazy {
println("Returning 5")
return@lazy 5
}
Que remarquez vous ?println en :
"Nothing ${value.value}"
Quel changement observez vous, et pourquoi ?
build.gradle
dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
}
launch {} :
launch {
// ...
}
Les coroutines, utilisent un pool de threads pour fonctionner, mais un thread peut faire tourner plusieurs coroutines, donc il n'est pas nécessaire d'avoir
beaucoup de threads lancés.
println("Start")
// Start a coroutine
GlobalScope.launch {
delay(1000)
println("Hello")
}
Thread.sleep(2000) // wait for 2 seconds
println("Stop")
La fonction delay() fonctionne comme la fonctions Thread.sleep(), mais elle ne bloque pas le thread, elle suspend simplement la
coroutine. Le thread retourne dans le pool de thread. Et la coroutine, reprendra son fonctionnement sur un thread disponible.Thread.sleep(2000) ?Thread.sleep(2000) par delay(2000) ?
delay(...), nous allons l'appeler dans une fonction runBlocking {} :
runBlocking {
delay(2000)
}
Ce qui nous donnera au final :
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
println("Start")
// Start a coroutine
GlobalScope.launch {
delay(1000)
println("Hello")
}
// Step 1
//Thread.sleep(2000) // wait for 2 seconds
// Step 2
// delay(2000)
// Step 3
runBlocking {
delay(2000)
}
println("Stop")
}
threads :
val c = AtomicLong()
for (i in 1..1_000_000L)
thread(start = true) {
c.addAndGet(i)
}
println(c.get())
Que remarquez vous, concernant la charge de la machine ?
val c = AtomicLong()
for (i in 1..1_000_000L)
GlobalScope.launch {
c.addAndGet(i)
}
println(c.get())
Que constate-t-ons ?main() affiche le résultat.
Nous allons corriger cela.
async {}. C'est comme launch {} mais cela retourne une instance de
Deferred<T> qui comporte une fonction await() qui retourne le résultat de la coroutine. Deferred<T> est une sorte de
future très basique.AtomicLong n'est plus nécessaire
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
n
}
}
val sum = deferred.sumBy { it.await() }
println("Sum: $sum")
Quel est le problème ici ?
await() dans un contexte de coroutine, nous utilisons de nouveau runBlocking {}
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
n
}
}
runBlocking {
val sum = deferred.sumBy { it.await() }
println("Sum: $sum")
}
Nous devrions obtenir le résultat suivant :
Sum: 1784293664
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
delay(1000)
n
}
}
runBlocking {
val sum = deferred.sumBy { it.await() }
println("Sum: $sum")
}
Allons nous devoir attendre 1 million de secondes (11,5 jours) pour obtenir notre résultat ?
fun workload(n: Int): Int {
delay(1000)
return n
}
Le compilateur, n'est pas content, car delay ne peut être utilisé que dans une cadre de coroutine. Marquons la fonctions avec le mot clé
suspend :
suspend fun workload(n: Int): Int {
delay(1000)
return n
}
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
fun main() {
suspend fun workload(n: Int): Int {
//delay(3000)
return n
}
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
workload(n)
}
}
runBlocking {
val sum = deferred.sumBy { it.await() }
println("Sum: $sum")
}
}
Channel.
sequence qui utilise
yield :
fun main(args: Array<String>) {
val fibonacciSeq = sequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
// Print the first five Fibonacci numbers
println(fibonacciSeq.take(8).toList())
}
Ce qui devrait nous afficher :
[1, 1, 2, 3, 5, 8, 13, 21]
fun main(args: Array<String>) {
val lazySeq = sequence {
print("START ")
for (i in 1..5) {
yield(i)
print("STEP ")
}
print("END")
}
// Print the first three elements of the sequence
lazySeq.take(3).forEach { print("$it ") }
}
Combien de fois est affiché le message END ?yieldAll, par exemple :
fun main(args: Array<String>) {
val lazySeq = sequence {
yield(0)
yieldAll(1..10)
}
lazySeq.forEach { print("$it ") }
}
suspend fun SequenceScope<Int>.yieldIfOdd(x: Int) {
if (x % 2 != 0) yield(x)
}
val lazySeq = sequence<Int> {
for (i in 1..10) yieldIfOdd(i)
}
fun main(args: Array<String>) {
lazySeq.forEach { print("$it ") }
}
filter. Par exemple prenons les 8 premières valeurs de la suite de fibonacci qui sont
supérieures à 20 :
fun main(args: Array<String>) {
val fibonacciSeq = sequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
// Print the first five Fibonacci numbers
println(fibonacciSeq.filter { it > 20 }.take(8).toList())
}
val personSequence = sequence {
while (true)
yield(Person.getPerson())
}
fun main() {
println(personSequence.filter { it.firstName.length == 4 }.take(10).toList())
}
override fun onDestroy() {
super.onDestroy()
async.cancelAll()
}
// list_folders.kts
import java.io.File
val folders = File(args[0]).listFiles { file -> file.isDirectory() }
folders?.forEach { folder -> println(folder) }
Pour lancer le script, il faut ajouter l'option -script et passer le chemin du script à lancer :
$ kotlinc -script list_folders.kts "path_to_folder_to_inspect"
lock()
lock(l) { foo() }
Au lieu de créer un objet de type fonction en paramètre, et de générer un appel à celle-ci, le compilateur pourrait produire le code suivant :
l.lock()
try {
foo()
}
finally {
l.unlock()
}
lock() avec le modifieur inline:
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
Ce modifieur va impacter la fonction, en elle même, mais aussi la lambda passée en paramètre : tout sera mis en ligne à l'endroit de l'appel.C:\Users<your_user>\AppData\Roaming\Google\AndroidStudio4.1\templates.setOnClickListener.with.
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="Salut"
android:text="btn2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintStart_toEndOf="@+id/button1" />
buttonCallback :
val buttonCallback: (View) -> Unit = { localView ->
if (BuildConfig.DEBUG) {
Log.d(TAG, "onCreate ${localView.tag}")
}
}
with :
with(binding) {
button1.setOnClickListener(buttonCallback)
button2.setOnClickListener(buttonCallback)
button3.setOnClickListener(buttonCallback)
}
addTextChangedListener :
binding.editTextHeight.addTextChangedListener { v ->
calcIMC()
}
calcIMC() :
fun calcIMC() {
val weight = try {
binding.editTextWeight.text.toString().toInt()
} catch (e: NumberFormatException) {
binding.textViewResult.text = "ERROR"
0
}
val height: Float = try {
binding.editTextHeight.text.toString().toFloat()
} catch (e: NumberFormatException) {
binding.textViewResult.text = "ERROR"
0f
}
if (weight != 0 && height != 0f) {
binding.textViewResult.text = (weight / (height * height)).toString()
}
}
android_webview.
android_SharedPreferences_loadandroid_SharedPreferences_save
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'android.arch.lifecycle:viewmodel:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'
annotationProcessor 'android.arch.lifecycle:compiler:1.1.1'




import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface NewsApi {
@GET("top-headlines")
fun getNewsList(
@Query("country") newsSource: String?,
@Query("apiKey") apiKey: String?
): Call<NewsResponse?>?
}
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class RetrofitService {
private val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <S> createService(serviceClass: Class<S>?): S {
return retrofit.create(serviceClass)
}
}
open class SingletonHolder<out T: Any, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val checkInstance = instance
if (checkInstance != null) {
return checkInstance
}
return synchronized(this) {
val checkInstanceAgain = instance
if (checkInstanceAgain != null) {
checkInstanceAgain
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
open class NewsRepository private constructor(context: Context) {
private var newsApi: NewsApi? = null
init {
newsApi = RetrofitService.createService(NewsApi::class.java)
}
fun getNews(country: String?, key: String?): MutableLiveData<NewsResponse?>? {
val newsData = MutableLiveData<NewsResponse?>()
newsApi?.getNewsList(country, key)?.enqueue(object : Callback<NewsResponse?> {
override fun onResponse(call: Call<NewsResponse?>?, response: Response<NewsResponse?>) {
if (response.isSuccessful()) {
newsData.setValue(response.body())
} else {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onResponse $response")
}
}
}
override fun onFailure(call: Call<NewsResponse?>?, t: Throwable?) {
newsData.setValue(null)
if (BuildConfig.DEBUG) {
Log.d(TAG, "onFailure $t")
}
}
})
return newsData
}
companion object : SingletonHolder<NewsRepository, Context>(::NewsRepository) {
private const val TAG = "NewsRepository"
}
}
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class NewsViewModel: ViewModel() {
private var mutableLiveData: MutableLiveData<NewsResponse?>? = null
private var newsRepository: NewsRepository? = null
fun init(context: Context) {
if (mutableLiveData != null) {
return
}
newsRepository = NewsRepository.getInstance(context)
mutableLiveData = newsRepository?.getNews("fr", "96c6792fdad6434fbc3a893daba40e0f")
}
fun getNewsRepository(): LiveData<NewsResponse?>? {
return mutableLiveData
}
}
RecyclerView dans notre layout principal :
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvNews"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<TextView
android:id="@+id/item_news_textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:transitionName="title"
app:layout_constraintEnd_toStartOf="@id/item_news_imageView_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title title title title title title title title title title title title title title title title title title title title title title title title title title title title title" />
<TextView
android:id="@+id/item_news_textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:transitionName="description"
app:layout_constraintEnd_toStartOf="@id/item_news_imageView_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_news_textView_title"
tools:text="description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description" />
<ImageView
android:id="@+id/item_news_imageView_image"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher"
android:transitionName="image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="item_news_textView_description,item_news_imageView_image" />
<TextView
android:id="@+id/item_news_textView_publishedAt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier"
tools:text="14 minutes ago" />
<TextView
android:id="@+id/item_news_textView_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier"
tools:text="by author" />
</androidx.constraintlayout.widget.ConstraintLayout>
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide
class NewsAdapter(val context: Context, val myDataset: List<Article>) :
RecyclerView.Adapter<NewsAdapter.MyViewHolder>() {
private var mDataset: List<Article>? = null
private var mInflater: LayoutInflater? = null
init {
mInflater = LayoutInflater.from(context)
mDataset = myDataset
}
class MyViewHolder(v: View) : ViewHolder(v) {
// each data item is just a string in this case
var tvTitle: TextView
var tvDescription: TextView
var tvPublishedAt: TextView
var tvAuthor: TextView
var imageView: ImageView
init {
tvTitle = v.findViewById(R.id.item_news_textView_title)
tvDescription = v.findViewById(R.id.item_news_textView_description)
tvPublishedAt = v.findViewById(R.id.item_news_textView_publishedAt)
tvAuthor = v.findViewById(R.id.item_news_textView_author)
imageView = v.findViewById(R.id.item_news_imageView_image)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// create a new view
val v = mInflater!!.inflate(R.layout.item_news, parent, false)
return MyViewHolder(v)
}
override fun getItemCount(): Int = if(mDataset == null) 0 else mDataset!!.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
if(mDataset != null){
holder.tvTitle.setText(mDataset!![position].title)
holder.tvDescription.setText(mDataset!![position].description)
holder.tvPublishedAt.setText(mDataset!![position].publishedAt)
holder.tvAuthor.setText(mDataset!![position].author)
Glide.with(holder.imageView).load(mDataset!![position].urlToImage)
.into(holder.imageView)
}
}
}
var articleArrayList: ArrayList<Article> = ArrayList()
var newsAdapter: NewsAdapter? = null
var rvHeadline: RecyclerView? = null
var newsViewModel: NewsViewModel? = null
private fun setupRecyclerView() {
if (newsAdapter == null) {
newsAdapter = NewsAdapter(this@MainActivity, articleArrayList);
rvHeadline?.setLayoutManager(LinearLayoutManager(this));
rvHeadline?.setAdapter(newsAdapter)
//rvHeadline?.setItemAnimator(DefaultItemAnimator())
//rvHeadline?.setNestedScrollingEnabled(true)
} else {
newsAdapter?.notifyDataSetChanged();
}
}
rvHeadline = findViewById(R.id.rvNews);
newsViewModel = ViewModelProviders.of(this).get(NewsViewModel::class.java)
newsViewModel?.init(this@MainActivity)
newsViewModel?.getNewsRepository()?.observe(this, Observer<NewsResponse?> {
var newsArticles = it?.articles
if (newsArticles != null) {
articleArrayList.addAll(newsArticles)
newsAdapter?.notifyDataSetChanged()
}
})
setupRecyclerView()
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles
Il faut activer la compatibilité JVM 1.8 en ajoutant dans le build.gradle, le code suivant :
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
java.net.SocketException: socket failed: EPERM (Operation not permitted)
Il suffit que nous désinstallions l'application, pour la réinstaller.
private val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
Par :
var client = OkHttpClient.Builder()
.connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100, TimeUnit.SECONDS).build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/").client(client)
.addConverterFactory(GsonConverterFactory.create(Gson()))
.build()
Cela nous permet d'attendre un peu plus de temps avant d'avoir une erreur. Il faut dans ce cas là, afficher une indication à l'utilisateur pour qu'il comprenne
qu'il doit attendre.
Unsupported major.minor version 52.0 nous indique, que nous avons compilé le code avec une version de JDK 1.8 et que nous
essayons de le lancer avec une version antérieure .