La classe booléenne de Java - pourquoi pas une énumération?

11

Il me semble que la classe booléenne est un candidat idéal pour être implémenté en tant qu'énumération.

En regardant le code source, la plupart de la classe est constituée de méthodes statiques qui pourraient être déplacées inchangées vers une énumération, le reste devenant beaucoup plus simple comme une énumération. Comparer l'original (commentaires et méthodes statiques supprimés):

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{
   public static final Boolean TRUE = new Boolean(true);
  public static final Boolean FALSE = new Boolean(false);
   private final boolean value;
   public Boolean(boolean value) {
       this.value = value;
   }
   public Boolean(String s) {
       this(toBoolean(s));
   }
   public boolean booleanValue() {
       return value;
   }
   public String toString() {
       return value ? "true" : "false";
   }
   public int hashCode() {
       return value ? 1231 : 1237;
   }
   public boolean equals(Object obj) {
       if (obj instanceof Boolean) {
           return value == ((Boolean)obj).booleanValue();
       }
       return false;
   }
   public int compareTo(Boolean b) {
       return compare(this.value, b.value);
   }
}

avec une version enum:

public enum Boolean implements Comparable<Boolean>
{
   FALSE(false), TRUE(true);
   private Boolean(boolean value) {
       this.value = value;
   }
   private final boolean value;
   public boolean booleanValue() {
       return value;
   }

   public String toString() {
       return value ? "true" : "false";
   }
}

Y a-t-il une raison pour laquelle Boolean ne peut pas devenir un enum?

S'il s'agit du code Sun pour remplacer la méthode equals (), il manque une vérification très fondamentale de la comparaison des références des deux objets avant de comparer leurs valeurs. Voici comment je pense que la méthode equals () devrait être:

   public boolean equals(Object obj) {

       if (this == obj) {
          return true;
       }

       if (obj instanceof Boolean) {
           return value == ((Boolean)obj).booleanValue();
       }
       return false;
   }
Highland Mark
la source
4
Prévoyez-vous une autre valeur pour un booléen qui n'est pas vrai ou faux?
1
@MichaelT Une énumération n'a pas besoin d'avoir plus de 2 valeurs. Ce serait un peu inutile en Java car il a une instruction spécialisée pour le traitement des booléens ( if) mais d'un point de vue conceptuel / théorie des types, les booléens et les énumérations sont tous les deux des types de somme, donc je pense qu'il est juste de demander pourquoi ils ne l'ont pas fait 't combler l'écart entre eux.
Doval
1
Remarque: Vous semblez également avoir manqué l'implémentation de valueOf(String)(qui serait en conflit avec la valeur de l'énumération) et la magie derrière getBooleanqui peut faire en sorte que Boolean.valueOf("yes")renvoie vrai plutôt que faux. Les deux font partie de la spécification 1.0 et nécessiteraient une compatibilité descendante appropriée.
8
@MichaelT FileNotFound bien sûr!
Donal Fellows

Réponses:

13

Eh bien, je suppose que je pourrais commencer par faire valoir que les énumérations Java n'ont pas été ajoutées au langage de programmation Java avant le JDK 1.5. et par conséquent, cette solution n'était même pas une alternative au début lorsque la classe booléenne a été définie.

Cela étant dit, Java a la réputation de conserver la compatibilité descendante entre les versions et donc, même si nous considérons aujourd'hui votre solution comme une bonne alternative, nous ne pouvons pas le faire sans casser des milliers de lignes de code en utilisant déjà l'ancien booléen. classe.

edalorzo
la source
3
Vous pourriez trouver la spécification de langage Java 1.0 pour java.lang.Boolean pour vous aider. Le new Boolean("True")et new Boolean("true")peut également causer des problèmes avec l'implémentation de l'énumération hypothétique.
Il semble incorrect d'autoriser plusieurs objets (immuables), et donc l'utilisation des constructeurs fournis sur Boolean n'est pas une bonne idée - comme le dit la documentation de l'API.
Highland Mark
La spécification du langage n'aiderait pas avec ce genre de question car elle ne spécifie pas les implémentations des classes. C'est la meilleure façon de mettre en œuvre la spécification.
Highland Mark
13

Il y a certaines choses qui ne fonctionnent pas, et ne fonctionnent pas de manière assez surprenante lorsque vous les comparez aux fonctionnalités précédentes du booléen Java.

Nous allons ignorer la boxe car cela a été ajouté avec 1.5. En théorie, si Sun l'avait voulu, ils auraient pu faire en sorte que le enum Booleancomportement se fasse exactement comme celui de la boxe class Boolean.

Pourtant, il existe d'autres façons surprenantes (pour le codeur) que cela se briserait soudainement par rapport à la fonctionnalité de la classe précédente.

Le problème valueOf (String)

Un exemple simple de ceci est:

public class BooleanStuff {
    public static void main(String args[]) {
        Boolean foo = Boolean.valueOf("TRUE");
        System.out.println(foo);
        foo = Boolean.valueOf("TrUe");
        System.out.println(foo);
        foo = Boolean.valueOf("yes");  // this is actually false
        System.out.println(foo);

        // Above this line is perfectly acceptable Java 1.3
        // Below this line takes Java 1.5 or later

        MyBoolean bar;
        bar = MyBoolean.valueOf("FALSE");
        System.out.println(bar);
        bar = MyBoolean.valueOf("FaLsE");
        System.out.println(bar);
    }

    enum MyBoolean implements Comparable<MyBoolean> {
        FALSE(false), TRUE(true);
        private MyBoolean(boolean value) { this.value = value; }
        private final boolean value;
        public boolean booleanValue() { return value; }
        public String toString() { return value ? "true" : "false"; }
    }
}

L'exécution de ce code donne:

vrai
vrai
faux
faux
Exception dans le thread "main" java.lang.IllegalArgumentException: aucune constante enum BooleanStuff.MyBoolean.FaLsE
    à java.lang.Enum.valueOf (Enum.java:236)
    à BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:17)
    à BooleanStuff.main (BooleanStuff.java:13)

Le problème ici est que je ne peux pas passer à travers tout ce qui est pas TRUEou FALSEà valueOf(String).

C'est ok ... nous allons simplement le remplacer par notre propre méthode ...

    public static MyBoolean valueOf(String arg) {
        return arg.equalsIgnoreCase("true") ? TRUE : FALSE;
    }

Mais ... il y a un problème ici. Vous ne pouvez pas remplacer une méthode statique .

Et donc, tout le code qui circule trueou tout Trueautre cas mélangé sera erroné - et de manière assez spectaculaire avec une exception d'exécution.

Un peu plus de plaisir avec valueOf

Il y a quelques autres bits qui ne fonctionnent pas trop bien:

public static void main(String args[]) {
    Boolean foo = Boolean.valueOf(Boolean.valueOf("TRUE"));
    System.out.println(foo);

    MyBoolean bar = MyBoolean.valueOf(MyBoolean.valueOf("FALSE"));
    System.out.println(bar);
}

Pour foo, je reçois juste un avertissement concernant la mise en boîte d'une valeur déjà en boîte. Cependant, le code pour bar est une erreur de syntaxe:

Erreur: (7, 24) java: aucune méthode appropriée trouvée pour valueOf (BooleanStuff.MyBoolean)
    la méthode BooleanStuff.MyBoolean.valueOf (java.lang.String) n'est pas applicable
      (l'argument réel BooleanStuff.MyBoolean ne peut pas être converti en java.lang.String par conversion d'invocation de méthode)
    la méthode java.lang.Enum.valueOf (java.lang.Class, java.lang.String) n'est pas applicable
      (ne peut pas instancier d'arguments car les listes d'arguments réelles et formelles diffèrent en longueur)

Si nous contraignons cette erreur de syntaxe dans un Stringtype:

public static void main(String args[]) {
    Boolean foo = Boolean.valueOf(Boolean.valueOf("TRUE"));
    System.out.println(foo);

    MyBoolean bar = MyBoolean.valueOf(MyBoolean.valueOf("FALSE").toString());
    System.out.println(bar);
}

Nous récupérons notre erreur d'exécution:

vrai
Exception dans le thread "main" java.lang.IllegalArgumentException: aucune constante enum BooleanStuff.MyBoolean.false
    à java.lang.Enum.valueOf (Enum.java:236)
    à BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:11)
    à BooleanStuff.main (BooleanStuff.java:7)

Pourquoi quelqu'un écrirait ça? Je ne sais pas ... mais son code qui fonctionnait et ne fonctionnerait plus.


Ne vous méprenez pas, j'aime vraiment l'idée d'une seule copie d'un objet immuable donné. L'énumération résout ce problème. J'ai personnellement rencontré du code fournisseur contenant des bogues provenant du code fournisseur qui ressemblait à ceci:

if(boolValue == new Boolean("true")) { ... }

cela n'a jamais fonctionné (non, je ne l'ai pas corrigé car l'état incorrect a été corrigé ailleurs, et la correction de cela a cassé cela de manière étrange que je n'ai vraiment pas eu le temps de déboguer) . S'il s'agissait d'une énumération, ce code aurait plutôt fonctionné.

Cependant, les nécessités de la syntaxe autour de l'énumération (sensible à la casse - creuser dans l' énumConstantDirectory derrière valueOf, les erreurs d'exécution qui doivent fonctionner de cette façon pour les autres énumérations) et le fonctionnement des méthodes statiques provoquent un certain nombre de choses à casser qui l'empêchent de étant une baisse en remplacement d'un booléen.

Communauté
la source
1
Si l'on concevait, à partir de zéro un nouveau langage avec la connaissance du fonctionnement (et non du fonctionnement) de Java, le fait d'avoir le type d'objet booléen soit une structure de type énumération ... cela ne correspond tout simplement pas à la façon dont Java fonctionne maintenant. Je suis sûr que certains concepteurs de langage se donnent des coups de pied pour cela. Si l'on pouvait recommencer avec Java 8 et des choses comme les méthodes par défaut dans les interfaces, je suis sûr que beaucoup des défauts de Java auraient pu être faits un peu plus propres - en même temps, j'apprécie vraiment pouvoir prendre un certain code Java 1.3 et toujours le compiler en 1.8 - et c'est où nous en sommes maintenant.
Il n'aurait pas été très difficile d'ajouter une méthode ofor fromet un javadoc approprié.
assylias
@assylias la convention avec la plupart des autres codes java est valueOfet Boolean.valueOf () existe depuis 1.0 . Soit Enums ne pourrait pas utiliser valueOf comme méthode statique, soit Boolean aurait besoin d'une méthode différente de celle qu'il a utilisée. Faire soit casse la convention ou la compatibilité - et ne pas avoir de booléen comme une énumération rompt non plus. De là, le choix est assez simple.
"Mais ... il y a un problème ici. Vous ne pouvez pas remplacer une méthode statique." Vous ne "remplacez" rien - la méthode n'existe de toute façon pas dans une superclasse. Le problème est que la méthode est automatiquement définie pour toutes les énumérations et que vous ne pouvez pas la redéfinir.
user102008
"Pour foo, je reçois juste un avertissement concernant la mise en boîte d'une valeur déjà mise en boîte. Cependant, le code pour la barre est une erreur de syntaxe:" Ceci est une comparaison incorrecte. Dans Boolean.valueOf(Boolean.valueOf("TRUE")), il existe deux valueOf méthodes différentes : valueOf(String)et valueOf(boolean). L'erreur de syntaxe est due au fait que vous avez oublié d'implémenter le valueOf(boolean)in MyBoolean. Ensuite, il y a la mise en boîte automatique entre les deux appels, qui est codée en dur dans la langue, Booleanmais pas MyBoolean. Si vous avez implémenté des valueOf(boolean) MyBoolean.valueOf(MyBoolean.valueOf("FALSE").booleanValue())travaux
user102008
2

Très probablement parce que le booleantype primitif n'est pas un Enum, et les versions en boîte des types primitifs se comportent presque de manière identique à leur version non en boîte. Par exemple

Integer x = 5;
Integer y = 7;
Integer z = x + y;

(La performance n'est peut-être pas la même, mais c'est un sujet différent.)

Ce serait un peu étrange si vous pouviez écrire:

Boolean b = Boolean.TRUE;
switch (b) {
case Boolean.TRUE:
    // do things
    break;
case Boolean.FALSE:
    // do things
    break;
}

mais non:

boolean b = true;
switch(b) {
case true:
    // do things
    break;
case false:
    // do things
    break;
}  
Doval
la source
1
Vous souhaiterez peut-être afficher l'instruction if qui ne fonctionnerait pas non plus avec une énumération.
@MichaelT J'imagine que le compilateur serait toujours en mesure de le déballer et de faire le iftravail comme il le fait actuellement. D'un autre côté, il n'y a aucun moyen d'ignorer le fait que vous avez ajouté des fonctionnalités supplémentaires à celles Booleanque booleanvous n'avez pas.
Doval
Whoa ... vous ne pouvez pas écrire d'instructions switch pour les booléens en Java? C'est fou.
Thomas Eding
Les classes de boxe agissent uniquement comme les primitives en raison du déballage. L'entier n'a pas d'opérateur +.
Highland Mark
@HighlandMark C'est vrai, mais mon point est qu'ils ont fait de grands efforts pour s'assurer que les types en boîte étaient utilisables de la même manière que leurs homologues primitifs. Le déballage est quelque chose qu'ils devaient mettre en œuvre, il n'est pas gratuit.
Doval
0

En plus de valueOfproblème (qui est un problème au niveau Java, cela pourrait bien fonctionner au niveau JVM), c'est parce qu'il Booleana un constructeur public. C'était une mauvaise idée, actuellement déconseillée, mais c'est celle qui est là pour rester.

Konrad Borowski
la source
0

La raison en est que "bool" faisait partie du langage Java bien avant "enum". Pendant de nombreuses années, "bool" était très souhaitable, tandis que "enum" n'était pas disponible. Ce n'est que maintenant que vous pouvez dire "si l'énumération était disponible depuis le début, alors nous aurions pu implémenter bool comme une énumération au lieu d'un type séparé".

Dans Swift, qui aurait pu exprimer "bool" comme une énumération, il existe trois structures nommées "Bool", "DarwinBoolean" et "ObjCBool" implémentant le protocole "ExpressibleByBooleanLiteral". (DarwinBoolean est compatible avec un booléen C ou C ++, ObjCBool ​​est compatible avec un BOOL Objective-C). "true" et "false" sont des valeurs spéciales reconnues par le compilateur et ne peuvent être utilisées que pour initialiser des objets prenant en charge le protocole "ExpressibleByBooleanLiteral". Bool a une variable interne "_value" contenant un entier d'un bit.

Bool ne fait donc pas partie du langage Swift, mais de la bibliothèque standard. true et false font partie du langage, tout comme le protocole ExpressibleByBooleanLiteral.

gnasher729
la source