Classe booleana de Java - por que não uma enumeração?

11

Parece-me que a classe booleana é um candidato ideal para ser implementado como um enum.

Observando o código-fonte, a maioria da classe são métodos estáticos que podem ser movidos inalterados para um enum, o restante se torna muito mais simples como um enum. Compare o original (comentários e métodos estáticos removidos):

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

com uma versão 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";
   }
}

Existe alguma razão pela qual o booleano não pode se tornar um enum?

Se este é o código Sun para substituir o método equals (), está faltando uma verificação muito fundamental na comparação das referências dos dois objetos antes de comparar seus valores. É assim que eu acho que o método equals () deve ser:

   public boolean equals(Object obj) {

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

       if (obj instanceof Boolean) {
           return value == ((Boolean)obj).booleanValue();
       }
       return false;
   }
Highland Mark
fonte
4
Você prevê outro valor para um booleano que não é verdadeiro ou falso?
1
@ MichaelT Um enum não precisa ter mais de 2 valores. Seria meio inútil em Java porque tem uma declaração especializada para processar booleanos ( if), mas do ponto de vista da teoria conceitual / de tipo booleanos e enums são instâncias de tipos de soma, por isso acho justo perguntar por que eles não não preenche a lacuna entre eles.
Doval 28/03
1
Nota: Você também parece ter perdido a implementação de valueOf(String)(que entraria em conflito com o valor da enumeração) e a mágica por trás da getBooleanqual ela pode fazer com que Boolean.valueOf("yes")retorne verdadeiro e não falso. Ambos fazem parte da especificação 1.0 e precisariam de compatibilidade com versões anteriores.
8
@MichaelT FileNotFound of course!
Donal Fellows

Respostas:

13

Bem, acho que poderia começar argumentando que as enumerações Java não foram adicionadas à linguagem de programação Java até o JDK 1.5. e, portanto, essa solução não era sequer uma alternativa nos primeiros dias em que a classe booleana foi definida.

Dito isso, o Java tem a reputação de manter a compatibilidade com versões anteriores entre versões e, portanto, mesmo que hoje consideremos sua solução como uma boa alternativa, não podemos fazê-lo sem quebrar milhares de linhas de código por aí já usando o antigo booleano classe.

edalorzo
fonte
3
Você pode encontrar a especificação da linguagem Java 1.0 para java.lang.Boolean para ajudar. O new Boolean("True")e new Boolean("true")também pode causar alguns problemas com a implementação do enum hipotético.
Parece errado permitir vários objetos (imutáveis) e, portanto, usar os Construtores fornecidos no Boolean não é uma boa ideia - como diz a documentação da API.
Highland Mark
A especificação de idioma não ajudaria nesse tipo de pergunta, pois não especifica as implementações das Classes. É assim que melhor implementar a especificação.
Highland Mark
13

Existem algumas coisas que não funcionam e não funcionam de maneiras surpreendentes quando você as compara à funcionalidade anterior do booleano de Java.

Vamos ignorar o boxe, pois isso foi algo adicionado ao 1.5. Hipoteticamente, se a Sun quisesse, eles poderiam ter enum Booleanse comportado exatamente como o boxe realizado no class Boolean.

No entanto, existem outras maneiras surpreendentes (para o codificador) de que isso seria interrompido repentinamente em comparação com a funcionalidade da classe anterior.

O problema valueOf (String)

Um exemplo simples disso é:

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

A execução deste código fornece:

verdade
verdade
falso
falso
Exceção no encadeamento "main" java.lang.IllegalArgumentException: Nenhuma enumeração constante BooleanStuff.MyBoolean.FaLsE
    em java.lang.Enum.valueOf (Enum.java:236)
    em BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:17)
    em BooleanStuff.main (BooleanStuff.java:13)

O problema aqui é que eu não posso passar por qualquer coisa que não é TRUE, ou FALSEpara valueOf(String).

Tudo bem ... vamos substituí-lo por nosso próprio método ...

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

Mas ... há um problema aqui. Você não pode substituir um método estático .

E assim, todo o código que está passando em torno trueou Trueou algum outro caso misturado vai erro fora - e bastante espetacular com uma exceção de tempo de execução.

Um pouco mais divertido com valor

Existem outros bits que não funcionam muito bem:

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

Pois foo, recebo apenas um aviso sobre o boxe com um valor já em caixa. No entanto, o código para bar é um erro de sintaxe:

Erro: (7, 24) java: nenhum método adequado encontrado para valueOf (BooleanStuff.MyBoolean)
    O método BooleanStuff.MyBoolean.valueOf (java.lang.String) não é aplicável
      (o argumento real BooleanStuff.MyBoolean não pode ser convertido em java.lang.String pela conversão de chamada de método)
    O método java.lang.Enum.valueOf (java.lang.Class, java.lang.String) não é aplicável
      (não é possível instanciar argumentos, porque as listas de argumentos reais e formais diferem em tamanho)

Se forçarmos esse erro de sintaxe de volta a um Stringtipo:

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

Recuperamos o erro de tempo de execução:

verdade
Exceção no encadeamento "main" java.lang.IllegalArgumentException: Nenhuma enumeração constante BooleanStuff.MyBoolean.false
    em java.lang.Enum.valueOf (Enum.java:236)
    em BooleanStuff $ MyBoolean.valueOf (BooleanStuff.java:11)
    em BooleanStuff.main (BooleanStuff.java:7)

Por que alguém escreveria isso? Não sei ... mas seu código costumava funcionar e não funcionava mais.


Não me interpretem mal, eu realmente gosto da ideia de apenas uma cópia de um determinado objeto imutável de todos os tempos. O enum resolve esse problema. Eu, pessoalmente, encontrei o código do fornecedor que apresentava bugs no código do fornecedor que se parecia com isso:

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

isso nunca funcionou (não, eu não o consertei porque o estado incorreto foi consertado em outro lugar, e consertar isso quebrou isso de maneiras estranhas que eu realmente não tive tempo para depurar) . Se isso fosse um enum, esse código teria funcionado.

No entanto, as necessidades da sintaxe em torno da enum ( diferencia maiúsculas de minúsculas - investigam o enumConstantDirectory por trás valueOf, erros de tempo de execução que precisam funcionar dessa maneira para as outras enumerações) e a maneira como os métodos estáticos funcionam causa uma quebra de várias coisas que o impedem de sendo uma queda em substituição a um booleano.

Comunidade
fonte
1
Se alguém estava projetando, do zero uma nova linguagem com o conhecimento de como o Java funciona (e não funciona), fazer com que o tipo de objeto booleano seja uma estrutura semelhante à enumeração ... simplesmente não se encaixa perfeitamente no modo como o Java funciona agora. Tenho certeza de que existem alguns designers de linguagem se esforçando por isso. Se alguém pudesse começar de novo com o Java 8 e coisas como os métodos padrão nas interfaces, tenho certeza de que muitas das características incorretas do Java poderiam ter sido feitas de maneira bem mais limpa - ao mesmo tempo, eu realmente aprecio poder um código Java 1.3 e ainda o compile no 1.8 - e é onde estamos agora.
Não teria sido muito difícil adicionar um método ofou fromo javadoc apropriado.
assylias
@assylias a convenção com grande parte do outro código java é valueOfe Boolean.valueOf () existe desde 1.0 . Ou o Enums não seria capaz de usar valueOf como um método estático ou o Boolean precisaria de um método diferente daquele que está usando. Fazer quebra de convenção ou compatibilidade - e não ter um booleano como enum também não quebra. A partir disso, a escolha é bastante simples.
"Mas ... há um problema aqui. Você não pode substituir um método estático." Você não está "substituindo" nada - o método não existe em nenhuma superclasse. O problema é que o método é definido automaticamente para todas as enumerações e você não pode redefini-lo.
user102008
"Para foo, recebo apenas um aviso sobre o boxe com um valor já em caixa. No entanto, o código para bar é um erro de sintaxe:" Esta é uma comparação incorreta. Em Boolean.valueOf(Boolean.valueOf("TRUE")), existem dois valueOf métodos diferentes : valueOf(String)e valueOf(boolean). O erro de sintaxe é porque você esqueceu de implementar o valueOf(boolean)in MyBoolean. Depois, há o autounboxing entre as duas chamadas, que é codificado na língua para Boolean, mas não MyBoolean.Se você implementou valueOf(boolean) MyBoolean.valueOf(MyBoolean.valueOf("FALSE").booleanValue())obras
user102008
2

Provavelmente porque o booleantipo primitivo não é um Enume as versões em caixa dos tipos primitivos se comportam quase de forma idêntica à sua versão não em caixa. Por exemplo

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

(O desempenho pode não ser o mesmo, mas esse é um assunto diferente.)

Seria meio estranho se você pudesse escrever:

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

mas não:

boolean b = true;
switch(b) {
case true:
    // do things
    break;
case false:
    // do things
    break;
}  
Doval
fonte
1
Você pode mostrar a instrução if que também não funcionaria com uma enumeração.
@ MichaelT: Eu imagino que o compilador ainda possa descompactá-lo e fazer o iftrabalho exatamente como ele faz atualmente. Por outro lado, não há como ignorar o fato de que você adicionou funcionalidades extras às Booleanque booleannão possuem.
Doval 28/03
Whoa ... você não pode escrever instruções de opção para booleanos em Java? Isso é louco.
Thomas Eding 28/03
As classes de boxe agem apenas como as primitivas devido ao unboxing. O número inteiro não tem um operador +.
Mark Highland
@HighlandMark Isso é verdade, mas o que quero dizer é que eles passaram por um grande esforço para garantir que os tipos de caixas fossem utilizáveis ​​da mesma maneira que seus colegas primitivos. Unboxing é algo que eles tiveram que implementar, não é de graça.
Doval 29/03
0

Além da valueOfquestão (que é uma questão no nível Java, pode funcionar bem no nível da JVM), é porque Booleanpossui um construtor público. Essa foi uma péssima idéia, atualmente descontinuada, mas chegou para ficar.

Konrad Borowski
fonte
0

O motivo é que "bool" fazia parte da linguagem Java muito antes do "enum". Por muitos anos, "bool" era muito desejável, enquanto "enum" não estava disponível. Somente agora você pode dizer "se o enum estivesse disponível desde o início, poderíamos ter implementado o bool como um enum em vez de um tipo separado".

No Swift, que poderia expressar "bool" como uma enumeração, existem três estruturas denominadas "Bool", "DarwinBoolean" e "ObjCBool" implementando o protocolo "ExpressibleByBooleanLiteral". (DarwinBoolean é compatível com um booleano C ou C ++, ObjCBool ​​é compatível com um BOOL Objective-C). "true" e "false" são valores especiais reconhecidos pelo compilador e só podem ser usados ​​para inicializar objetos que suportam o protocolo "ExpressibleByBooleanLiteral". Bool possui uma variável interna "_value" que contém um número inteiro de um bit.

Portanto, o Bool não faz parte da linguagem Swift, mas da biblioteca padrão. true e false fazem parte do idioma, assim como o protocolo ExpressibleByBooleanLiteral.

gnasher729
fonte