Pourquoi je suis passé à Kotlin

Ramel Adjibi
Videdressing Labs
Published in
10 min readDec 14, 2018

--

Développeur Android, j’ai toujours été un fan de Java. Java est le language de programmation orienté objet le plus utilisé, et l’un des rares languages à pouvoir être exécuté sur différentes plateformes sans réécrire le code source; à part cela, voici d’autres raisons qui font que Java est l’un de mes languages préférés :

  • Java est open source : C’est ce qui fait la popularité du language. Le code source de la JVM est téléchargeable sur le site d’Oracle.
  • Java est orienté objet : Le fait qu’il soit orienté objet est une bonne chose dans la mesure où on peut développer en adoptant une approche modulaire et flexible.
  • Java est simple et facile à apprendre : what else ? :-)
  • Java vient avec une API très très riche allant de la connexion aux bases de données, à la gestion d’appels réseaux, en passant par le parsing de documents XML; tellement de possibilités natives :-D
  • Java a une énorme collection de librairies open source, notamment dû à une très forte et grande communauté de développeurs #proudtobeapartof :-)
  • Java suit le principe du write once, run everywhere : on peut compiler le code Java sur la plateforme A et l’exécuter sur la plateforme B.

Ceci n’est qu’une partie des features de Java qui en font un language fun. Tout ceci est bien beau mais bon, en tant que technophile, le fun se trouve aussi souvent dans le fait de devoir tester/explorer de nouvelles techno, et dans ce cas précis, de nouveaux languages de programmation :-)

J’ai testé Kotlin pour écrire des applications pour Android…J’ai adoré tout de suite pour son côté pratique, non verbeux, et je vais vous dire pourquoi :

1°) Interoperabilité avec Java

Oui, Kotlin est 100% interoperable avec Java; on peut intégrer directement le code Kotlin dans un projet Java existant. C’est ce que je fais déjà pour notre application Android chez Videdressing.

2°) Constructeur, Getter, Setter

Prenons la classe suivante écrite avec Java :

public class Person {
private String firstName;
private String lastName;

public Person() {}

public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
}

Et ensuite, son équivalence avec Kotlin :

data class Person (var firstName: String = "foo", var lastName: String = "bar")

Notons tout d’abord la simplicité avec laquelle Kotlin permet de définir un POKO (Plain Old Kotlin Object, yeah i made it all up :-)); ensuite, on remarque l’absence des méthodes “get” et “set” dans la classe en Kotlin, contrairement à son homologue Java.

Dans notre exemple, nous avons 2 constructeurs en Java pour créer notre objet comme suit :

//method 1
Person person1 = new Person();
//method 2
Person person2 = new Person("foo", "bar");

Avec Kotlin, lors de la définition de la classe, on peut directement passer les valeurs par défaut des paramètres

data class Person (var firstName: String = "foo", var lastName: String = "bar")

Kotlin offre aussi différentes façon de créer un objet via la même classe; voyons voir :

//method 1
val person1 = Person()

//method 2
val person2 = Person("foofoo")

//method 3
val person3 = Person("foofoo", "barbar")

//method 4
val person4 = Person(lastName = "barbar", firstName = "foofoo")

Comme nous avons déjà des valeurs par défaut, lorsque l’on crée le nouvel objet, on est pas obliger d’initialiser toutes les valeurs du constructeur (méthode 2); et aussi, une des features de Kotlin que j’adore pour la création des objets est la méthode 4 dans laquelle on peut passer les valeurs des paramètres dans l’ordre que l’on souhaite tout en prenant soin d’appeler le nom des paramètres concernés.

Pour pouvoir accéder aux propriétés firstName et lastName, nous avons ici définit les méthodes “get” et “set” en Java.

Pour utiliser l’objet Person en java, on pourrait avoir une méthode comme suit :

public String getPersonName(String firstName, String lastName){

Person person = new Person();

person.setFirstName(firstName);
person.setLastName(lastName);

return person.getFirstName() + " " + person.getLastName();
}

Et ensuite voila l’équivalent Kotlin de la méthode getPersonName()

fun getPersonName(firstName: String, lastName: String): String {

val person = Person(firstName, lastName)

return person.firstName + " " + person.lastName

}

3°) Les variables et les constantes

Avec Java, pour définir une variable et une constante, on doit préciser le type de ces objets :

String s1 = "foo";
final int number = 7;

Comment procède Kotlin pour cela ?

var s1 = "foo"
val
number = 7

Avec Kotlin, pas besoin de préciser le type, ce dernier est déterminé au moment de l’initialisation des objets avec leurs valeurs. Remarquez aussi l’absence de “;” avec Kotlin; ils sont optionnels :-)

4°) La gestion du “null”

Il arrive parfois en Java de devoir tester une variable sur sa nullabilité via une suite de test imbriqués les unes dans les autres, cela peut devenir très complexe, très vite. Prenons par exemple le test suivant :

HTML html = myHelperClass.getHtml();
String text = "";
if (html != null){
Body body = html.getBody();
if (body != null){
Div div = body.getDiv();
if (div.getText() != null){
text = div.getText();
}
}
}
System.out.println(text);

Très sympa cette suite de “if” et de “!=null”. Avec de tels tests, on peut aussi facilement se tromper, et en même temps, le code ici n’est pas très lisible je trouve.

Eh ben là vous vous demandez comment Kotlin améliore les choses dans ce cas ? easy :-)

val html = myHelperClass.html
val text = html?.body?.div?.text ?: ""
println(text)

Oui vous avez bien lu, avec Kotlin, on se retrouve à un code de 4 lignes contre 13 lignes en Java; si l’on souhaite aller plus loins, le code Kotlin peut encore être refactoriser comme suit :

println(myHelperClass.html?.body?.div?.text ?: "")

Plusieurs éléments sont à noter ici avec Kotlin :

  • on utilise pas de getter pour atteindre les propriétés
  • le paramètre “?” seul sert à tester et retourner “null” si la propriété est nulle ou si l’une de ses sous propriétés est aussi nulle.
  • Le paramètre “?:” sert à proposer en cas de nullabilité, une valeur alternative (ou valeur par défaut en cas de null) #awesome

5°) L’extension des méthodes

Encore une superbe feature de Kotlin. Supposons que vous ayez une méthode qui prend en paramètre une chaine de caractères et met en majuscule toutes les premières lettres de tous les mots de cette chaine. En java, on peut écrire cette méthode de la sorte :

public class Utils {

public static String customCamelCase(String phrase){

char[] array = phrase.toCharArray();

array[0] = Character.toUpperCase(array[0]);

for (int i = 1; i < array.length; i++) {
if (Character.isWhitespace(array[i - 1])) {
array[i] = Character.toUpperCase(array[i]);
}
}

return new String(array);
}
}

Pour utiliser cette méthode en Java, on pourrait écrire :

String camelPhrase = Utils.customCamelCase("there must be another and better way to do this!!!");

Sans oublier d’importer bien sûr la classe “Utils” avant l’appel de la méthode

import com.videdressing.utils.Utils;

Au travers de cet exemple, on remarque que utiliser cette méthode dans différente classe de notre application, nous oblige quand même à importer la classe Utils dans laquelle elle a été définie; importer cette classe c’est aussi importer tout ce qu’elle contient comme méthodes, sous classes, objets etc…; mais Kotlin offre mieux comme alternative. Vu que cette méthode nous servira à traiter des objets de type “String”, Kotlin nous offre la possibilité d’étendre juste la classe “String” elle même en y injectant notre méthode.

Vous avez demandé comment faire ? Simple :-)

Dans un fichier Kotlin, on définit la classe et la méthode que l’on souhaite injecter, par exemple pour la classe String :

fun String.customCamelCase(): String {

val array = toCharArray()

array[0] = Character.toUpperCase(array[0])

for (i in 1 until array.size) {
if (Character.isWhitespace(array[i - 1])) {
array[i] = Character.toUpperCase(array[i])
}
}

return String(array)
}

Nous avons injecté la méthode “customCamelCase()” à la classe String; maintenant, quand nous aurons un objet de type String et que nous souhaiterons utiliser la méthode “customCamelCase()”, on pourra procéder comme suit :

val phrase = "there must be another and better way to do this!!!"
val
newPhrase = phrase.customCamelCase()

Contrairement à la version Java où on doit importer la classe Utils (dans notre exemple) à chaque fois, Notre méthode injectée est maintenant appelée comme n’importe quelle méthode native de la classe String. #sweet :-).

L’injection de méthodes ne se limite pas seulement à la classe String bien sûr.

Il arrive parfois que l’on souhaite récupérer une instance de l’activité parente d’un objet View, mais hélas, la méthode View.getParentActivity() n’existe pas :-( ; eh bien, grâce à Kotlin, nous pouvons enfin rajouter cette méthode à la classe View

fun View.getParentActivity(): AppCompatActivity?{
var context = this.context
while (context is ContextWrapper) {
if (context is AppCompatActivity) {
return context
}
context = context.baseContext
}
return null
}

Et ainsi on peut enfin trouver l’activité parente d’un objet View comme suit :

val parentActivity: AppCompatActivity? = view.getParentActivity()

This is magic..really :-)

6°) Smart cast

Prenons l’exemple de ces 2 codes écrits en Java et Kotlin :

Java version :

public void isAPerson(Object aPerson){
if(aPerson instanceof Person){
String displayName = ((Person) aPerson).getDisplayName();
}
}

Kotlin version :

fun isAPerson(aPerson: Any) {
if (aPerson is Person) {
val displayName = aPerson.getDisplayName()
}
}

Ces 2 méthodes sont identiques dans leur fonctionnement; on cherche à récupérer le nom à afficher d’un objet de type “Person”. Vous allez remarquer que en Java, on doit toujours employer l’opérateur de cast avant de se servir des méthodes dédiées à la classe en question; alors qu’avec Kotlin, une fois que le type de l’objet est confirmé par la condition, on peut directement commencer à se servir des propriétés et méthodes associées.

7°) When do you switch ?

Le switch-case de Java est très simple d’utilisation; en cherchant son équivalent en Kotlin, je suis tombé sur when.

Alors when de Kotlin n’est pas du tout un équivalent du swicth-case du Java, mais est plutôt, pour ma part, une version dopée du légendaire if-else; du code sera plus parlant :-)

val x : Any = 0

when(x){
1 -> println("x == 1")
2, 3 -> println("x == 2 or x == 3")
in 4..10 -> println("x is between 4 and 10")
in arrayOf(11, 13, 15, 17) -> println("x is contained in this array")
is String -> println("x is String")
else -> println("What else is there ?")
}

En Java avec le switch-case, il aurait fallu écrire un “case” pour chaque valeur de x et en plus on ne pourrait pas le faire sans connaitre au préalable le type de x. Ça j’achète :-)

8°) Destructuration

Il vous ait certainement déjà arrivé en Java de parcourir une liste et d’avoir besoin simultanément de l’objet courant et de sa position dans la liste

ArrayList<String> listData = new ArrayList<>(Arrays.asList("One", "Two", "Three", "Four", "Five"));

for(int i = 0; i < listData.size(); i++){
String data = listData.get(i);
//do something
}

Kotlin permet de déstructurer certains objets comme les listes pour avoir accès à la position et la valeur de l’objet courant dans la liste

val listData = ArrayList(Arrays.asList("One", "Two", "Three", "Four", "Five"))

for ((position, value) in listData.withIndex()) {
//do something
}

Je trouve la déstructuration encore plus intéressante quand on doit parcourir un objet Map

Parcourons un objet Map en Java

Map<Integer, String> mapData = new HashMap<>();

//add map values here

Set<Map.Entry<Integer, String>> entries = mapData.entrySet();

for(Map.Entry<Integer, String> entry : entries){
int key = entry.getKey();
String value = entry.getValue();

//do something
}

Parcourons un objet Map en Kotlin

val mapData = HashMap<Int, String>()

//add map values here

mapData.forEach{key, value ->
//do something
}

Contrairement au Java où on passe par la récupération d’un Set de Map.Entry à parcourir plus tard, remarquez la feature de déstructuration de Kotlin qui nous donne directement accès aux données :-)

9°) Les méthodes et variables libres

Parfois en Java on a besoin de méthodes utilitaires souvent définies dans une classe que l’on va nommer “Utils” ou autre; ces méthodes ont pour vocation à être utilisées partout dans le code du projet sans avoir besoin de les lier à une classe ou un objet en particulier; l’inconvénient est que ces méthodes doivent toujours être définies dans une classe, et de ce fait, pour s’en servir, on est obligé d’importer la classe avant d’utiliser la méthode en question.

Reprenons l’exemple de la méthode “customCamelCase()” dans la classe Utils :

public class Utils {

public static String customCamelCase(String phrase){

char[] array = phrase.toCharArray();

array[0] = Character.toUpperCase(array[0]);

for (int i = 1; i < array.length; i++) {
if (Character.isWhitespace(array[i - 1])) {
array[i] = Character.toUpperCase(array[i]);
}
}

return new String(array);
}
}

Pour utiliser cette méthode, on procèdera comme suit :

String newPhrase = Utils.customCamelCase("there must be another and better way to do this!!!");

On devra bien sûr importer toute la classe Utils avec ses méthodes, sous classes, objets etc…avant d’utiliser juste cette méthode; un fonctionnement un peu “overkill” je trouve. En passant à Kotlin, on aura juste besoin de créer un simple fichier Kotlin avec l’extension .kt, exemple Utils.kt, puis à l’intérieur définir notre méthode :

fun customCamelCase(phrase: String): String {

val array = phrase.toCharArray()

array[0] = Character.toUpperCase(array[0])

for (i in 1 until array.size) {
if (Character.isWhitespace(array[i - 1])) {
array[i] = Character.toUpperCase(array[i])
}
}

return String(array)
}

Pour s’en servir en Kotlin, on fera juste un appel comme suit :

val newPhrase = customCamelCase("there must be another and better way to do this!!!")

That’s it.

Voici quelques unes des features apportées par Kotlin qui m’ont fait adopté ce language. Ce que je viens de citer n’est qu’une infime partie de ce que l’on peut faire avec Kotlin; si vous souhaitez en apprendre plus, les sites de Kotlin et Kotlin pour Android sont de bons moyens pour commencer.

Une des différences majeures que je constate avec Kotlin, c’est que le language est moins verbeux que Java; on écrit moins de code en Kotlin qu’en Java pour effectuer une tâche donnée.

Ajourd’hui je développe en Kotlin dans notre application Android Videdressing. Tout le nouveau code est écrit en Kotlin et cohabite bien avec l’ancien code Java; n’est ce pas la classe ça ? :-D

That’s all folks :-)

Avez vous vous aussi adopté Kotlin ? Si oui, quelles sont vos raisons ? Si non, quelles sont vos raisons :-D ?

N’hésitez pas à me faire vos retours/suggestions quant à ce post et son contenu.

Thanks for reading.

--

--

Ramel Adjibi
Videdressing Labs

Σ = android lead dev (@Videdressing), photographer, workout warrior, crypto lovers, foods lover, sleeper, entrepreneur, life student…to be continued :-)