Булевый класс Java - почему бы не перечисление?

11

Мне кажется, что булев класс является идеальным кандидатом для реализации в качестве перечисления.

Глядя на исходный код, большая часть класса - это статические методы, которые можно перемещать без изменений в перечисление, остальные становятся намного проще, чем перечисление. Сравните оригинал (комментарии и статические методы удалены):

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

с версией 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";
   }
}

Есть ли какая-то причина, почему Boolean не может стать перечислением?

Если это код Sun, переопределяющий метод equals (), он пропускает очень фундаментальную проверку сравнения ссылок двух объектов перед сравнением их значений. Вот как я думаю, что метод equals () должен быть:

   public boolean equals(Object obj) {

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

       if (obj instanceof Boolean) {
           return value == ((Boolean)obj).booleanValue();
       }
       return false;
   }
Хайленд Марк
источник
4
Предвидите ли вы другое значение для логического значения, которое не является истинным или ложным?
1
@MichaelT Перечисление не должно иметь более двух значений. Это было бы бессмысленно в Java, потому что в нем есть специальная инструкция для обработки booleans ( if), но с концептуальной точки зрения / с точки зрения теории типов логические значения и перечисления являются экземплярами типов суммы, поэтому я думаю, что было бы справедливо спросить, почему они не не ликвидировать разрыв между ними.
Довал
1
Примечание. Похоже, вы также упустили реализацию valueOf(String)(которая будет конфликтовать со значением перечисления) и магию, getBooleanкоторая может сделать его так, чтобы он Boolean.valueOf("yes")возвращал значение true, а не false. Оба из них являются частью спецификации 1.0 и требуют соответствующей обратной совместимости.
8
@MichaelT FileNotFound конечно!
Донал Феллоуз

Ответы:

13

Ну, я думаю, я мог бы начать с того, что перечисления Java не были добавлены в язык программирования Java до JDK 1.5. и поэтому это решение не было даже альтернативой в первые дни, когда был определен логический класс.

При этом Java имеет репутацию сохраняющей обратной совместимости между выпусками, и поэтому, даже если мы сегодня можем рассматривать ваше решение как хорошую альтернативу, мы не сможем сделать это, не разбив тысячи строк кода, уже использующих старый Boolean. класс.

edalorzo
источник
3
Возможно, вам поможет спецификация языка Java 1.0 для java.lang.Boolean. new Boolean("True")И new Boolean("true")также может вызвать некоторые проблемы , связанные с гипотетической реализации перечислений.
Кажется неправильным разрешать множественные (неизменяемые) объекты, и поэтому использование предоставленных Конструкторов в Boolean не является хорошей идеей, как говорится в документации API.
Highland Mark
Спецификация языка не помогла бы с этим вопросом, поскольку она не определяет реализации Классов. Вот как лучше всего реализовать спецификацию.
Highland Mark
13

Есть некоторые вещи, которые не работают, и не работают довольно удивительным образом, когда вы сравниваете их с предыдущими функциональными возможностями Java.

Мы собираемся игнорировать бокс, так как он был добавлен в 1.5. Гипотетически, если бы Sun захотел, они могли бы заставить enum Booleanсебя вести себя так же, как бокс, выполненный на class Boolean.

Тем не менее, есть и другие удивительные (для программиста) способы, которые внезапно сломались бы по сравнению с функциональностью более раннего класса.

Проблема valueOf (String)

Простой пример этого:

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

Выполнение этого кода дает:

правда
правда
ложный
ложный
Исключение в потоке "main" java.lang.IllegalArgumentException: нет константы перечисления BooleanStuff.MyBoolean.FaLsE
    в java.lang.Enum.valueOf (Enum.java:236)
    at BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:17)
    в BooleanStuff.main (BooleanStuff.java:13)

Проблема здесь в том, что я не могу пройти через что-то, что не является TRUEили FALSEк чему valueOf(String).

Это нормально ... мы просто переопределим это с помощью нашего собственного метода ...

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

Но ... здесь есть проблема. Вы не можете переопределить статический метод .

И так, весь код, который передается trueили Trueсмешанный случай, будет выдавать ошибку - и это весьма впечатляюще, за исключением времени выполнения.

Еще немного веселья с valueOf

Есть некоторые другие биты, которые не работают слишком хорошо:

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

Для foo, я просто получить предупреждение о боксе уже боксировал значение. Тем не менее, код для бара является синтаксической ошибкой:

Ошибка: (7, 24) Java: не найден подходящий метод для valueOf (BooleanStuff.MyBoolean)
    метод BooleanStuff.MyBoolean.valueOf (java.lang.String) не применим
      (фактический аргумент BooleanStuff.MyBoolean не может быть преобразован в java.lang.String путем преобразования вызова метода)
    метод java.lang.Enum.valueOf (java.lang.Class, java.lang.String) не применим
      (не может быть создан из аргументов, потому что фактические и формальные списки аргументов различаются по длине)

Если мы приведем эту синтаксическую ошибку обратно в Stringтип:

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

Мы возвращаем нашу ошибку во время выполнения:

правда
Исключение в потоке "main" java.lang.IllegalArgumentException: нет константы перечисления BooleanStuff.MyBoolean.false
    в java.lang.Enum.valueOf (Enum.java:236)
    at BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:11)
    в BooleanStuff.main (BooleanStuff.java:7)

Зачем кому-то писать это? Не знаю ... но его код, который раньше работал и больше не будет работать.


Не поймите меня неправильно, мне действительно нравится идея только одной копии данного неизменяемого объекта. Перечисление действительно решает эту проблему. Я лично сталкивался с кодом поставщика, в котором были ошибки из кода поставщика, который выглядел примерно так:

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

это никогда не работало (Нет, я не исправлял это, потому что неправильное состояние было исправлено где-то еще, и исправление этого сломало это странным образом, что у меня действительно не было времени на отладку) . Если бы это было перечисление, этот код сработал бы вместо этого.

Тем не менее, необходимость синтаксиса вокруг перечисления (с учетом регистра - поиск в enumConstantDirectory позади valueOf, ошибки времени выполнения, которые должны работать таким же образом для других перечислений) и способ работы статических методов вызывает множество вещей, которые мешают ему быть каплей в замене логического.

Сообщество
источник
1
Если кто-то проектировал, с нуля новый язык со знанием того, как Java работает (и не работает), имея тип объекта Boolean как enum-подобную структуру ... это просто не совсем соответствует тому, как сейчас работает Java. Я уверен, что есть некоторые языковые дизайнеры, которые пинают себя за это. Если бы можно было начать заново с Java 8 и такими вещами, как методы по умолчанию в интерфейсах, я уверен, что многие ошибки Java можно было бы сделать немного чище - в то же время я действительно ценю возможность принять некоторый код Java 1.3 и все еще компилировать его в 1.8 - и вот где мы сейчас.
Не было бы очень сложно добавить метод ofили fromсоответствующий javadoc.
assylias
@assylias соглашение с большей частью другого Java-кода valueOfи Boolean.valueOf () существует с 1.0 . Либо Enums не сможет использовать valueOf в качестве статического метода, либо Boolean понадобится метод, отличный от того, который он использовал. Выполнение или нарушает соглашение или совместимость - и отсутствие логического перечисления не нарушает ни то, ни другое. Исходя из этого, выбор довольно прост.
«Но ... здесь есть проблема. Вы не можете переопределить статический метод». Вы ничего не «переопределяете» - метод все равно не существует в суперклассе. Вместо этого проблема заключается в том, что метод автоматически определяется для всех перечислений, и вы не можете переопределить его.
user102008
«Что касается foo, я просто получаю предупреждение о том, что в бокс уже помещено значение. Однако код для bar является синтаксической ошибкой:« Это неверное сравнение. В Boolean.valueOf(Boolean.valueOf("TRUE")), есть два разных valueOf метода: valueOf(String)и valueOf(boolean). Синтаксическая ошибка, потому что вы забыли реализовать valueOf(boolean)в MyBoolean. Кроме того, между двумя вызовами существует автоматическая коробка, которая жестко задана на языке, Booleanно не работает. MyBooleanЕсли вы реализовали valueOf(boolean) MyBoolean.valueOf(MyBoolean.valueOf("FALSE").booleanValue())работу
user102008,
2

Скорее всего, потому, что примитивный booleanтип не является Enum, а коробочные версии примитивных типов ведут себя почти идентично своей распакованной версии. Например

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

(Производительность может не совпадать, но это другая тема.)

Было бы странно, если бы вы могли написать:

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

но нет:

boolean b = true;
switch(b) {
case true:
    // do things
    break;
case false:
    // do things
    break;
}  
Doval
источник
1
Возможно, вы захотите показать оператор if, который не будет работать и с перечислением.
@MichaelT Я предполагаю, что компилятор все еще сможет распаковать его и сделать ifработу точно так же, как в настоящее время. С другой стороны, нельзя игнорировать тот факт, что вы добавили дополнительную функциональность Boolean, booleanкоторой нет.
Довал
Вау ... вы не можете писать операторы switch для логических выражений в Java? Это безумие.
Томас Эдинг
Классы бокса действуют как примитивы из-за распаковки. Целое число не имеет оператора +.
Highland Mark
@HighlandMark Это правда, но я хочу сказать, что они приложили большие усилия, чтобы удостовериться, что коробочные типы были пригодны для использования так же, как их примитивные аналоги. Распаковка это то, что они должны были реализовать, это не бесплатно.
Довал
0

В дополнение к valueOfвопросу (что является проблемой на уровне Java, он может работать нормально на уровне виртуальной машины Java), это потому , что Booleanимеет открытый конструктор. Это была плохая идея, в настоящее время она устарела, но она здесь, чтобы остаться.

Конрад Боровски
источник
0

Причина в том, что «bool» был частью языка Java намного раньше, чем «enum». В течение многих лет «bool» было очень желательно иметь, в то время как «enum» не было доступно. Только теперь вы можете сказать: «Если бы enum был доступен с самого начала, мы могли бы реализовать bool как enum вместо отдельного типа».

В Swift, который мог бы выразить «bool» как перечисление, есть три структуры с именами «Bool», «DarwinBoolean» и «ObjCBool», реализующие протокол «ExpressibleByBooleanLiteral». (DarwinBoolean совместим с C или C ++ bool, ObjCBool ​​совместим с Objective-C BOOL). «true» и «false» - это специальные значения, распознаваемые компилятором, и могут использоваться только для инициализации объектов, поддерживающих протокол «ExpressibleByBooleanLiteral». Bool имеет внутреннюю переменную "_value", содержащую одноразрядное целое число.

Так что Bool не является частью языка Swift, но входит в стандартную библиотеку. true и false являются частью языка, как и протокол ExpressibleByBooleanLiteral.

gnasher729
источник