L'inférence de type en Java : on continue de faire n'importe quoi, yaye !

Je viens de tomber sur un article de D-Zone qui détaille une proposition pour ajouter l’inférence de type pour les variables locales dans Java 9. Alors… Bon, vous me connaissez, en ce qui concerne Java, j’aime bien tirer sur l’ambulance. Et dans cet article, je vais pas me priver :D

On remarque, qu’encore une fois, Java ajoute une fonctionnalité réclamée à cor et à cri par les développeurs avec des plombes de retard. Après tout, c’est vrai, l’inférence de type sur les variables locales, ça existe depuis, quoi ? 13 ans dans Groovy ? 12 dans Scala ? 9 dans C♯? 5 dans C++ ?

L’inférence de type pour les nuls

Bon, revenons au point de départ : l’inférence de type, c’est quoi ? C’est capacité pour le compilateur ou l’interpréteur du langage à déduire le type d’une variable sans que le développeur n’ai à le préciser explicitement. Dans Java, il y avait déjà des cas possibles d’inférence de type depuis Java 7 pour l’utilisation des classes et objets génériques.

Exemple :

ArrayList<String> strings = new ArrayList<>();

Ici, on peut directement utiliser un couple de chevrons vide dans la partie

new ArrayList<>();

, le type du générique est inféré à partir de celui déclaré de la variable. De même pour les méthodes génériques, il est possible d’omettre le couple de chevrons. Ceci :

ArrayList<String> strings = new ArrayList<>();
Collections.<String>copy(strings, new ArrayList<>());

est strictement équivalent à cela :

ArrayList<String> strings = new ArrayList<>();
Collections.copy(strings, new ArrayList<>());

Mais bien évidemment, ce n’est pas de ça dont il est question. Mais alors pas du tout ! Non, ce dont je vous parle, c’est de l’inférence de type pour toute déclaration de variable, soit ça :

var strings = new ArrayList<String>();

Et c’est donc ceci qu’il est question de rajouter à Java. Sauf qu’encore une fois, c’est fait n’importe comment.

L’implémentation (bancale) de Java (encore)

Bon, avant toute chose, il convient de faire preuve d’un minimum d’honnêteté intellectuelle en précisant que je m’apprête à parler d’une proposition d’amélioration, c’est-à-dire d’un travail susceptible d’évoluer. Il n’est pas exclut d’avoir de bonnes surprises et de le voir évoluer dans le bon sens. Mais au vu de l’adoration de la communauté pour la complexité, je doute sincèrement que ça évolue dans le bon sens. Ceci étant dit, passons aux choses sérieuses.

Il est donc proposé d’ajouter un mot-clef

var

à Java pour permettre de déclarer des variables dont le type serait déduit à l’initialisation. Et c’est une bonne chose. Ce qui m’a fait bondir, en revanche, c’est la liste des limitations décrites dans la proposition d’amélioration :

Main.java:81: error: cannot infer type for local
variable x
        var x;
            ^
  (cannot use 'val' on variable without initializer)

Main.java:82: error: cannot infer type for local
variable f
        var f = () -> { };
            ^
  (lambda expression needs an explicit target-type)

Main.java:83: error: cannot infer type for local
variable g
        var g = null;
            ^
  (variable initializer is 'null')

Main.java:84: error: cannot infer type for local
variable c
        var c = l();
            ^
  (inferred type is non denotable)

Main.java:195: error: cannot infer type for local variable m
        var m = this::l;
            ^
  (method reference needs an explicit target-type)

Main.java:199: error: cannot infer type for local variable k
        var k = { 1 , 2 };
            ^
  (array initializer needs an explicit target-type)

Alors… On va commencer par ces deux-ci :

Main.java:81: error: cannot infer type for local
variable x
        var x;
            ^
  (cannot use 'val' on variable without initializer)

Main.java:83: error: cannot infer type for local
variable g
        var g = null;
            ^
  (variable initializer is 'null')

En gros, la première limitation, c’est l’obligation d’initialiser la variable à une valeur non-nulle. Vous me direz que c’est normal : si la variable n’est pas initialisée, il est impossible d’en déduire le type. Ah bon ? Pourquoi ? Les objets en Java n’héritent-ils pas tous de

Object

? Pourquoi, dans ce cas, les variables non-initialisées ne pourraient pas être implicitement typés

Object

? Vous me direz : ça pose problème avec les types natifs. Mais même pas, puisque les types natifs peuvent eux aussi être transformés en objets grâce la magie de l’autoboxing.

Bon, ça commence bien…

Ensuite, il y a le cas des tableaux :

Main.java:199: error: cannot infer type for local variable k
        var k = { 1 , 2 };
            ^
  (array initializer needs an explicit target-type)

Pourquoi !? Pour quelle putain de raison le compilateur n’est pas foutu de déduire le type du tableau des objets qui le composent !? C’est possible en Kotlin, c’est possible en Groovy, c’est possible en Scala et c’est possible pour les listes. Alors pourquoi ça ne l’est pas pour les tableaux !?

Ensuite, il y a le cas de l’appel de fonction :

Main.java:84: error: cannot infer type for local
variable c
        var c = l();
            ^
  (inferred type is non denotable)

Celui-là, je ne suis pas certain de le comprendre. Je ne vois pas bien pourquoi il serait impossible de déduire le type de retour d’une fonction. D’autant que l’exemple de départ de la proposition, c’est celui-ci :

var list = new ArrayList<String>();  // infers ArrayList<String>
var stream = list.stream();          // infers Stream<String>

C’est peut-être juste pour donner un exemple de type d’erreur, je suis pas sûr…

Et puis, pour finir, il y a mon petit chouchou à moi :

Main.java:82: error: cannot infer type for local
variable f
        var f = () -> { };
            ^
  (lambda expression needs an explicit target-type)

Main.java:195: error: cannot infer type for local variable m
        var m = this::l;
            ^
  (method reference needs an explicit target-type)

Ah bah oui, forcément, vu la lamentable implémentation des lambdas du langage, ceci ne pouvait qu’arriver… J’explique : en Java — contrairement à tous les bon langages de programmation moderne : Groovy, Kotlin, Scala, C♯… Même C++ ! — les lambdas ne sont pas des objets de première classe, c’est-à-dire, des objets qui peuvent être utilisés sans restriction. En Java, les lambdas ne sont qu’un pauvre sucre syntaxique pour déclarer des implémentations anonymes d’interfaces. Ça signifie qu’on ne peut pas arbitrairement déclarer de nouvelles lamdbas. Il est nécessaire de passer par une interface. Du coup, de manière évidente, le type de l’objet ne correspond pas à un type-fonction, c’est-à-dire à une signature de méthode, mais à une interface.

En déclarant ça :

var f = () -> { };

ou ça :

var m = this::l;

le compilateur se retrouve incapable de déduire le type qui se trouve derrière puisqu’à la signature de la lambda peut correspondre un nombre arbitraire d’interfaces qui, incidemment, ne sont pas du même type. Ce genre de choses arrive quand on fait son travail n’importe comment…

Bref, je sens encore poindre dans la prochaine version de Java de grosses barres de what the fuck

Les commentaires sont fermés.