Tout ce qui pue dans le langage Java

Il vaut mieux le savoir !

a marqué ce sujet comme résolu.

Bonjour !

Comme on est tous plus ou moins obligés d'utiliser Java dans notre vie professionnelle, je propose de réunir ici les « perles » du langage, les trucs les plus moches sur lesquels on finit par tomber un peu par hasard, les pièges dont on ne sait pas trop pourquoi ils existent, bref, les aspects du langage (ou de ses bibliothèques) qui puent vraiment. Il vaut mieux en rire ! :D

Pour commencer, un exemple assez classique :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A{
    int a;
    A(int a) {
        this.a = a;
    } 
}
class B extends A{
    int b;
    B(int a,int b) {
         this.a = a;
         this.b = b;
    } 
}

Ce code est un exemple typique de l'absurdité de certaines règles du langage, complètement arbitraires : dans la classe B, il faut obligatoirement rajouter un appel à un constructeur de super, sinon le compilateur insère un appel à super() (implicitement). Et ici, bien sûr, on n'a pas de tel constructeur. Le compilateur nous envoie balader avec l'erreur

1
2
3
4
5
6
Constructeur2.java:9: cannot find symbol
symbol  : constructor A()
location: class A
    B(int a,int b) {
                   ^
1 error

Merci pour cette règle parfaitement arbitraire — il n'aurait pas sourcillé si on n'avait pas eu de constructeur du tout dans A !

Dans le même goût :

1
2
3
4
5
class A{
    private int a;
    public A() {System.out.println(a);}
    public A(int a) {this.a = a; this();}
}

On aimerait bien profiter du constructeur vide (qui pourrait ne pas être public !) pour factoriser un code quelconque — mais on ne peut pas, parce que l'appel à this() doit toujours être placé au début des autres constructeurs. Ce qui le rend plutôt inutile ! :p

Bonjour !

Comme on est tous plus ou moins obligés d'utiliser Java dans notre vie professionnelle, je propose de réunir ici les « perles » du langage, les trucs les plus moches sur lesquels on finit par tomber un peu par hasard, les pièges dont on ne sait pas trop pourquoi ils existent, bref, les aspects du langage (ou de ses bibliothèques) qui puent vraiment. Il vaut mieux en rire ! :D

Pour commencer, un exemple assez classique :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A{
    int a;
    A(int a) {
        this.a = a;
    } 
}
class B extends A{
    int b;
    B(int a,int b) {
         this.a = a;
         this.b = b;
    } 
}

Ce code est un exemple typique de l'absurdité de certaines règles du langage, complètement arbitraires : dans la classe B, il faut obligatoirement rajouter un appel à un constructeur de super, sinon le compilateur insère un appel à super() (implicitement). Et ici, bien sûr, on n'a pas de tel constructeur. Le compilateur nous envoie balader avec l'erreur

1
2
3
4
5
6
Constructeur2.java:9: cannot find symbol
symbol  : constructor A()
location: class A
    B(int a,int b) {
                   ^
1 error

Merci pour cette règle parfaitement arbitraire — il n'aurait pas sourcillé si on n'avait pas eu de constructeur du tout dans A !

pizza_yolo

Si je crée la classe A sans constructeur par défaut, c'est peut-être parce que je veux obliger les "autres développeurs" à initialiser l'attribut a s'ils veulent instancier A. Je veux être certain que a sera toujours "initialisé". Du coup, c'est normal que mes classes filles ne puissent pas utiliser le constructeur par défaut… histoire que mon objet soit toujours dans un état cohérent. Donc personnellement, je trouve cette contrainte très importante.

Icic les comportements que tu décris sont parfaitement logiques et compréhensibles. Même s'ils peuvent paraître surprenants au premier abord, ils sont bien plus justifiables que, par exemple, la table de vérité de == en JavaScript ou en PHP.

Dans le premier cas, tu n'as pas compris ce qu'est le constructeur par défaut et encore moins comment Java construit un objet.

Si je reprends ton exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A{
    int a;
    A(int a) {
        this.a = a;
    } 
}
class B extends A{
    int b;
    B(int a,int b) {
         this.a = a;
         this.b = b;
    } 
}

Ici tu déclare quoi ?

  • Que tu as un objet A avec un paramètre a
  • Que pour des raisons logiques, tu veux obliger l'objet A à être créé avec son paramètre a, d'où le seul constructeur disponible qui prends a en paramètre.
  • Que tu as un objet B, qui est un A (héritage) avec un paramètre b supplémentaire.
  • Que pour des raisons logiques, tu veux obliger l'objet B à être créé avec ses paramètres a et b, d'où le seul constructeur disponible qui prends a et b en paramètre.

Mais qu'est-ce que tu essaie de faire dans le constructeur de B et que visiblement tu n'as pas compris ?

Le constructeur crée B. Sauf que par héritage, B est un A, donc il faut commencer par créer A au tout début du constructeur de B pour créer un objet cohérent.

Là où il y a une subtilité, c'est que Java t'évite d'écrire des masses de code inutile en ajoutant des automatismes :

  1. Si tu n'as pas de constructeur du tout, tu ne peux pas construire l'objet. Donc plutôt que de t'obliger à créer un constructeur vide sans paramètre, il le rajoute tout seul. Si tu déclares au moins un constructeur, tu as déjà une manière de construire ton objet. En rajouter automatiquement pourrait avoir des effets de bord, donc Java ne le rajoute pas.
  2. Si tu appelles un constructeur sur un objet qui hérite d'un autre (c'est-à-dire tous les objets, puisqu'ils héritent au moins implicitement de Object), il faut commencer par créer l'objet parent. Si ce n'est pas fait explicitement, Java considère que tu appelles le constructeur sans paramètres et donc ajoute un appel automatique à ce constructeur au début du constructeur hérité.

Résumé en code ça donne :

Pas de constructeur sur l'objet Toto = ajout automatique de :

1
2
3
public Toto() {
    super();
}

Pas d'appel automatique à un constructeur parent en début de constructeur = ajout automatique de super();.

On voit bien que cette assertion est fausse :

il n'aurait pas sourcillé si on n'avait pas eu de constructeur du tout dans A !

Il y a toujours un constructeur dans un objet, et c'est logique, comment le construire sans ça ? Simplement si tu ne le précise pas, Java l'ajoute pour toi.

Qu'est-ce qui se passe donc dans ton cas ? Ceci :

  1. Tu déclares que le seul moyen de construire A c'est de lui passer son paramètre a
  2. Tu essaie de construire B sans appel explicite au constructeur de A. Donc Java suppose que tu veux appeler le constructeur par défaut… qui n'existe pas puisque tu as spécifié dans A qu'un constructeur sans paramètres n'est pas une manière valide de construire A !

Un code correct serait :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A{
    int a;
    A(int a) {
        this.a = a;
    } 
}
class B extends A{
    int b;
    B(int a,int b) {
         super(a);
         this.b = b;
    } 
}

Le second problème correspond à une limitation de Java. Celle-ci je te l'accorde peut être chiante ; mais là encore ça vient de la manière dont Java crée ses objets.

1
2
3
4
5
class A{
    private int a;
    public A() {System.out.println(a);}
    public A(int a) {this.a = a; this();}
}

La règle est très simple : l'appel au constructeur crée l'objet. Avec une subtilité : quand la 1ère ligne du constructeur est un appel à un autre constructeur, Java va créer l'objet à l'aide de cet autre constructeur au lieu de le créer directement, puis appliquer les traitements du premier constructeur. Ceci pour permettre aux constructeurs de s'appeler mutuellement.

La conséquence directe et pas toujours très facile à comprendre, c'est qu'en matière de constructeurs, le code générique se retrouve dans le constructeur le plus spécialisé, les autres étant là pour fournir des comportements par défaut.

Donc, que se passe-t-il dans le constructeur A(int a) de ton exemple ?

  1. La première ligne n'est pas un appel à un constructeur : Java instancie un objet A.
  2. Java affecte la valeur de a
  3. Tu veux de nouveau instancier A. Ca n'a aucun sens, donc Java te plante à la gueule.

Une version correcte de ce code serait quelque chose comme :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static class A{
    private int a;
    public A() {
        this(0);
    }
    public A(int a) {
        this.a = a;
        System.out.println(a);
    }
}

On voit bien la notion de "valeur par défaut" dans le constructeur sans paramètre.


Tout à fait hors sujet, quelque chose m'interpelle dans ton message, c'est ceci :

Comme on est tous plus ou moins obligés d'utiliser Java dans notre vie professionnelle

pizza_yolo

Tu veux dire que ton chef de projet / commercial t'a balancé sur un projet qui utilise Java alors que tu ne sais même pas comment fonctionne un constructeur Java ?

Je pense qu'il est sérieusement temps de changer d'entreprise (parce que là c'est une faute grave de ton supérieur) et/ou de demander une formation Java.

Le problème n'est pas qu'il y ait des règles, même peu naturelles ou difficiles à comprendre. Le problème, c'est qu'il y a des choses que la syntaxe du langage n'exprime pas (le quatrième attribut de visibilité par exemple), qui sont implicites et/ou rajoutées par le compilateur, et même parfois qu'à l'inverse il y a des choses qu'il est possible d'écrire, qui sont syntaxiquement correctes et qui vont quand même être refusées à la compilation (voire à l'exécution) pour des raisons de conception douteuse. Et c'est très clair quand tu dis

Il y a toujours un constructeur dans un objet, et c'est logique, comment le construire sans ça ? Simplement si tu ne le précise pas, Java l'ajoute pour toi.

Pour changer un peu de registre, les fameux tableaux covariants :

1
2
3
4
5
6
7
class Covariants {
  public static void main(String[] _) {
    String[] t1 = new String[1];
    Object[] t2 = t1;
    t2[0] = new Integer(42);
  }
}

Ce code ne génère aucune erreur à la compilation. Aucune ! Pourtant il est bien sûr incorrect, et crashe quand on l'exécute — au passage, bonjour la pénalité de performance due aux tests dynamiques lorsqu'on écrit dans un tableau.

+0 -0

Je n'aime pas le langage Java pour de multiples raisons (quand on vient du C++, il nous manque beaucoup de choses, côté langage).

En revanche, ce que fait le langage relativement à l'appel au constructeur par défaut est parfaitement logique : il y a toujours un constructeur, et un appel au constructeur parent est toujours requis. S'il y avait des baffes à distribuer ici, c'est au développeur de la classe racine qui n'a pas déclaré ses attributs privés. En effet, en permettant aux classes filles d'altérer les attributs, l'objet parent n'est plus à même d'assurer correctement ses invariants : il doit faire confiance à des tiers, et il y a toutes les chances que n'importe quoi soit fait à un moment ou un autre. Il est important de comprendre le rôle des constructeurs relativement aux invariants.

Pour les tableaux covariants, il faut s'en prendre au void*/Object dont tout hérite plutôt qu'à une vraie généricité (et le typage renforcé qui va avec) qui a été perdue en cours de route… Mais c'est une autre histoire.

C'est bien la première fois que je vois quelqu'un râler parce que Java n'est pas assez verbeux !

La conception de Java est certes perfectible, et il y a des choix de syntaxe qui sont injustifiables (la visibilité "par défaut" typiquement). Mais qu'il y ait des automatismes est normal, comme dans tout langage. D'ailleurs l'évolution au fil du temps a été d'ajouter des automatismes, pas d'en enlever.

Essaie de faire du Java <= 4, dans lequel tu n'as aucune forme d'auto-boxing, et tu verras que ces automatismes peuvent être très utiles.

On pourrait aussi parler du Garbage Collector, qui est un bel automatisme et qui fait des tas de trucs non explicites, comme… supprimer des objets ?

Personnellement je suis bien content de ne pas avoir à me taper les constructeurs et appels à super(); à la main de partout.

Les covariants, c'est effectivement une lacune du compilateur qui devrait pouvoir détecter que dans ta ligne 4, t2 n'est pas un tableau d'Object mais de String puisque tu lui force à être un tableau de String (et là on rejoint la réflexion de mon voisin du dessus).

Cela dit c'est un problème que je n'ai jamais rencontré en 5 ans de vie professionnelle. Sans doute parce que dans la majorité des cas on utilise des List et non des tableaux.

Peux-tu détailler ceci ?

au passage, bonjour la pénalité de performance due aux tests dynamiques lorsqu'on écrit dans un tableau


En fait j'ai l'impression que tu prends Java pour ce qu'il n'est pas, et ça expliquerait tes réactions : les automatismes, cette réflexion sur les performances.

Java n'est pas C. Il n'est pas conçu pour que tu ait la main sur tout. Il est conçu pour être utilisé en entreprise et fournir un maximum de vérifications de partout, si possible à la compilation. Donc oui, il y a des test quand tu écris dans un tableau. C'est normal, c'est la philosophie même du langage qui veut ça, et si tu utilises le langage tu l'utilises aussi pour ça : parce qu'il t'interdit de mettre n'importe quoi dans un tableau. Le contraire serait même inquiétant.

Et contrairement à ce que tu sembles penser, vues les performances de Java, je doute que la pénalité de performances soit si importante que ça. Par exemple il existe une batterie de codecs vidéo en Java pur et ils utilisent massivement les tableaux.


Au-delà de ça, il reste des choses à critiquer dans Java, même si Java 8 a encore apporté d'énormes progrès là-dessus.

  • Verbosité (de moins en moins vrai)
  • Consommation mémoire (par rapport à du code compilé, même si souvent les problèmes de consommation mémoire de Java viennent d'une mauvaise compréhension de son modèle mémoire, qui lui n'est pas évident)
  • Types natifs et tous les cas particuliers qu'ils apportent
  • Sacro-sainte rétrocompatibilité, qui est à la fois un avantage et un inconvénient parce qu'on trouve toujours des API complètement obsolète mais non marquées "dépréciées".
  • Des API standard bordéliques et mal gaulées (de moins en moins vrai)

PS @lmghs : aurais-tu quelques minutes pour détailler ça ?

Je n'aime pas le langage Java pour de multiples raisons (quand on vient du C++, il nous manque beaucoup de choses, côté langage).

Je ne connais pas du tout C++ et je suis curieux de savoir ce qu'il manque. Je sais que "l'héritage multiple" en fait partie (c'est une volonté du langage, discutable mais compréhensible vue la philosophie choisie à l'origine) mais je me doute qu'il y a plein d'autres trucs ?

Du coup, j'en profite pour poser la question qui me hante depuis des années : à part par inertie technologique, quel est l'intérêt d'utiliser Java ? En d'autres termes, pourquoi utiliser Java plutôt que C++ ou Python ou Haskell ?

De manière générale, je ne comprends pas la logique d'un langage compilé vers du bytecode : pourquoi compiler le code source si c'est pour qu'il soit quand même interprété à l'exécution, avec les pertes de performance associées et l'obligation pour l'utilisateur d'installer un logiciel annexe pour pouvoir faire fonctionner le tien ?

+1 -4

Pour ce qui est de ce qui manque en Java par rapport au C++ et ce qui fait que perso j'aime pas (en plus de la verbosité) ce sont les "sécurité". On ne peut pas limiter les utilisations avec des const comme on fait en C++. Dans ce dernier il est possible de passer un tableau dont les valeurs ne sont pas modifiables, une variable du type tableau donc on ne peut pas modifier l'adresse (puisqu'on passe une variable du type pointeur, on peut modifier la variable donc l'adresse du tableau). Alors qu'en Java l'utilisateur d'une variable fait ce qu'il veut.. Si un gars a coder une fonction qui modifie les données sans prévenir ça peut être plus que problématique, en C++, tu sais que si c'est pas écrit const ça risque de modifier !

De manière générale, je ne comprends pas la logique d'un langage compilé vers du bytecode : pourquoi compiler le code source si c'est pour qu'il soit quand même interprété à l'exécution, avec les pertes de performance associées et l'obligation pour l'utilisateur d'installer un logiciel annexe pour pouvoir faire fonctionner le tien ?

  • pour éviter de faire toute la phase de parsing/lexing, on a juste des opérations à exécuter linéairement;
  • pour que seul le look and feel soit sensiblement différent d'une plateforme à l'autre mais que le reste soit totalement identique en terme de résultat et de performances;
  • pour fédérer les compétences, java est beaucoup plus utilisé qu'Haskell ou Python et est moins spécialisé que C++, notamment tu as du full stack web grâce à JEE.

Maintenant, moi je n'aime pas java pour :

  • la présence encore extrêmement forte de Swing qui est un framework totalement imprédictible;
  • la verbosité globale, même si Java 1.7 a fait de gros efforts là dessus (switch de string, multicatch, diamond operator…) et que 1.8 continue le gros oeuvre (lambda, annotations…);
  • la quantité de code à produire pour des opérations de tous les jours (les collections, tout ça);
  • pas de possibilité d'utiliser l'opérateur [] sur les collections.

Du coup, j'en profite pour poser la question qui me hante depuis des années : à part par inertie technologique, quel est l'intérêt d'utiliser Java ? En d'autres termes, pourquoi utiliser Java plutôt que C++ ou Python ou Haskell ?

De manière générale, je ne comprends pas la logique d'un langage compilé vers du bytecode : pourquoi compiler le code source si c'est pour qu'il soit quand même interprété à l'exécution, avec les pertes de performance associées et l'obligation pour l'utilisateur d'installer un logiciel annexe pour pouvoir faire fonctionner le tien ?

Dominus Carnufex

Je ne souhaite pas rentrer dans un quelconque troll ou guerre des langages, mais simplement préciser un point : le python (pour le haskell je n'en sais rien du tout) est compilé en bytecode puis exécuté à chaque invocation. Tu peux éventuellement "fixer" le bytecode une bonne fois pour toute : ça te donne un fichier mon_prog.pyc (c pour compiled donc).

Donc, sans parler des différences entre ces 2 langages, mais uniquement en considérant la manière de gérer les bytecodes, Python recompile tout le temps et c'est le même prog qui compile puis exécute, alors qu'en Java tu compile une fois pour toute et ensuite t'as un 2e prog (la JVM bien sûr) qui éxécute.

Après, pour répondre strictement à ceci :

En d'autres termes, pourquoi utiliser Java plutôt que C++ ou Python ou Haskell ?

Java/C++ : de fait, l'utilisation de la JVM oblige les utilisateurs à l'installer, mais au moins ils n'ont pas à recompiler ton prog si jamais la plateforme matérielle change. Exemple d'utilisation de ceci : Android. On est d'accord que ça pourrait être fait autrement, mais il n'empêche qu'en l'état c'est bien pratique (au moins pour les développeurs).

+0 -0

Un nom de classe de 92 caractères, ça pue ?

1
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState

La source

Richou D. Degenne

Ça pue, on est d'accord, mais on s'en fout : c'est une classe en .sun, t'es pas sensé l'utiliser. Si un logiciel fait de la merde, c'est de la faute du logiciel, pas de Java lui-même.

Du coup, j'en profite pour poser la question qui me hante depuis des années : à part par inertie technologique, quel est l'intérêt d'utiliser Java ? En d'autres termes, pourquoi utiliser Java plutôt que C++ ou Python ou Haskell ?

Dominus Carnufex

Je ne connais pas Haskell, mais Java a des domaines d'utilisation très différents de C++ ou Python :

  • C'est un langage à machine virtuelle, C++ est compilé.
  • Il n'a pas du tout la même philosophie de Python. De plus l'implémentation standard de Java est beaucoup plus performante que l'implémentation standard de Python.

De manière générale, je ne comprends pas la logique d'un langage compilé vers du bytecode : pourquoi compiler le code source si c'est pour qu'il soit quand même interprété à l'exécution, avec les pertes de performance associées et l'obligation pour l'utilisateur d'installer un logiciel annexe pour pouvoir faire fonctionner le tien ?

Dominus Carnufex

Pour compléter la réponse d'Artragis, je rajouterais le "Compile once, run everywhere" (qui était la "pub" de Java à une époque.

Par exemple, tous les codes que je produit au boulot sont fait sous Windows et sont exécutés en production sous Linux, sans la moindre recompilation à faire.

Maintenant, moi je n'aime pas java pour :

  • la présence encore extrêmement forte de Swing qui est un framework totalement imprédictible;

artragis

Swing est une grosse daube, on est d'accord. Mais c'est une daube facultative. Ça fait 5 ans que je fais du Java professionnellement, j'ai touché à Swing pendant 3 jours, et encore parce que je l'ai choisi.

  • la verbosité globale, même si Java 1.7 a fait de gros efforts là dessus (switch de string, multicatch, diamond operator…) et que 1.8 continue le gros oeuvre (lambda, annotations…);
  • la quantité de code à produire pour des opérations de tous les jours (les collections, tout ça);
  • pas de possibilité d'utiliser l'opérateur [] sur les collections.

artragis

100% d'accord avec ça. Je ne sais pas ce que donne Ceylon, mais sur le papier ça a l'air cool.

Pour compléter la réponse d'Artragis, je rajouterais le "Compile once, run everywhere" (qui était la "pub" de Java à une époque.

Par exemple, tous les codes que je produit au boulot sont fait sous Windows et sont exécutés en production sous Linux, sans la moindre recompilation à faire.

SpaceFox

Je n'ai jamais trouvé cet argument très pertinent. Je fais du C++ sous Linux, cela génère sans problème les binaires pour linux et windows. Du point de vue de l'utilisateur, du moment où il n'a qu'une chose à installer, que le code soit écrit en C++ ou Java, il s'en moque complètement (si les perfs sont identiques). Ou pas, certains vont râler sur le fait de devoir installer une JVM. D'autres vont râler parce que le fichier à télécharger est plus lourd.

Une chaîne de production est lourde en générale (si elle est bien faite). On va avoir besoin de validation statique et dynamique, de tests unitaires, de la génération de la doc, de la création des paquets, de la produit du DVD, etc. Avoir une chaîne de compilation (Java) ou en avoir 2 ou 3 (C++), cela ne fait pas non plus une grosse différence. C'est casse pied à mettre en place, mais une fois que c'est fait, ça tourne (presque) tout seul.

Ce qui va être casse pied à gérer, c'est tout ce qui va être spécifique aux systèmes : les différentes versions du système (et/ou de la JVM), la prise en charge des libs sur le système, les contraintes graphiques sur tel ou tel système (écran rétinat sur iOS et HD sur Mac OS X), l'accès aux fonctionnalités spécifiques d'un système, etc. Et pour ça, Java et C++, c'est le même bateau

+1 -0

Swing est une grosse daube, on est d'accord. Mais c'est une daube facultative. Ça fait 5 ans que je fais du Java professionnellement, j'ai touché à Swing pendant 3 jours, et encore parce que je l'ai choisi.

Ok, malheureusement, moi je n'ai droit qu'à des offres de stage et java c'est soit JEE (que je ne fais pas parce que le prof de mon école qui apprend JEE le fait oldschool de A à Z (vieille version, vieille philosophie, vieille pédagogie…)) soit du swing.

Il parait que maintenant JavaFX a mis cette époque de côté, mais il fut une époque où le JDK n'était pas livré par défaut avec JavaFX et donc pour faire du graphique soit on mettait l'adaptateur Qt (ou tout autre lib compilées avec JNI/JRA) soit on avait Swing/awt.

Tiens, en ouvrant ce topic, je m'attendais plus à des perles du genre « il existe un moyen pour que while(i>0 && i<0); provoque une boucle infinie »; je ne me rappelle plus du code exact mais il existe des trucs très abscons dans ce goût-là quand on joue avec l'autoboxing.

Quoi qu'il en soit, ça n'a rien à voir avec les absurdités de plusieurs langages typés dynamiquement… je pense en particulier à php et à javascript, champions dans le domaine des illogismes selon moi (et pourtant c'est parmi les langages les plus utilisés). De ce côté-là, Java a quand même plus de 20 ans d'expérience et le modèle objet est plutôt bien construit; même si on regrette parfois de ne pas pouvoir faire new T(), new T[], T.class, etc. en conséquence du type erasure, et de ne pas avoir de surcharge d'opérateur.

De toute façon, tous les langages ont des défauts et des choses plus ou moins illogiques et/ou absurdes. Certains en ont juste un peu plus que d'autres.

Et sinon, pour swing, bordel, pourquoi personne n'utilise SWT à part eclipse ? C'est très bien comme UI toolkit pourtant. 10 fois pllus rapide, moins lourd, plus réactif, plus accessible que cette vieille bouse de swing; c'est peut-être juste pas plus simple.

+0 -1

PS @lmghs : aurais-tu quelques minutes pour détailler ça ?

Je n'aime pas le langage Java pour de multiples raisons (quand on vient du C++, il nous manque beaucoup de choses, côté langage).

Je ne connais pas du tout C++ et je suis curieux de savoir ce qu'il manque.

Des minutes, il en faudrait plein pour détailler ^^'. Je parle du langage uniquement, pas de l'écosystème, qui est je pense plus intéressant. Et je me suis déjà exprimé sur le sujet dans le thread de 40 (ou le double?) pages de long sur dvpz.

De l'héritage

Je sais que "l'héritage multiple" en fait partie (c'est une volonté du langage, discutable mais compréhensible vue la philosophie choisie à l'origine) mais je me doute qu'il y a plein d'autres trucs ?

SpaceFox

Pour l'héritage multiple, Java l'a sous une forme légèrement différente. En Java, il y a une chose qui correspond à l'héritage virtuel multiple de classes 100% abstraites (appelées interfaces en Java). Mais vu que les classes sont 100% abstraites, il n'est pas possible d'y coller (nativement) du comportement comme de la vérification de contrat (au sens programmation par contrat). En C++ on fait ça, et on a collé un nom au pattern: le pattern NVI (cf la FAQ C++ de dvpz pour plus de détails ou mon blog quand j'aurai publié le 3e billet sur la PpC qui présente la pattern NVI). Oui, on peut faire de la PpC en Java, à l'aide de frameworks qui agissent comme des préprocesseurs. En C++, c'est avec de l'huile de coude, mais on reste natif – bien qu'il y ait un regain de discussions au sujet de la PpC pour le C++17. En Eiffel et dans d'autres langages c'est directement intégré.

L'héritage multiple est généralement critiqué car mal utilisé. C'est vrai, quand on ne maitrise pas le LSP, on fait déjà n'importe quoi en terme d'héritage simple (j'ai d'ailleurs un vague souvenir d'une SortedList qui dérive d'une List dans les premières versions de Java). Alors le multiple … le pire est assuré. Pour hériter sans pondre des horreurs, il faut vraiment s'intéresser à la théorie. Chose assez peu compatible avec l'approche "engageons une horde de programmeurs sous-payés et peu expérimentés" – qui fait le succès de Java chez nos employeurs : il est plus facile de dégrossir un programme en Java qu'un programme en C++ (à la fin, il y a toujours des experts qui passent pour ajuster le tir sur les points complexes ; mais le gros-oeuvre en Java va beaucoup plus vite)

Java a eu une approche intéressante en introduisant sémantiquement parlant la notion d'Interface. Il pousse vers le LSP, cela nous éloigne de cet héritage bancalement vendu dans les années 80/90 pour "réutiliser du code". Par contre, la dualité/dichotomie implements/inherits n'est pas la bonne à mon avis. Ce qui est sémantiquement important à distinguer, c'est is subtituable to et is implemented in terms of. Et certes parfois, il y a un mélange des deux. C++ permet la distinction au travers d'une syntaxe qui est sémantiquement pauvre. Le ruby lui est bon sur ce sujet. En Java, le seul moyen d'y arriver, c'est délégation ou héritage – et les deux héritages du Java impliquent nécessairement la substituabilité syntaxique, et ainsi dériver SortedList de List est un échec.

Œillères OO

Les développeurs Java se font souvent avoir par les œillères qu'il impose. Vu que l'on ne peut pas définir de fonctions libres, il tendent à croire que le langage n'est que OO (les types natifs étant la preuve du contraire). De toutes façons ce n'est pas bien grave qu'il soit plus que OO – je dirai au contraire que l'ouverture est un gage de qualité.
Non, le hic là dedans, ce sont les classes ridicules qui ne doivent pas être instanciées comme Math (IIRC du nom), qui vont contenir les fonctions de calcul de sinus, d'élévation à la puissance, etc. Un espace de noms aurait été bien plus propre.

A propos d'OO il manque les multimethods aux langages OO mainstreams (Java, C++, C#).

Du typage

Ca combiné à l'absence de généricité (autre que le sucre syntaxique intégré au Java), il n'y a pas moyen d'avoir des vrais fonctions génériques qui s'appliquent à n'importe quelle séquence. Il faut toujours viser une interface hyper précise et exclure celles qui ressemblent.

Bref, aussi syntaxiquement lourde soit la généricité du C++, elle est bien plus souple que celle du Java pour écrire des choses génériques. Je me souviens avoir galéré dans le passé en Java pour écrire une fonction générique sur des énums. Il n'était pas possible de factoriser mon code (j'avais donné le détail dans le fil sur dvpz je me souviens).

A propose du meilleur typage induit par la généricité en C++, je vous invite à regarder boost.unit. Une proposition de patch du Java avait été faite sur cette thématique il y a bien longtemps, mais elle fut refusée pour raison que cela ne sert à rien/personne ne s'en servirait.

Passage par valeur only

Autre idée reçue chez le développeur Java : il croit qu'il y a un passage par référence. Or il n'y a que des passages par valeur – les références sont des types particuliers et ils sont passés par valeur. Du coup, impossible d'écrire une fonction swap sur des types natifs, ou une fonction qui renvoie en paramètre sortant une instance qui vient d'être allouée (sans rajouter des boites autour des instances sortantes – notez que cela exclue les types natifs).

autres

Je pourrais continuer avec la surcharge des opérateurs, avec le fait que je préfère le RAII au dispose-pattern (mais ça, ce n'est pas important car un équivalent existe), l'Object racine et le bronx qu'il met en présence de sémantique de valeur, …

Bon en lisant le titre du topic ça semblait cool, mais au final quelle a été ma déception.

Comment on peut juger d'un langage sans l'avoir "vraiment" pratiqué ? Un langage de programmation est un tout cohérent, certains choix sont fait par ci par là pour certaines raisons précises.

L'interdiction de l'héritage multiple, la généricité limitée, la verbosité du langage, etc. Ce sont des choix fait pour coller aux mieux aux concepts conceptuels Orienté Objets. Et quand le programme est bien conçu, on se rend compte au final que c'est suffisant.

L'interdiction de l'héritage multiple, la généricité limitée, la verbosité du langage, etc. Ce sont des choix fait pour coller aux mieux aux concepts conceptuels Orienté Objets. Et quand le programme est bien conçu, on se rend compte au final que c'est suffisant.

firm1

Pour le MI et les autres concepts OO, je ne peux que renvoyer à Eiffel qui est un peu un des langages de référence sur le sujet. Donc pour moi le côté : le MI c'est pas OO, ou les multi-methods, c'est pas OO (OK, là Eiffel n'a pas), ne tiennent pas la route. Car un, Java est loin d'être parfait côté OO, et deux ce n'est pas important. Mais alors vraiment pas. Si vous avez un peu de temps à tuer, sans vous lancer dans la vague anti-OO qui a un léger regain de presse en ce moment, je vous invite à écouter cette présentation. https://www.youtube.com/watch?v=COuHLky7E2Q En termes de bien conçu, la copie de Java est là revoir.

Oui, on peut toujours faire autrement. Mais quand on se limite, on limite aussi notre expressivité et là apparaissent des designs patterns qui n'ont d'autres objectifs que de chercher des contournements aux langages que nous utilisons pour remplir des tâches qui sont parfois bien plus simples dans d'autres langages/paradigmes.

PS: l'OO c'est juste abstraction via encapsulation, et substituabilité via le lien fort de l'héritage/sous-typage – qui tire le LSP et la PpC (et encore des théoriciens plus pointus que moi pourront arguer que cette vision est également très limitative). Le reste, c'est des choix de conception et d'interfaces pour les utilisateurs.

Vous permettez que, pour une fois et malgré le titre du topic, que je tape un peu sur C++ aussi ?

Outre les critiques habituelles et puériles du genre « y'a pas de GC c'est nul », en C++, je trouve que séparer la définition de l'implémentation (.hpp/.cpp) est une fondalementalement très bonne chose. Cependant, quelque chose dans cette séparation m'irrite aussi un peu.

JE sais bien qu'il y a des raisons pratiques et historiques derrière ça, mais je trouve extrêmement dommage que les membres privés aient l'obligation de se trouver dans le .hpp aussi. Pour moi ils ne devraient rien avoir à y faire. L'utilisateur du .hpp n'a pas à savoir ce que j'utilise en privé dans mon implémentation.

J'ai déjà essayé de contourner cette limitation avec un truc ultra crado de ce genre; divisant par 2 voire 3 le temps de compilation en bonus simplement parce que l'énorme fichier unordered_map avec ses gigantesques dépendances n'est inclut que là où il est vraiment nécessaire ; ce qui me fait dire que c'est une bonne solution mais avec un mauvais code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MyClass.hpp
class MyClass {
private:
char storage[123];
public:
// constructeur, méthodes, destructeur; osef ici
};

// MyClass.cpp
#include<unordered_map>
using namespace std;

MyClass::MyClass () {
new(storage) unordered_map<string,string>(); // Ultra dégueu
}

unordered_map<string,string>& 
MyClass::myMember () {
return (unordered_map<string,string>&)(storage); // Un peu dégueu
}

MyClass::~MyClass {
myMember().~unordered_map<string,string>(); // Ultra dégueu
}

J'ai sûrement dû louper quelque chose dans mon apprentissage du C++ pour réussir à pondre des atrocités pareilles. Mon langage de prédilection reste quand même toujours le java.

Dans la continuité de ceci, il y a bien sûr l'impossibilité de séparer la définition et l'implémentation des fonctions/classes génériques comme on le fait normalement pour le reste; là aussi c'est très dommage.

Quelqu'un pour m'expliquer où j'ai tort ?

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte