It seems to me that the Boolean class is an ideal candidate to be implemented as an enum.
Looking at the source code, most of the class is static methods which could be moved unchanged to an enum, the rest become much simpler as an enum. Compare original (comments and static methods removed):
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);
}
}
with an enum version:
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";
}
}
Is there any reason why Boolean couldn't become an enum?
If this is the Sun code to override the equals() method, it is missing a very fundamental check of comparing the references of the two objects before comparing their values. This is how I think the equals() method should be:
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Boolean) {
return value == ((Boolean)obj).booleanValue();
}
return false;
}
5 Answers 5
Well, I guess I could start by arguing that Java enumerations were not added to the Java programming language until the JDK 1.5. and therefore this solution was not even an alternative in the early days when the Boolean class was defined.
That being said, Java has the reputation of keeping backwards compatibility between releases and so, even if we, today, may consider your solution as a good alternative, we cannot do it without breaking thousands of lines of code out there already using the old Boolean class.
-
3You might find the Java 1.0 language spec for java.lang.Boolean to be of help. The
new Boolean("True")
andnew Boolean("true")
may also cause some problems with the hypothetical enum implementation.user40980– user409802014年03月28日 20:25:24 +00:00Commented Mar 28, 2014 at 20:25 -
It seems wrong to allow multiple (immutable) objects, and so using the provided Constructors on Boolean is not a good idea - as the API documentation says.Highland Mark– Highland Mark2014年03月28日 22:15:49 +00:00Commented Mar 28, 2014 at 22:15
-
The language spec wouldn't help with this sort of question as it doesn't specify the implementations of the Classes. This is how best to implement the specification.Highland Mark– Highland Mark2014年03月28日 22:26:12 +00:00Commented Mar 28, 2014 at 22:26
There are some things that don't work, and don't work in rather surprising ways when you compare them to previous functionality of Java's boolean.
We're going to ignore boxing as that was something added with 1.5. Hypothetically, if Sun wanted to they could have made the enum Boolean
to behave just like the boxing performed on the class Boolean
.
Yet, there are other surprising (to the coder) ways that this would suddenly break compared to the functionality of the earlier class.
The valueOf(String) problem
A simple example of this is:
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"; }
}
}
The run of this code gives:
true true false false Exception in thread "main" java.lang.IllegalArgumentException: No enum constant BooleanStuff.MyBoolean.FaLsE at java.lang.Enum.valueOf(Enum.java:236) at BooleanStuff$MyBoolean.valueOf(BooleanStuff.java:17) at BooleanStuff.main(BooleanStuff.java:13)
The problem here is that I can't pass through anything that isn't TRUE
or FALSE
to valueOf(String)
.
That's ok... we'll just override it with our own method...
public static MyBoolean valueOf(String arg) {
return arg.equalsIgnoreCase("true") ? TRUE : FALSE;
}
But... there's a problem here. You can't override a static method.
And so, all the code that is passing around true
or True
or some other case mixed will error out - and quite spectacularly with a runtime exception.
Some more fun with valueOf
There are some other bits that don't work too well:
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);
}
For foo
, I just get a warning about boxing an already boxed value. However, the code for bar is a syntax error:
Error:(7, 24) java: no suitable method found for valueOf(BooleanStuff.MyBoolean) method BooleanStuff.MyBoolean.valueOf(java.lang.String) is not applicable (actual argument BooleanStuff.MyBoolean cannot be converted to java.lang.String by method invocation conversion) method java.lang.Enum.valueOf(java.lang.Class,java.lang.String) is not applicable (cannot instantiate from arguments because actual and formal argument lists differ in length)
If we coerce that syntax error back into a String
type:
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);
}
We get our runtime error back:
true Exception in thread "main" java.lang.IllegalArgumentException: No enum constant BooleanStuff.MyBoolean.false at java.lang.Enum.valueOf(Enum.java:236) at BooleanStuff$MyBoolean.valueOf(BooleanStuff.java:11) at BooleanStuff.main(BooleanStuff.java:7)
Why anyone would write that? Dunno... but its code that used to work and would no longer work.
Don't get me wrong, I really do like the idea of only one copy of a given immutable object ever. The enum does solve this problem. I've personally encountered vendor code that had bugs in it from vendor code that looked something like this:
if(boolValue == new Boolean("true")) { ... }
that never worked (No, I didn't fix it because the incorrect state was fixed somewhere else, and fixing this broke that in strange ways that I really didn't have the time to debug). If this was an enum, that code would have worked instead.
However, the necessities of the syntax around the enum (case sensitive - dig into the enumConstantDirectory behind valueOf
, runtime errors that need to work that way for the other enums) and the way static methods work causes a number of things to break that prevents it from being a drop in replacement for a Boolean.
-
1If one was designing, from scratch a new language with the knowledge of how Java works (and doesn't), having the Boolean object type be an enum like structure... it just doesn't quite fit with how Java works now. I'm sure there are some language designers kicking themselves for it. If one could start over with Java 8 and things like the default methods in interfaces, I'm sure that a lot of the misfeatures of Java could have been done quite a bit cleaner - at the same time I really do appreciate being able to take a some Java 1.3 code and still compile it in 1.8 - and thats where we are now.user40980– user409802014年03月30日 00:05:17 +00:00Commented Mar 30, 2014 at 0:05
-
It wouldn't have been very difficult to add a
of
orfrom
method and appropriate javadoc.assylias– assylias2014年04月01日 23:03:06 +00:00Commented Apr 1, 2014 at 23:03 -
@assylias the convention with much of the other java code is
valueOf
and Boolean.valueOf() has been there since 1.0. Either Enums wouldn't be able to use valueOf as a static method, or Boolean would need a different method than the one it has been using. Doing either breaks convention or compatibility - and not having Boolean be an enum breaks neither. From this, the choice is fairly simple.user40980– user409802014年04月01日 23:27:26 +00:00Commented Apr 1, 2014 at 23:27 -
"But... there's a problem here. You can't override a static method." You are not "overriding" anything -- the method doesn't exist in a superclass anyway. The problem instead is that the method is automatically defined for all enums, and you cannot redefine it.user102008– user1020082015年07月09日 23:38:02 +00:00Commented Jul 9, 2015 at 23:38
-
"For foo, I just get a warning about boxing an already boxed value. However, the code for bar is a syntax error:" This is an incorrect comparison. In
Boolean.valueOf(Boolean.valueOf("TRUE"))
, there are two differentvalueOf
methods:valueOf(String)
andvalueOf(boolean)
. The syntax error is because you forgot to implement thevalueOf(boolean)
inMyBoolean
. Then there's the autounboxing between the two calls, which is hardcoded in the language forBoolean
but notMyBoolean
.If you implementedvalueOf(boolean)
MyBoolean.valueOf(MyBoolean.valueOf("FALSE").booleanValue())
worksuser102008– user1020082015年07月09日 23:47:57 +00:00Commented Jul 9, 2015 at 23:47
Most likely because the primitive boolean
type isn't an Enum
, and the boxed versions of primitive types behave almost identically to their unboxed version. E.g.
Integer x = 5;
Integer y = 7;
Integer z = x + y;
(The performance may not be the same, but that's a different subject.)
It'd be kind of strange if you could write:
Boolean b = Boolean.TRUE;
switch (b) {
case Boolean.TRUE:
// do things
break;
case Boolean.FALSE:
// do things
break;
}
but not:
boolean b = true;
switch(b) {
case true:
// do things
break;
case false:
// do things
break;
}
-
1You might wish to show the if statement that wouldn't work with an enum either.user40980– user409802014年03月28日 20:20:55 +00:00Commented Mar 28, 2014 at 20:20
-
@MichaelT I imagine the compiler would still be able to unbox it and make the
if
work just like it currently does. On the other hand there's no way to ignore the fact that you've added extra functionality toBoolean
thatboolean
doesn't have.Doval– Doval2014年03月28日 20:24:00 +00:00Commented Mar 28, 2014 at 20:24 -
Whoa... you can't write switch statements for booleans in Java? That's crazy.Thomas Eding– Thomas Eding2014年03月28日 21:58:29 +00:00Commented Mar 28, 2014 at 21:58
-
The boxing classes only act like the primitives due to unboxing. Integer doesn't have a + operator.Highland Mark– Highland Mark2014年03月28日 22:18:38 +00:00Commented Mar 28, 2014 at 22:18
-
@HighlandMark That's true, but my point is that they went through great pains to make sure boxed types were usable in the same ways as their primitive counterparts. Unboxing is something they had to implement, it doesn't come for free.Doval– Doval2014年03月29日 16:41:34 +00:00Commented Mar 29, 2014 at 16:41
In addition to valueOf
issue (which is an issue on Java level, it could work fine on JVM level), it's because Boolean
has a public constructor. That was a bad idea, currently deprecated, but it's one that is here to stay.
The reason is that "bool" was part of the Java language much earlier than "enum". For many years, "bool" was very desirable to have, while "enum" wasn't available. Only now you can say "if enum had been available from the start, then we could have implemented bool as an enum instead of a separate type".
In Swift, which could have expressed "bool" as an enum, there are three structs named "Bool", "DarwinBoolean" and "ObjCBool" implementing the "ExpressibleByBooleanLiteral" protocol. (DarwinBoolean is compatible to a C or C++ bool, ObjCBool is compatible to an Objective-C BOOL). "true" and "false" are special values recognised by the compiler, and can only be used to initialise objects supporting the "ExpressibleByBooleanLiteral" protocol. Bool has an internal variable "_value" containing a one bit integer.
So Bool is not part of the Swift language, but of the standard library. true and false are part of the language, and so is the ExpressibleByBooleanLiteral protocol.
if
) but from a conceptual/type theory point of view booleans and enums are both instances of sum types, so I think it's fair to ask why they didn't bridge the gap between them.valueOf(String)
(which would conflict with the enum's valueOf) and the magic behindgetBoolean
which can make it so thatBoolean.valueOf("yes")
returns true rather than false. Both of which are part of the 1.0 spec and would need appropriate backwards compatibility.