Kotlin : la programmation orientée objet
Introduction
Kotlin est un langage orienté objet et propose donc :
-
la création de classes avec son lot de modificateurs,
-
la création d’interfaces,
-
la mise en place d’héritages,
-
le principe de polymorphisme,
-
des collections.
Tous ces concepts seront traités dans ce chapitre ainsi qu’un point sur les expressions lambda.
Les classes
1. Présentation
La classe est l’élément central de la programmation orientée objet. Elle permet de définir et de créer des objets.
Une classe peut contenir :
-
des constructeurs,
-
des propriétés,
-
des fonctions,
-
des classes imbriquées,
-
des classes internes,
-
des déclarations d’objets.
Nous allons examiner chacun de ces éléments.
2. Syntaxe
Voici la syntaxe permettant de créer une classe :
class <NomDeLaClasse> [constructeur][([paramètres])]
3. Déclaration d’une classe
Le mot-clé class permet de déclarer une classe.
Exemple de déclaration d’une classe vide
class Animal
Cet exemple montre la déclaration d’une classe vide. Les accolades permettant de définir le bloc d’instructions ne sont pas obligatoires.
Exemple de déclaration d’une classe avec un bloc d’instructions vide
class Animal
{
}
Le bloc d’instructions d’une classe se situe entre les accolades.
Les deux exemples ci-dessus produisent exactement la même chose.
4. Création et utilisation d’instances d’une classe
L’appel du constructeur permet de créer un objet/instance d’une classe.
Exemples de création d’objets de type Personne
val personne1 = Personne()
val personne2:Personne = Personne()
var personne3:Personne = Personne()
Dans cet exemple, trois objets de type Personne ont été créés.
5. Les modificateurs de classe
Un modificateur de classe permet d’attribuer des caractéristiques spécifiques à une classe. Par défaut, une classe a les caractéristiques des modificateurs public et final. Une classe peut avoir les modificateurs suivants : final, open, abstract, public, internal, protected et private. Certains modificateurs sont incompatibles de par leur nature.
a. Modificateur public
Le modificateur public est un modificateur de visibilité. La spécificité du modificateur public est que la classe l’utilisant est disponible et utilisable depuis n’importe quel endroit. Par défaut, les classes sont public.
b. Modificateur internal
Le modificateur internal est un modificateur de visibilité. La spécificité du modificateur internal...
Les classes objet
1. Présentation
Tout projet informatique contient des classes permettant de stocker des données, ce sont des classes que l’on appelle bo, model, pojo, entité, etc. Elles sont là pour stocker et utiliser les informations contenues dans votre source de données.
La structure de ces classes est souvent la même. Kotlin appelle ces classes des data class et propose un mécanisme pour faciliter la création de celles-ci, grâce au mot-clé data.
2. Syntaxe
Voici la syntaxe permettant de créer une classe objet :
data class <NomDeLaClasse>(propriété,[propriétés])
3. Usage
Grâce au mot-clé data devant la déclaration d’une classe, le compilateur va créer automatiquement les propriétés déclarées dans le constructeur et créer et définir les fonctions equals(), hashCode() et copy().
La déclaration d’une data class doit respecter les points suivants :
-
Le constructeur doit contenir au moins un paramètre.
-
Tous les paramètres du constructeur doivent être marqués du type val ou var.
-
Une data class ne peut pas avoir de modificateur abstract, open, sealed ou inner.
Il est possible de redéfinir les fonctions equals(), hashCode() et toString().
Exemple de déclaration d’une data class
data class Personne(var nom:String,val prenom:String...
L’héritage
1. Présentation
Une classe peut hériter d’une et seulement d’une seule autre classe. Lors de l’héritage, on parlera de classes filles qui héritent d’une classe mère. Lorsqu’une classe hérite d’une autre classe, la classe fille peut hériter des propriétés et des fonctions de la classe mère. Par défaut, toutes les classes héritent de la classe Any. La classe Any contient trois fonctions : equals(), hashCode(), toString(), cela signifie donc que tous les objets de toutes les classes disposent des fonctions equals(), hashCode() et toString().
Contenu de la classe Any
package kotlin
public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}
La classe Any contient deux modificateurs : le modificateur public pour être disponible depuis n’importe quel autre fichier ; le modificateur open pour indiquer qu’il est possible d’en hériter. Les trois fonctions dans la classe Any ont le modificateur open, cela signifie que toutes les classes peuvent redéfinir les fonctions equals(), hashCode() et toString().
2. Les bases de l’héritage
a. Présentation
L’héritage permet de mettre en place une hiérarchie entre les classes : l’intérêt principal est de factoriser au mieux les structures des classes. Il y a donc des classes mères qui contiennent plutôt les structures génériques et des classes filles qui contiennent du code plus spécialisé.
b. Syntaxe
Voici la syntaxe pour mettre en place un héritage :
[modificateurs] class <ClasseFille>[([parametres])]:<ClasseMere>
([parametres])
c. Héritage simple
Pour définir un héritage explicitement, il est nécessaire d’écrire deux points accompagnés du nom de la classe mère derrière le nom de la classe fille.
Exemple d’héritage
open class Mere
class Fille:Mere()
Dans cet exemple, une première classe nommée Mere est déclarée. La classe Mere contient le modificateur open afin que l’on puisse en hériter. Une seconde...
Les classes abstraites
1. Présentation
Une classe abstraite permet de factoriser des comportements et sert de base à des classes qui en héritent. Une classe abstraite n’est pas instanciable.
2. Syntaxe
Voici la syntaxe permettant de définir une classe abstraite :
abstract class <NomDeLaClasse>([paramètres])
{
...
}
3. Usage
Exemple de classe abstraite
abstract class Vehicule(var vitesse:Float)
{
open var info:String = "information"
get() = field.toUpperCase()
}
class Voiture(var speed:Float):Vehicule(speed)
{
override var info: String = "5L/100"
}
Les classes scellées
1. Présentation
Une classe scellée (sealed class) se comporte comme une classe abstraite avec une particularité en plus. Une classe scellée permet de restreindre les classes pouvant être une sous-classe de celle-ci. Seules les classes définies dans le même fichier que la classe scellée peuvent en hériter.
2. Syntaxe
Voici la syntaxe permettant de définir une classe scellée :
sealed class <NomDeLaClasse>
3. Usage
Exemple d’utilisation d’une classe scellée
sealed class Vehicule
data class Bus(var nombrePassager:Int) : Vehicule()
object PorteAvion : Vehicule()
class Voiture : Vehicule()
Les interfaces
1. Présentation
Une interface permet de décrire un comportement. Une classe implémentant une interface doit donc implémenter le comportement décrit par l’interface. Les interfaces ne peuvent pas stocker d’état et peuvent contenir :
-
des déclarations de fonctions abstraites,
-
des fonctions,
-
des propriétés abstraites,
-
des propriétés avec redéfinition des accesseurs.
2. Syntaxe
Voici la syntaxe permettant de définir une interface :
interface <NomDeLInterface> [extends AutresInterfaces]
{
...
}
Voici la syntaxe permettant à une classe d’implémenter une interface :
class <NomClasse> : <NomInterface> {
...
}
3. Usage
Exemple d’une interface
interface MonInterface {
fun uneFonction()
fun uneAutreFonction() {
//blocs d'instructions possibles
}
}
Exemple d’implémentation
class UneClasse:MonInterface {
override fun uneFonction(){
//blocs d'instructions
}
}
Les classes internes
1. Présentation
Une classe interne est une classe définie à l’intérieur d’une autre classe. Une classe interne peut accéder aux éléments de sa classe englobante. Ce mécanisme est mis en place lorsqu’il y a un lien très étroit entre la classe interne et sa classe englobante.
2. Syntaxe
Voici la syntaxe permettant de définir une classe interne :
class <NomClasseExterne>
{
inner class <NomClasseInterne>
{
...
}
}
3. Usage
Exemple d’utilisation d’une classe interne
class ClasseEnglobante
{
private val info: String = "Ola"
inner class ClasseInterne {
fun test() = info
}
}
Les classes imbriquées
1. Présentation
Une classe imbriquée est une classe définie à l’intérieur d’une autre classe. Une classe imbriquée ne peut pas accéder aux éléments de sa classe englobante. Ce mécanisme est mis en place lorsqu’il y a un lien très étroit entre la classe imbriquée et sa classe englobante.
2. Syntaxe
Voici la syntaxe permettant de définir une classe imbriquée :
class <NomClasseExterne>
{
class <NomClasseImbriquée>
{
...
}
}
3. Usage
Exemple d’utilisation d’une classe imbriquée
class ClasseEnglobante
{
private val info: String = "Ola"
class ClasseImbriquee {
fun test() = "Hello"
}
}
Les classes anonymes
1. Présentation
Une classe anonyme est une classe interne qui dérive d’une classe mère ou qui implémente une interface. Une classe anonyme ne porte pas de nom.
Il y a deux façons d’utiliser une classe anonyme :
-
avec les object expressions
-
avec les object declarations
2. Syntaxe avec les object expressions
Voici la syntaxe permettant de créer une classe anonyme :
object : Implementation() {
// ...
}
3. Usage avec les object expressions
Lorsqu’il est nécessaire d’avoir un objet d’une classe qui ne sera utilisée qu’une seule fois, alors l’utilisation d’une classe anonyme est la bienvenue.
Les classes anonymes sont souvent utilisées en tant que paramètre d’une méthode.
Exemple de classe anonyme
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
Singleton
1. Présentation
Le Singleton est un célèbre design pattern fréquemment utilisé permettant à une classe de n’avoir qu’une seule instance.
2. Syntaxe
Voici la syntaxe permettant de définir un singleton :
object Bus
{
...
}
3. Usage
Exemple de singleton
object Bus
{
var nombrePassager:Int = 0
fun entrer(nom:String)
{
println("$nom entre")
nombrePassager++
}
fun sortir(nom:String)
{
println("$nom sort")
nombrePassager--
}
}
Fonctions statiques
1. Présentation
Une fonction statique peut être exécutée grâce au nom de la classe et ne nécessite pas d’instance de la classe dont elle fait partie. Les fonctions statiques sont utiles pour certains patterns et pour factoriser des fonctionnalités qui n’ont pas besoin d’utiliser de valeur dans un objet. Elles se contentent simplement des valeurs qui leur sont fournies en paramètres et des constantes de la classe dans laquelle elles se trouvent.
2. Syntaxe
Voici la syntaxe permettant de créer une fonction statique :
class <NomDeLaClasse>
{
companion object {
fun <nomDeLaFonctionStatique()
{
...
}
}
}
3. Usage
Pour définir des fonctions de classes (appelées aussi fonctions statiques), c’est-à-dire des fonctions que l’on appelle via le nom de la classe, un bloc d’instructions préfixé de companion objet doit être utilisé.
Exemple de déclaration d’une fonction de classe
class Test
{
companion object {
fun bonjour()
{
...
Enum classe
1. Présentation
Une enum classe permet de définir des objets qui pourront avoir une valeur parmi un panel de valeurs prédéfinies. Ce panel de valeurs est défini lors de la déclaration de l’enum classe.
2. Déclarations
Lors de la déclaration d’une classe enum, vous pouvez définir un ensemble de valeurs simples.
Exemple d’utilisation d’un Enum avec des valeurs simples
enum class jours {
LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI
}
Vous pouvez définir un ensemble de valeurs associées à des propriétés initialisables.
Exemple d’utilisation d’un Enum avec des propriétés initialisables
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
Vous pouvez définir un ensemble de valeurs associées à des fonctions.
Exemple d’utilisation d’un Enum avec des fonctions
enum class Pays {
FRANCE {
override fun getCapitale() = "Paris"
},
BELGIQUE {
override fun getCapitale() = "Bruxelle"
};
abstract fun getCapitale(): String
}
Généricité
1. Présentation
Le principe de généricité est de développer des classes, des interfaces ou des fonctions de manière à ce qu’elles puissent être utilisées par différents types d’objets. C’est pourquoi les classes génériques ont des paramètres de type.
2. Syntaxe
Voici la syntaxe permettant de définir une classe avec un paramètre de type :
class <NomDeLaClasseGenerique><<ParametreDeType>><([parametres]) {
}
Voici la syntaxe permettant de définir une interface avec un paramètre de type :
interface <NomDeLInterfaceGenerique><<ParametreDeType>> {
}
Voici la syntaxe permettant de définir une fonction avec un paramètre de type :
fun <ParametreDeType> <NomDeLaFonctionGenerique>([parametres]):
[TypeDeRetour] {
// ...
}
3. Usage
Exemple d’une classe générique
/**
* Classe permettant de concaténer des objets d'un même type.
* Les différents éléments concaténés sont séparés par un '|'.
*/
class Concateneur<T>(vararg items:T)
{
var nombreDeConcatenation = 0
...
La gestion des erreurs d’exécution
1. Présentation
Lorsqu’il y a une erreur d’exécution dans un programme, un objet de type Exception est créé. Cet objet hérite toujours de la classe Throwable et contient plusieurs informations :
-
un message de description de l’erreur,
-
une stack trace afin d’indiquer où se trouve l’erreur,
-
le motif, cette information est optionnelle.
Pour traiter une erreur, afin que le programme se termine de manière convenable ou continue son exécution normalement, il existe un mécanisme de traitement des erreurs. Ce mécanisme est articulé autour des mots-clés try catch et finally : le mot-clé try permet d’englober les instructions susceptibles de produire une erreur, try peut être utilisé comme une expression. Le mot-clé catch permet de définir les instructions à exécuter en cas d’erreur, il est possible d’utiliser plusieurs fois le mot-clé catch pour définir des instructions spécifiques selon le type d’erreur. Le mot-clé finally permet de définir les instructions à exécuter, qu’il y ait ou non une erreur ; ce mot-clé est facultatif.
Il est possible de générer une exception en utilisant le mot-clé throw combiné avec un objet Exception dérivant...
Les collections
1. Présentation
Les collections permettent d’enregistrer des listes d’objets de même type. Il en existe plusieurs types pour stocker vos objets :
Type |
Description |
List |
Permet d’enregistrer des éléments d’un même type de manière indexée et ordonnée. Un objet de type List ne peut être modifié. |
MutableList |
Permet d’enregistrer des éléments d’un même type de manière indexée et ordonnée. Un objet de type MutableList peut être modifié. |
Set |
Permet d’enregistrer des éléments d’un même type de manière non ordonnée, ce type de liste n’accepte pas de valeur en double. Un objet de type Set ne peut pas être modifié. |
MutableSet |
Permet d’enregistrer des éléments d’un même type de manière non ordonnée, ce type de liste n’accepte pas de valeur en double. Un objet de type MutableSet peut être modifié. |
Map |
Permet d’enregistrer des données sous forme de clé/valeur. Un objet de type Map ne peut pas être modifié. |
MutableMap |
Permet d’enregistrer des données sous forme de clé/valeur. Un objet de type MutableMap peut être modifié. |
Les collections créées à partir des classes List, Set et Map sont immuables.
2. Syntaxe
a. Collection de type List
Voici la syntaxe permettant de définir un objet de type List :
<val|var> <nomVariable>[:List<Int><ParametreDeType>>] =
mutableListOf([valeurs])
b. Collection de type MutableList
Voici la syntaxe permettant de définir un objet de type MutableList :
<val|var> <nomVariable>[:MutableList<] = listOf([valeurs])
c. Collection de type Set
Voici la syntaxe permettant de définir un objet de type Set :
<val|var> <nomVariable>[:Set<Int>] = setOf([valeurs])
d. Collection de type MutableSet
Voici la syntaxe permettant de définir un objet de type MutableSet :
<val|var> <nomVariable>[:MutableSet<Int>] =
mutableSetOf([valeurs])
e. Collection de type Map
Voici la syntaxe permettant de définir un objet de type Map :
<val|var> <nomVariable>[:Map<String, Int>] = mapOf([valeurs])
Les valeurs se définissent comme ceci :...
Les expressions lambda
1. Présentation
Les expressions lambda permettent de passer des traitements en paramètres d’une méthode ; elles permettent de réduire de manière significative la quantité de code.
Une expression lambda est une fonction anonyme, il n’y a pas de déclaration de type de retour, pas de modificateur de fonction, pas de nom. C’est une façon concise et rapide de définir une fonction à l’endroit où elle est utilisée. Les expressions lambda sont juste composées d’une liste de paramètres et d’un bloc d’instructions.
Les lambdas sont une des composantes principales de la programmation fonctionnelle.
2. Syntaxe
Voici la syntaxe d’une expression lambda :
[paramètres] [->] <[{]blocDInstructions[}]>
L’opérateur -> sépare les paramètres du bloc d’instructions qui va les utiliser.
Quelques règles concernant les expressions lambda :
-
La partie "paramètres" n’est pas obligatoire si aucun paramètre n’est utilisé.
-
Il peut y avoir de zéro à n paramètres.
-
Il doit y avoir des parenthèses autour de la partie "paramètres" sauf s’il n’y a qu’un seul paramètre et que son type est inféré, alors les parenthèses sont...