Vous êtes sur la page 1sur 18

Développement Mobile

Parti 2: Langage Kotlin pour Android

Module GIM21S04
Technologie XML et Technologie Mobile

4ème Année Génie informatique

REDOUANE EZZAHIR

2019/2020


Table de matière

Kotlin en bref 3

Pourquoi Kotlin 3

Android et Kotlin 3

Créer un projet Kotlin 4

Convertir un fichier Java en Kotlin 4

Règles de base 4

Types 4

Références 5

Nullable types 5

Les intervalles (Range) 6

Le transtypage (cast) 6

Contrôle du flot d’exécution 6

Les fonctions 8

Classes et objets 9

Visibilités 9

Constructeur (Ctor) 10

Propriétés & getter / setter 11

Héritage 11

Interfaces 12

Classes de données 13

object & Companion object 14

Retour sur les fonctions 15

Surcharge d’opérateur 17

Documentation 17

Kotlin en bref

Tout d’abord, Kotlin est un langage de programmation orienté objet


et fonctionnel. Il porte le nom d’une île proche de Saint
Pétersbourg, la ville où est basée l’équipe de JetBrains impliquée
dans le développement de ce langage. Cette société a également
développer l’IDE (Integrated Development Environment) IntelliJ, sur
lequel est basé Android Studio. JetBrains est réputé pour réaliser
des produits de qualité.
En bref, voici quelques unes des vertues de Kotlin :
Langage peu verbeux : ;
Langage clair et concis : getter/setter, accès direct aux attributs,
etc.
Affranchissement du NullPointerException
Interpollation de chaines de charactère : Log.i(TAG, "my width
is $mWidth")
Déclaration de variables simplifiée : soit val soit var
Conversion de type automatique : val mWidth = 0
Concrètement, pour avoir une démonstration du langage Kotlin, il
suffit de se rendre dans la partie TRY du site officiel.

Pourquoi Kotlin
• Google I/O 2017 : langage de premier ordre pour Android

• Intégré par défaut à partir d’Android Studio 3.0

• Langage à typage statique

• S’exécute sur une JVM (et par extension sur ART)

• Beaucoup d’inférence de type, donc moins verbeux que Java

• Multiparadigme (procedural, fonctionnel, orienté objets)

• Pousse aux best practices

Android et Kotlin
AS 3 est sorti, en version de prévisualisation, tout juste pour la G I/
O 2017 [9]. Il supporte le langage Kotlin. L’avantage est qu’il peut
être installé en parallèle d’une version existante d’AS. Les versions
stable et Canary d’AS partagent alors les dossiers d’installation
(SDK Android et préférences).
D’ailleurs, voici les principaux atouts du support de Kotlin dans AS
3:
Cohabitation du code Java et Kotlin
Conversion automatique du code Java vers Kotlin : Ctrl+C de code
Java Ctrl+V dans un fichier .kt ou Convertir un fichier Java en
Kotlin

Créer un projet Kotlin


1. Dans AS 3, créer un nouveau projet : Start a new Android Studio
project.
2. Cocher la case Include Kotlin support, si ce n’est pas déjà le cas.
3. Cliquer sur Next puis configurer le projet à votre guise.

Convertir un fichier Java en Kotlin


Aller dans le menu Code > Convert Java File to Kotlin File

Règles de base
• Code contenu dans des fichiers .kt

• Pas besoin de ; en fin de ligne

fun main(args: Array<String>) {


println("Hello Kotlin")
}
• Déclarations à la UML

• Conventions de nommage très similaires à Java

• cf. https://kotlinlang.org/docs/reference/coding-
conventions.html

Types
• Pas de types primitifs

• Action sans résultat : singleton Unit (≈void de Java)

• Rien, fonction qui ne termine pas normalement : Nothing

• Nombres : Double, Float, Long, Int, Short, Byte

• Pas de conversions implicite : toInt(), toFloat(), …

• Littéraux : comme en Java (0b101010, 0xB8, 12, 3.14f)

• Possibilité de séparateur 1_000_000

• Booléens : Boolean (true et false)

• Tableaux : classe Array (get, set, [], size) + fonctions

• Texte : Char, String

• Les chaînes de caractères sont immuables

• "Hello\tTab" (escaped string) ou """ avec saut de lignes


""" (raw string)

• Utilisation possible de string templates :

val name = "Laurent"


val greetings = "Salut $name !"
val scream = "EH HO… ${name.toUpperCase()} ! »

Références
• val : reférence une valeur constante (immuable)

val answer : Int = 42
• val age = 33; // Type inféré
• val what: String // Type nécessaire si pas
d'initialisation
• what = "Whaaat ?" // Initialisation différée

• age += 2 // Erreur, pas modifiable

• var : reférence une variable (peu changer de valeur)



var x : Int = 2;
• var y = 'y';
• ++x; // OK, muable

• const : constante connue à la compilation

◦ soit top level, soit membre d’un object

◦ initialisée avec une String ou un valeur primitive

◦ pas de getter personnalisé

• const val PI_APPROX: float = 3.14f;

• Comparaison de références

◦ == comparaison structurelle (equals())

◦ === comparaison d’instances (emplacement mémoire)

Nullable types
• Nullable : Double?, String?, …

• Pour éviter les NullPointerException (NPE)

• ?. pour un accès sûr

• Elvis operator : ?:

val l: Int = if (b != null) b.length else -1


// équivalent
val l = b?.length ?: -1
• Forçage : !!. lève une NPE si l’objet est null (à éviter !)

Les intervalles (Range)


• Opérateur .. (issu de la fonction rangeTo())

• Fonctions extensions : until, downTo, step

1..10 // de 1 inclus jusqu'à 10 inclus


1 until 10 // de 1 inclus jusqu'à 10 exclus
4..1 // vide
4 downto 1 // 4, 3, 2, 1
10 downto 1 step 2 // 10, 8, 6, 4, 2
• step doit être strictement positif

Le transtypage (cast)
• Savoir si un objet est d’un certain type : is

if (obj is String) {
print(obj.trim())
}

if (obj !is String) { // equivalent à !(obj is String)


print("Not a String")
}
else {
print(obj.length)
}
• Cast explicite souvent inutile : le compilateur s’en occupe
(smart cast)

// x est automatiquement casté en String à droite du


||
if (x !is String || x.length == 0) return

// x est automatiquement casté en String à droite du


&& et dans le if
if (x is String && x.length > 0) {
print(x.length)
}
• On peut néamoins caster avec as (unsafe cast)

• Lève une exception si pas possible

val x: String = y as String


• Utiliser as? pour safe cast

• Le résultat est un nullable type

val x: String? = y as? String

Contrôle du flot d’exécution


• if, while, break, continue : comme en Java

• possibilité de labels : unLabel@ … break@unLabel

loop@ for (i in 1..100) {


for (j in 1..100) {
if (…) break@loop
}
}
• when : équivalent du switch (mais plus sympa)

when (x) {
0, 1 -> print("peu")
in 2..10 -> print("moyen")
is String -> print("${x.trim()} est une String")
else -> {
println("rien de tout ça")
print("on peu mettre un block")
}
}
println(
when {
x.isOdd() -> "impair"
x.isEven() -> "pair"
else -> "bizarre"
})
• when et if peuvent êrte utilisées comme expressions : elles
renvoient comme valeur le resultat de leur dernière instruction
exécutée

• for : parcours sur tout ce qui fournit un itérateur (foreach de


C#), i.e. :

◦ fournit une méthode iterator()

◦ cet itérateur a une méthode next() et une méthode


hasNext() qui retourne un Boolean (doivent être
marquées operator)

• Itération classique (intervalle, String, Array, …)

for (i in 0..9) {
println(i)
}
for (c in "Hello") {
println(c)
}
• Itération avec indices

for (i in array.indices) {
println(array[i])
}
for ((index, value) in array.withIndex()) {
println("The element at $index is $value")
}
• Pour les execptions : comme en Java (sauf qu’il n’existe pas
de notion de checked exception)

• try/catch/finally est un expression

Les fonctions
• Possibilité de fonction top level (pas forcément membre d’un
objet)

fun sum(a: Int, b: Int): Int {


return a + b
}
• Avec inférence du type de retour pour les expression body
(pratique pour getter/setter)

fun sum(a: Int, b: Int) = a + b


• Paramètres avec valeur par défaut (pratique pour les Ctor)

fun say(text: String = "Something") = println(text)


say() // Affiche Something
say("Hi guys") // Affiche Hi guys
• Paramètres nommés

fun Box.setMargins(left: Int, top: Int, right: Int,


bottom: Int) { … }

myBox.setMargins(10,10,20,20) // haut ? bas ? gauche


? droite ?
myBox.setMargins(left = 10, right = 10, top = 20,
bottom = 20)
• Paramètres variables (Type... de Java)

• Utilisation du spread operator (*) pour paramètres nommés

fun foo(vararg strings: String) { … }


foo("s1", "s2")
foo(strings = *arrayOf("a", "b", "c"))
• On peut déclarer une fonction dans une autre fonction

• Utile pour récursivité terminale

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])
}
Classes et objets
• Squelette de classe standard en Java (entre POJO et Bean)

class Person {
private String name;
private int age;

public Person(String name, int age) {


this.name = name;
this.age = age;
}

public String getName() { return name; }


public void setName(String name) { this.name = name; }
public String getAge() { return age; }
public void setAge(int age) { this.age = age; }

public int hashCode() { … }


public boolean equals() { … }

@Override
public toString() {
return "Person(name=" + name + ", age=" + age + ")";
}
}
• L’équivalent en Kotlin

data class Person(var name: String, var age: Int)


• \o/ sympa… mais contraintes :

◦ Ne peut pas être abstract, open, sealed, inner

◦ Ctor principal doit avoir au moins un paramètre

◦ Les paramètres doivent tous être marqués val ou var

◦ equals(), hashCode(), toString() non générée si


présentes explicitement

• Instanciation : pas besoin de new

val moi = Person("Laurent Provot", 27)

Visibilités
Modifi
Package Classe
eur
à tout ceux qui voient la
public partout
classe
au sein du fichier
privat au sein de la classe
contenant la
e uniquement
déclaration
intern dans le même module à
dans le même module
al qui voit la classe
protec comme private + dans

ted les classes dérivées
• Module : ensemble de fichiers Kotlin compilés ensemble (pour
Android = Gradle source set)

Constructeur (Ctor)
• Constructeur principal

• On ne peut spécifier de code tout de suite

class Person constructor(firstName: String) {…}


class Person(firstName: String) {…}
• Bloc d’initialisation

• On peut utiliser les paramètres du ctor principal dans les


blocs d’initialisation et les initialisations d’attributs

• Initialisations effectuées dans l’ordre declaré

class Person(name: String) {


val nameUpper = name.toUpperCase();

init {
println("My name is: $name");
}
}
• Pour initialisation de propriétés

class Person(val firstName: String, var age: Int)


{ … }
• Si annotations ou modificateur (visibilité, …)

class Person private constructor(val firstName:


String) { … }
class Person @Inject constructor(val firstName:
String) { … }
• Ctor secondaires

class Person(val name: String) {


constructor(name: Streing, parent: Person) :
this(name) {
parent.children.add(this)
}
}
• Délégation au contructeur principal obligatoire (implicite sinon)

• Tous les blocs d’initialisation sont appelés

• Ctor par défaut si non précisé ou si tous les paramètres ont


des valeurs par défaut

Propriétés & getter / setter


• Attribut déclaré avec val = propriété en lecture seule

• Attribut déclaré avec var = propriété en lecture / écriture

class Person {
var name = "John Doe";
}
john = Person();
• Accès : john.name (utilisation du getter)

• Modification : john.name = "johnny" (utilisation du setter)

• Syntaxe complète

var <propertyName>[: PropertyType] [= <initializer>]


[<getter>]
[<setter>]
• Propriété personnalisée : accès à l’attribut avec field

var speed: Int


get() = field * 100;
set(value) {
if (value >= 0) field = value;
}
• Attribut (backing field) fourni seulement si nécessaire

val isEmpty: Boolean


get() = this.size == 0
• Changement de visibilité ou ajout d’annotation en conservant
l’implémentation par défaut

var devOnly: String


@NotNull get
private set

Héritage

• Hiérarchie basée sur la classe Any (≠ java.lang.Object)

• Autorisation excplicite de l’héritage avec open (l’opposé de


final en Java)

open class Base(arg: String)


class Derived(num: Int) : Base(arg.toString())
• Ctor secondaires : appel obligatoire de super ou délégation à
un autre ctor

class Derived : Base {


constructor(arg: String, num: Int) : super(arg) {…}
constructor(arg: String) : this(arg, 42)
}
• Redéfinition de méthode : explicite avec override

open class Base {


open fun fo() {…}
fun fc() {…}
}
class Derived() : Base() {
override fun fo() {…}
}
• Un membre déclaré override est automatiquement open; si
non désiré : le marquer final

• Redéfinition de propriété : pareil que pour les méthodes

• On peut redéfinir un val en var

• override peut être utilisé dans le constructeur prinicpal

open class Base {


open val x: Int = 0
}
class Derived : Base() {
override var x: Int = 42
}
class AnotherDerived(override val x: Int) : Base()
• Comme en Java, le code de la classe dérivée peut appeler
des méthodes/propriétés de sa classe de base grâce au mot
clé super

• Classe et méthode abstraite : déclarées avec le mot clé


abstract

• abstract implique open

Interfaces
• Comme en Java

• Une interface peut contenir des propriétés mais elles seront


soit abstraites, soit val et sans backing field

interface Named {
val name: String
}

interface FullNamed : Named {


val firstName: String
val lastName: String

override val name: String get() = "$firstName


$lastName"
}

data class Person(


override val firstName: String,
override val lastName: String
) : FullNamed
• Désambiguïsation grâce à super<…> si implémentation dans
l’interface

interface A {
fun foo() { print("A") }
}

interface B {
fun foo() { print("B") }
}

class C : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}

Classes de données

data class User(val name: String, val age: Int)


• En plus des propriétés, toString() et equals() / hashCode()

◦ copy()

◦ component1(), …, componentN() functions

• Méthode copy :

fun copy(name: String = this.name, age: Int =
this.age) = User(name, age)

• val john = User(name = "John", age = 42)
• val youngJohn = john.copy(age = 22)

• Méthodes component : déstructuration de la classe

• Pour chaque propriété du ctor principal, dans l’ordre de


déclaration

• Underscore possible si paramètre non utilisé



println(john.component1()) // affiche "John"
• val (name, age) = john
• println("$name, $age years old") // affiche "John, 42
years old"

• Sympa pour les retours de fonctions, Map, itérations avec


index

object & Companion object


• Généralisation des classes anonymes de Java

• Expression objet = création d’une instance locale avec


certains comportements

button.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { … }
override fun mouseEntered(e: MouseEvent) { … }
})
• Création d’objet sans supertype non trivial

fun foo() {
val local = object {
var x: Int = 0
var y: Int = 0
}
print(local.x + local.y)
print(local::class.simpleName) // affiche null
}
• object comme valeur de retour

class C {
// Private function : the return type is the anonymous
object type
private fun foo() = object { val x: String = "x"}
// Public function : so the return type is Any
fun publicFoo() = object { val x: String = "x" }

fun bar() {
val x1 = foo().x // OK
val x2 = publicFoo().x // ERROR: Unresolved reference
'x'
}
}
• Déclaration d’objet = singletons

• Initialisation thread-safe

object DatabaseManager {
fun connect(conn: Connector) { … }
fun fetchData() : Data { … }
}
• Ne peut pas être du côté droit d’une affectation

• Ne peut pas être déclaré local

• Déclaration dans une classe possible avec le mot clé


companion

class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}

val instance = MyClass.create()


• Utilisation à la static de Java

• Mais attention Factory implique une instance d’objet

• Utilisation de @JvmStatic si on veut que cela génère des


membres static dans le bytecode Java

• Les expressions objet sont exécutées immédiatement, les


déclarations objet sont initialisées de manière paresseuse et
les companion object au moment du chargement de leur
classe d’attachement

Retour sur les fonctions


• Les fonctions sont des variables comme les autres

• Elles ont un type :

◦ (A, B) -> C : une fonction qui prend 2 paramètres le


premier de type A, le second de type B et retoune un C

◦ () -> A : pas de paramètre en entrée

◦ (A) -> Unit : pas de valeur de retour

◦ A.(B) -> C : fonction qui peut être appelée sur un objet


de type A, prend un paramètre de type B et retourne une
valeur de type C

• Création concise grâce aux lambda

val square = { num: Int -> num * num }; // (Int) -> Int
val more : (String, Int) -> String = { str, num -> str +
num }
val noReturn : Int -> Unit = { num -> println(num) }
• Pour les lambdas qui ne prennent qu’un paramètre : it

val noReturn : Int -> Unit = { println(it) }


val concatInt : String.(Int) -> String = { this + it }
• Utilisation

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
}

val items = listOf(1, 2, 3, 4, 5)


items.(0, {acc, i -> acc + i}) // 15
items.("Elts :", {res, i -> res + " " + i}) // "Elts : 1
2 3 4 5"
items.(1, Int::times) // 120
• Une fonction de type A.(B) -> C peut être utilisée en lieu et
place de (A, B) -> C et vice versa

• Lorsque le dernier paramètre d’une fonction est une fonction,


si on passe un lambda on peut la sortir des parenthèses

items.fold(0) {sum, i -> sum + i}


• Fonctions d’extension

• Possible d’ajouter des fonctions à une classe après coup

• Toutes les instances peuvent en profiter

fun String.reverse() =
StringBuilder(this).reverse.toString()
"That's cool !".reverse() // "! looc s'tahT"
• Fonction infixes : infix

◦ Fonction membre ou fonction d’extension

◦ Un seul paramètre

◦ Pas de vararg ou de paramètre par défaut

infix fun String.open(rights: Acces): File { … }

"/home/provot/lecture" open Access.WRITE


// équivalent à
« /home/provot/lecture".open(Access.WRITE)

Surcharge d’opérateur
• Il est possible de surcharger les opérateurs

• On redéfinit des méthodes spécifiques de la classe

• Elles sont marquées operator

• +, -, *, /, %, ..

◦ a + b équivalent à a.plus(b)

◦ a..b équivalent à a.rangeTo(b)

• in, !in

◦ a.contains(b)

• Accès indexé []

◦ a[i] équivalent à a.get(i)

◦ a[i] = b équivalent à a.set(i, b)

• Appel de méthode

◦ a(i, j) équivalent à a.invoke(i, j)

• a == b

◦ a?.equals(b) ?: (b === null)

• a > b, a < b, a >= b, a <= b

◦ obtenus à partir de a.compareTo(b)

Documentation
• KDoc + génération avec Dokka

• Comme la javadoc

• Tags : @param, @return, @constructor, @receiver, @property,


@throws, @exception, @sample, @see, @author, @since et
@suppress

/**
* A group of *members*.
* This class has no useful logic; it's just
* a documentation example.
* @param T the type of a member in this group.
* @property name the name of this group.
* @constructor Creates an empty group.
*/
class Group<T>(val name: String) {
/**
* Adds a [member] to this group.
* @return the new size of the group.
*/
fun add(member: T): Int { ... }
}

Références :

• https://kotlinlang.org/docs/reference/coding-conventions.html

• https://kotlinlang.org/docs/reference/

• https://try.kotlinlang.org/

• http://lprovot.fr/Android/Kotlin.html

Vous aimerez peut-être aussi