Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
💥 Les 22 & 23 novembre : Accès 100% GRATUIT
à la Bibliothèque Numérique ENI. Je m'inscris !
  1. Livres et vidéos
  2. Kotlin
  3. Kotlin : la programmation orientée objet
Extrait - Kotlin Les fondamentaux du développement d'applications Android
Extraits du livre
Kotlin Les fondamentaux du développement d'applications Android Revenir à la page d'achat du livre

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...