Klasa boolowska Javy - dlaczego nie wyliczenie?

11

Wydaje mi się, że klasa boolowska jest idealnym kandydatem do wdrożenia jako wyliczenie.

Patrząc na kod źródłowy, większość klas to metody statyczne, które można przenieść bez zmian do wyliczenia, reszta staje się znacznie prostsza jako wyliczenie. Porównaj oryginał (usunięte komentarze i metody statyczne):

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);
   }
}

z wersją 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";
   }
}

Czy jest jakiś powód, dla którego Boolean nie mógł zostać enum?

Jeśli jest to kod Sun zastępujący metodę equals (), brakuje bardzo podstawowej kontroli porównania referencji dwóch obiektów przed porównaniem ich wartości. Tak myślę, że metoda equals () powinna być:

   public boolean equals(Object obj) {

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

       if (obj instanceof Boolean) {
           return value == ((Boolean)obj).booleanValue();
       }
       return false;
   }
Highland Mark
źródło
4
Czy przewidujesz inną wartość logiczną, która nie jest prawdą ani fałszem?
1
@MichaelT Wyliczenie nie musi mieć więcej niż 2 wartości. Byłoby to trochę bezcelowe w Javie, ponieważ ma wyspecjalizowane oświadczenie do przetwarzania wartości logicznych ( if), ale z punktu widzenia teorii konceptualnej / typów logiczne i wyliczenia są zarówno typami sum, więc myślę, że sprawiedliwe jest pytanie, dlaczego nie wypełnić lukę między nimi.
Doval
1
Uwaga: Wygląda na to, że przegapiłeś implementację valueOf(String)(która kolidowałaby z wartością enum wyliczenia) i magię, getBooleanktóra może sprawić, że będzie ona Boolean.valueOf("yes")zwracać prawdę, a nie fałsz. Oba są częścią specyfikacji 1.0 i wymagałyby odpowiedniej kompatybilności wstecznej.
8
@MichaelT FileNotFound oczywiście!
Donal Fellows

Odpowiedzi:

13

Cóż, myślę, że mógłbym zacząć od argumentowania, że ​​wyliczenia Java nie zostały dodane do języka programowania Java do JDK 1.5. i dlatego to rozwiązanie nie było nawet alternatywą we wczesnych dniach, kiedy zdefiniowano klasę logiczną.

Biorąc to pod uwagę, Java ma reputację polegającą na zachowaniu wstecznej kompatybilności między wydaniami, więc nawet jeśli dzisiaj możemy uznać twoje rozwiązanie za dobrą alternatywę, nie możemy tego zrobić bez zerwania tysięcy linii kodu już przy użyciu starej logiki Boolean klasa.

edalorzo
źródło
3
Można znaleźć Java 1.0 języka spec java.lang.Boolean być pomocne. new Boolean("True")I new Boolean("true")może również powodować pewne problemy z hipotetycznym realizacji enum.
Wydaje się niewłaściwe zezwalanie na wiele (niezmiennych) obiektów, więc używanie dostarczonych Konstruktorów na Boolean nie jest dobrym pomysłem - jak napisano w dokumentacji API.
Highland Mark
Specyfikacja języka nie pomogłaby w tego rodzaju pytaniach, ponieważ nie określa implementacji klas. Oto jak najlepiej wdrożyć specyfikację.
Highland Mark
13

Niektóre rzeczy nie działają i nie działają w dość zaskakujący sposób, gdy porównasz je z poprzednią funkcjonalnością wartości logicznej Java.

Zignorujemy boks, ponieważ dodano coś do 1.5. Hipotetycznie, gdyby Sun tego chciał, mogliby sprawić, enum Booleanby zachowywał się tak, jak boks wykonywany na class Boolean.

Istnieją jednak inne zaskakujące (dla kodera) sposoby, które nagle by się zepsuły w porównaniu z funkcjonalnością wcześniejszej klasy.

Problem valueOf (String)

Prostym przykładem tego jest:

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"; }
    }
}

Uruchomienie tego kodu daje:

prawdziwe
prawdziwe
fałszywe
fałszywe
Wyjątek w wątku „main” java.lang.IllegalArgumentException: Brak stałej wyliczania BooleanStuff.MyBoolean.FaLsE
    at java.lang.Enum.valueOf (Enum.java:236)
    w BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:17)
    at BooleanStuff.main (BooleanStuff.java:13)

Problem polega na tym, że nie mogę przejść przez cokolwiek, co nie jest TRUElub FALSEdo czego valueOf(String).

W porządku ... zastąpimy to naszą własną metodą ...

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

Ale ... jest tu problem. Nie można przesłonić metody statycznej .

I tak cały kod, który jest przekazywany truelub Trueinne pomieszane przypadki będą błędne - i całkiem spektakularnie z wyjątkiem środowiska wykonawczego.

Więcej zabawy z valueOf

Istnieje kilka innych bitów, które nie działają zbyt dobrze:

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);
}

Po fooprostu dostaję ostrzeżenie o boksowaniu już zapakowanej wartości. Jednak kod paska jest błędem składni:

Błąd: (7, 24) java: nie znaleziono odpowiedniej metody dla valueOf (BooleanStuff.MyBoolean)
    metoda BooleanStuff.MyBoolean.valueOf (java.lang.String) nie ma zastosowania
      (rzeczywistego argumentu BooleanStuff.MyBoolean nie można przekonwertować na java.lang.String metodą konwersji wywołania metody)
    metoda java.lang.Enum.valueOf (java.lang.Class, java.lang.String) nie ma zastosowania
      (nie można utworzyć wystąpienia argumentów, ponieważ rzeczywiste i formalne listy argumentów różnią się długością)

Jeśli zmuszymy ten błąd składniowy z powrotem do Stringtypu:

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);
}

Odzyskujemy nasz błąd czasu wykonania:

prawdziwe
Wyjątek w wątku „main” java.lang.IllegalArgumentException: Brak stałej wyliczania BooleanStuff.MyBoolean.false
    at java.lang.Enum.valueOf (Enum.java:236)
    at BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:11)
    at BooleanStuff.main (BooleanStuff.java:7)

Dlaczego ktoś miałby to napisać? Nie wiem ... ale kod, który kiedyś działał i już nie działał.


Nie zrozumcie mnie źle, naprawdę podoba mi się pomysł, aby kiedykolwiek mieć tylko jedną kopię danego niezmiennego obiektu. Wyliczenie rozwiązuje ten problem. Osobiście spotkałem się z kodem dostawcy, który zawierał błędy w kodzie dostawcy, który wyglądał mniej więcej tak:

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

to nigdy nie zadziałało (nie, nie naprawiłem tego, ponieważ nieprawidłowy stan został naprawiony gdzie indziej, a naprawienie tego zepsuło to w dziwny sposób, że tak naprawdę nie miałem czasu na debugowanie) . Gdyby to był wyliczenie, kod działałby zamiast tego.

Jednak konieczność składni wokół wyliczenia (rozróżniana jest wielkość liter - zagłęb się w enumConstantDirectory z tyłu valueOf, błędy w czasie wykonywania, które muszą działać w ten sposób dla innych wyliczeń) i sposób działania metod statycznych powoduje uszkodzenie szeregu rzeczy, które uniemożliwiają będący kroplą zastępującą wartość logiczną.

Społeczność
źródło
1
Jeśli ktoś projektuje, od podstaw nowy język ze znajomością tego, jak działa Java (i nie działa), posiadanie typu obiektu boolowskiego jest strukturą przypominającą wyliczenie ... to po prostu nie pasuje do tego, jak teraz działa Java. Jestem pewien, że niektórzy projektanci języków się za to skopali. Jeśli można zacząć od nowa z Javą 8 i takimi rzeczami, jak domyślne metody interfejsów, jestem pewien, że wiele błędów w Javie mogło zostać zrobionych całkiem czysto - jednocześnie bardzo doceniam to, że mogę wziąć trochę kodu Java 1.3 i nadal kompilujemy go w wersji 1.8 - i tam właśnie jesteśmy.
Nie byłoby bardzo trudno dodać metodę oflub fromodpowiednią metodę javadoc.
assylias
@assylias konwencja z większością innych kodów Java jest valueOfi Boolean.valueOf () istnieje tam od 1.0 . Albo Enums nie byłby w stanie użyć valueOf jako metody statycznej, albo Boolean potrzebowałby innej metody niż ta, której używał. Robienie albo łamie konwencję, albo zgodność - i nie posiadanie logicznego enum nie powoduje złamania. Z tego wybór jest dość prosty.
„Ale ... jest tutaj problem. Nie można zastąpić metody statycznej.” Niczego nie „przesłaniasz” - metoda i tak nie istnieje w nadklasie. Problem polega na tym, że metoda jest automatycznie definiowana dla wszystkich wyliczeń i nie można jej ponownie zdefiniować.
user102008,
„W przypadku foo otrzymuję ostrzeżenie o zapakowaniu już zapakowanej wartości. Jednak kod paska jest błędem składni:„ To niepoprawne porównanie. W Boolean.valueOf(Boolean.valueOf("TRUE"))istnieją dwa różne valueOf sposoby: valueOf(String)a valueOf(boolean). Błąd składni, ponieważ zapomniał wdrożyć valueOf(boolean)in MyBoolean. Następnie następuje automatyczne skasowanie między dwoma wywołaniami, które jest zakodowane na stałe w języku, Booleanale nie. MyBooleanJeśli zaimplementowałeś valueOf(boolean) MyBoolean.valueOf(MyBoolean.valueOf("FALSE").booleanValue())działa
użytkownik102008
2

Najprawdopodobniej dlatego, że booleantyp pierwotny nie jest Enum, a wersje pudełkowe typów pierwotnych zachowują się prawie identycznie jak ich wersja nieopakowana. Na przykład

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

(Wydajność może nie być taka sama, ale to inny temat.)

Byłoby dziwnie, gdybyś mógł napisać:

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

ale nie:

boolean b = true;
switch(b) {
case true:
    // do things
    break;
case false:
    // do things
    break;
}  
Doval
źródło
1
Możesz także chcieć pokazać instrukcję if, która nie działałaby również z wyliczeniem.
@MichaelT Wyobrażam sobie, że kompilator nadal będzie mógł go rozpakować i sprawić, by ifdziałał tak, jak obecnie. Z drugiej strony nie można zignorować faktu, że dodałeś do Booleantego dodatkową funkcjonalność , booleanktórej nie ma.
Doval
Whoa ... nie możesz pisać instrukcji switch dla booleans w Javie? To szalone.
Thomas Eding
Klasy bokserskie zachowują się jak prymityw z powodu rozpakowania. Liczba całkowita nie ma operatora +.
Highland Mark
@HighlandMark To prawda, ale chodzi mi o to, że bardzo się starali, aby upewnić się, że typy pudełkowe są użyteczne w taki sam sposób, jak ich prymitywne odpowiedniki. Unboxing to coś, co musieli wdrożyć, nie przychodzi za darmo.
Doval
0

Oprócz valueOfproblemu (który jest problemem na poziomie Java, może działać dobrze na poziomie JVM), ponieważ Booleanma publiczny konstruktor. To był zły pomysł, obecnie nieaktualny, ale na pewno zostanie.

Konrad Borowski
źródło
0

Powodem jest to, że „bool” był częścią języka Java znacznie wcześniej niż „enum”. Przez wiele lat „bool” był bardzo pożądany, a „enum” nie było dostępne. Dopiero teraz możesz powiedzieć „gdyby od początku dostępny był wylicznik, to moglibyśmy zaimplementować bool jako wyliczenie zamiast osobnego typu”.

W Swift, który mógł wyrazić „bool” jako wyliczenie, istnieją trzy struktury o nazwach „Bool”, „DarwinBoolean” i „ObjCBool” implementujące protokół „ExpressibleByBooleanLiteral”. (DarwinBoolean jest kompatybilny z boolem C lub C ++, ObjCBool ​​jest kompatybilny z BOOLem Objective-C). „true” i „false” są specjalnymi wartościami rozpoznawanymi przez kompilator i można ich używać tylko do inicjowania obiektów obsługujących protokół „ExpressibleByBooleanLiteral”. Bool ma wewnętrzną zmienną „_value” zawierającą jednobitową liczbę całkowitą.

Więc Bool nie jest częścią języka Swift, ale standardowej biblioteki. true i false są częścią języka, podobnie jak protokół ExpressibleByBooleanLiteral.

gnasher729
źródło