The Artima Developer Community
Sponsored Link

Weblogs Forum
Java Enums want to be Classes

25 replies on 2 pages. Most recent reply: Dec 15, 2010 1:59 PM by Andrea Francia

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 25 replies on 2 pages [ 1 2 | » ]
Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Java Enums want to be Classes (View in Weblogs)
Posted: Sep 20, 2005 12:57 PM
Reply to this message Reply
Summary
Or do they?
Advertisement

For the longest while, I was glad that Java didn't have support for enumerated types. I've used them in C and C++ but I often ended up in that weird place that calls for the Replace Type Code with State/Strategy refactoring: I start writing a switch statement that uses the enum and I know that the code in the cases could be in a class, if only I had one instead of the enum.

A few days ago, I was working on some Java code that was going to use in a session at the Better Software conference in San Francisco. It involved navigating a player through a game. The simplest thing was to create an enum called Direction:

public enum Direction
{
    NORTH,
    SOUTH,
    EAST,
    WEST,
}

So far, so good.

I continued working and I noticed that one of the things that I really needed to do was find the opposite of a direction. If Direction was a class it would be easy to do it in a nice OO way, I could just add a getOpposite() method.

Man! My first attempt at using enums in Java 5 and I hit a wall. "But wait!", I said to myself. I remembered that enums can have instance fields and methods in Java:

public enum Direction
{
    NORTH,
    SOUTH,
    EAST,
    WEST;
    
    public Direction getOpposite() {
    	...
    }
}

The trick is figuring out what code to put in getOpposite().

Ideally, it would've been great to set the opposite of each Direction on construction and return that value from getOpposite(). Initially, it looked like that was possible, too. I found that you can pass arguments to enum values on construction. The syntax is a little odd:

public enum Direction
{
    NORTH(SOUTH),
    SOUTH(NORTH),
    EAST(WEST),
    WEST(EAST);
    
    Direction opposite;
    
    Direction(Direction opposite) {
        this.opposite = opposite;
    }
    
    public Direction getOpposite() {
    	return opposite;
    }
}

Unfortunately, this doesn't compile. The compiler tells us that the field SOUTH can't be referenced before it is declared. It looked like the only workaround was to do the following, and you have to admit it is pretty nasty:

public Direction getOpposite() throws DirectionException {
    Direction opposite;
    switch(this) {
        case NORTH: opposite = SOUTH; break;
        case SOUTH: opposite = NORTH; break;
        case EAST: opposite = WEST; break;
        case WEST: opposite = EAST; break;
        default:
            throw new DirectionException("Direction with no opposite:" + this);
    }
    return opposite;
}

The exception is particularly galling. Some statically typed languages make it a compile-time error when a switch on an enumeration doesn't cover all the cases. With Java, we're left with a possible runtime error. That doesn't seem to fit the spirit of the language.

After a bit more reading, I discovered a solution. We can have instance-specific methods on enumerations like this:

public enum Direction {
    NORTH { public Direction getOpposite() { return SOUTH; }},
    EAST { public Direction getOpposite() { return WEST; }},
    SOUTH { public Direction getOpposite() { return NORTH; }},
    WEST { public Direction getOpposite() { return EAST; }};
	
    public abstract Direction getOpposite();
}

It seems like the Java 5 designers handled the evolutionary change case well. You can start with an enum and move to classes pretty easily, but it makes me wonder whether it would've just been easier to start with enums as classes:

public abstract class Direction {}
public class North extends Direction {}
public class South extends Direction {}
public class East extends Direction {}
public class West extends Direction {}

and let them evolve into this:

public abstract class Direction {
    public abstract Direction getOpposite();
}

public class North extends Direction {
    public Direction getOpposite() { return new South(); }
}
...

Next time I feel the need for an enum, I might do that instead.


Vesa Karvonen

Posts: 116
Nickname: vkarvone
Registered: Jun, 2004

Re: Java Enums want to be Classes Posted: Sep 20, 2005 1:29 PM
Reply to this message Reply
> Some statically typed languages make it a compile-time error when a
> switch on an enumeration doesn't cover all the cases.

Indeed. In SML it is actually a warning required by the Definition. Here is how the same example would look in SML:


datatype direction =
NORTH
| SOUTH
| WEST
| EAST

val opposite =
fn NORTH => SOUTH
| SOUTH => NORTH
| WEST => EAST
| EAST => WEST


The textbook solutions to simulating variant types in OO languages can lead to extremely complex code. I recall browsing through a book on writing compilers in Java. The book used the Composite pattern to represent an AST and the Visitor pattern to manipulate ASTs. The implementation required literally about 5 to 10 times more code (one Java file [10 or more lines] + one method [3 or more lines] in a visitor for each AST constructor) than the equivalent would have required in SML using variant types and pattern matching (one variant constructor [1 line of code] + one match clause [1 or more lines] for each AST constructor).

Vladimir Nesov

Posts: 27
Nickname: robotact
Registered: Aug, 2005

Re: Java Enums want to be Classes Posted: Sep 20, 2005 2:22 PM
Reply to this message Reply
> <pre>
> public abstract class Direction {}
> public class North extends Direction {}
> public class South extends Direction {}
> public class East extends Direction {}
> public class West extends Direction {}
> </pre>
>
> <p>and let them evolve into this:</p>
>
> <pre>
> public abstract class Direction {
> public abstract Direction getOpposite();
> }
>
> public class North {
> public Direction getOpposite() { return new South();
> h(); }
> }
> ...
> </pre>
>
> <p>Next time I feel the need for an enumeration, I might
> do that instead.</p>

You can write code using classes almost identical to that of enums. Your second example is of another spirit: it uses named subclasses and provides no instances (as well as obvious singleton). Enums in Java 5 are laguage wrapping of well known enums pattern (providing some code skipping and naming):

public abstract class Direction{
private Direction(){}
public abstract Direction getOpposite();
public final static Direction EAST=new Direction(){
public Direction getOpposite(){return WEST;}};
// and so on
}

Ravi Venkataraman

Posts: 80
Nickname: raviv
Registered: Sep, 2004

Re: Java Enums want to be Classes Posted: Sep 20, 2005 3:00 PM
Reply to this message Reply
This is an interesting problem.

The key to solving this is to remember that Direction has a magnitude. That is, on a conventional x-y axis, EAST is 0 degrees (positive x-axis), North is Positive y-axis (90 degrees), etc.

Have an Enum with a Constructor that takes the orientation as a parameter. To get the opposite direction simply rotate the direction by 180 degrees, do a mopdulo 360 (in case the direction + 180 becomes greater than 360), and return the ENUM value corresponding to it. That is, to find the opposite of North, gets its direction (90 degrees), add 180 to get 270 degrees. Now you know that South has 270 degrees as its orientation. Hence, the opposite of North is South!

To find the opposite of South, we get required direction is (270+180) modulo 360 = 90 => North.

This also has the added flexibility that there is only one piece of code that is used to find the opposite. You can easily add other directions such as NE, SW, etc., without having to write code for finding the opposite.

We should start thinking in terms of how humans think. When asked to find the opposite of a direction, a human simply rotates the angle through 180 degrees, and tells the opposite. With the suggested approach, the algorithm also does the same!

You can write it as an ENUM, a class, or whatever.

If anybody is interested, I'll post the code for this later.

Ravi

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Java Enums want to be Classes Posted: Sep 20, 2005 4:56 PM
Reply to this message Reply
You can also do this directly with enums since they are classes almost identical to normal classes, there are some things that are different:

1. They are abstract with restrictions on what can call their constructors (which means you can't instanciated them directly - only via an enum declaration)

2. They extend Enum (which means they can't extend anything else)

3. The first thing in the class are the static fields that are the enum values (which means if you want other static fields first you have to put them in an interface and implement that interface)

4. The definition of the enum values is syntactic sugar (e.g. for public static final Direction NORTH = new Direction() {};)

Therefore you can write your direction example as:
public enum Direction {
    NORTH() { Direction opposite() { return SOUTH; } },
    SOUTH() { Direction opposite() { return NORTH; } },
    EAST() { Direction opposite() { return WEST; } },
    WEST() { Direction opposite() { return EAST; } };
    abstract Direction opposite();
    public static void main( final String[] notUsed ) {
        out.println( NORTH.opposite() );
    }
}

Or if you prefer
public enum Direction_1 {
    NORTH, EAST, SOUTH, WEST;
    Direction_1 opposite() { return values()[ (ordinal() + 2) % 4 ]; }
    public static void main( final String[] notUsed ) {
        out.println( NORTH.opposite() );
        out.println( WEST.opposite() );
    }
}

Which is short but probably not as clear.

PS The Java highlight filter for some reason doesn't do enum!

Vesa Karvonen

Posts: 116
Nickname: vkarvone
Registered: Jun, 2004

Re: Java Enums want to be Classes Posted: Sep 20, 2005 5:00 PM
Reply to this message Reply
This is actually an interesting exercise in the sense that it is pretty difficult (or even impossible) to express properly in many mainstream languages. Here's how to do it properly in SML.

Let's first specify what we actually want as a signature:


signature DIRECTION =
sig
eqtype direction

val north : direction
val south : direction
val west : direction
val east : direction

val opposite : direction -> direction
end


What the above signature says is that there is a type direction whose values can be tested for equality. It also says that there are four (not necessarily distinct) named values of the type: north, south, west, and east. Finally, there is a function opposite that maps a direction to a direction.

The above signature can be matched by many modules. Here is an implementation using modular arithmetic:


structure IntDirection :> DIRECTION =
struct
type direction = int

val north = 90
val south = 270
val west = 180
val east = 0

fun opposite d = d + 180 mod 360
end


Here is an implementation using a datatype:


structure DatatypeDirection :> DIRECTION =
struct
datatype direction =
north
| south
| west
| east

val opposite =
fn north => south
| south => north
| west => east
| east => west
end


Here is an implementation using strings:


structure StringDirection :> DIRECTION =
struct
type direction = string

val north = "north"
val south = "south"
val west = "west"
val east = "east"

fun opposite d =
if d = north then south
else if d = south then north
else if d = west then east
else west
end


And so on...

All of the above structures (modules) were opaquely sealed by the same signature and they implement the same abstraction. The abstraction is enforced by the type system of SML. For instance, you can not pass an arbitrary integer to IntDirection.opposite nor can you convert an IntDirection.direction to an integer. Here is what happens (SML/NJ) if you try:


- IntDirection.opposite 50 ;
stdIn:13.1-13.25 Error: operator and operand don't agree [literal]
operator domain: IntDirection.direction
operand: int
in expression:
IntDirection.opposite 50

uncaught exception Error
raised at: ../compiler/TopLevel/interact/evalloop.sml:52.48-52.56
../compiler/TopLevel/interact/evalloop.sml:35.55
- IntDirection.north + 10 ;
stdIn:1.1-1.24 Error: operator and operand don't agree [literal]
operator domain: IntDirection.direction * IntDirection.direction
operand: IntDirection.direction * int
in expression:
IntDirection.north + 10
stdIn:1.20 Error: overloaded variable not defined at type
symbol: +
type: IntDirection.direction

uncaught exception Error
raised at: ../compiler/TopLevel/interact/evalloop.sml:52.48-52.56
../compiler/TopLevel/interact/evalloop.sml:35.55


It is also not possible to pass a IntDirection.direction
as a StringDirection.direction:


- StringDirection.opposite IntDirection.north ;
stdIn:1.1-5.5 Error: operator and operand don't agree [tycon mismatch]
operator domain: StringDirection.direction
operand: IntDirection.direction
in expression:
StringDirection.opposite IntDirection.north

uncaught exception Error
raised at: ../compiler/TopLevel/interact/evalloop.sml:52.48-52.56
../compiler/TopLevel/interact/evalloop.sml:35.55

Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Re: Java Enums want to be Classes Posted: Sep 20, 2005 5:49 PM
Reply to this message Reply
> This is actually an interesting exercise in the sense that
> it is pretty difficult (or even impossible) to express
> properly in many mainstream languages. Here's how to do it
> properly in SML.
> ...

That is the thing that really throws me about languages using Hindley-Milner. You can get abstraction for something like Direction, but it is not substitutable across the parameterization. It seems easier to make building blocks, but harder to compose them. String and Int bleed through, don't they?

Vesa Karvonen

Posts: 116
Nickname: vkarvone
Registered: Jun, 2004

Re: Java Enums want to be Classes Posted: Sep 20, 2005 6:22 PM
Reply to this message Reply
> You can get abstraction for something like Direction, but
> it is not substitutable across the parameterization.

Sorry, I don't quite understand what you mean here. What parameterization? (There is no parameterization in the SML examples I wrote.)

> It seems easier to make building blocks, but harder to
> compose them.

Again, I don't quite understand what you mean here.

Functional languages excel at composing stuff out of simple building blocks. There is a vast literature on so called combinator libraries.

> String and Int bleed through, don't they?

No, assuming that I understand your question correctly. An opaque signature ascription completely hides the representation of abstract type specifications. You can not treat a StringDirection.direction as a string nor an IntDirection.direction as an int outside the respective modules.

Jian Li

Posts: 1
Nickname: dearwolf
Registered: Sep, 2005

Re: Java Enums want to be Classes Posted: Sep 21, 2005 2:54 AM
Reply to this message Reply
Oh my god
Such method was already written in JDK API:

___________________________________________________
public enum Operation {
PLUS { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } };

// Do arithmetic op represented by this constant
abstract double eval(double x, double y);
}
___________________________________________________

why write it again?

Ravi Venkataraman

Posts: 80
Nickname: raviv
Registered: Sep, 2004

Re: Java Enums want to be Classes Posted: Sep 21, 2005 8:08 AM
Reply to this message Reply
The idea of using enum for the Directions limits us to the specific Directions chosen at design time. If only these four directions are needed, then an enum works fine. If new Directions need to be added, we have to rewrite the enum class.

If, instead of an enum, a class is used with North, South, etc., as sub-classes, then the design becomes divorced from reality.

North, South, etc., can only be thought of as instances of specific directions, never as classes in themselves. Thinking of them as classes leads one to the strange situation where an application can have 10 "South" objects! How can there be 10 South directions in real life?

In this case, I think sub-classing Direction has its limitations. Strictly speaking, North, South, etc., are instances of Direction, not sub-classes.

If we want a more flexible architecture with the ability to define new Directions (restricting ourselves to one Direction for each physical Direction), and get their opposites; and at the same time not have to recompile the Direction class whenever a new Direction is added, then we can not use enums. We must create a Direction class without sub-classes.

Vladimir Nesov

Posts: 27
Nickname: robotact
Registered: Aug, 2005

Re: Java Enums want to be Classes Posted: Sep 21, 2005 8:25 AM
Reply to this message Reply
If it's singletoned, there is no principal difference. Instead, if you use single class Direction with parameter, you can create 10 SOUTH directions (10 objects with the same parameter), while using enum pattern you can only get single instance.

Ravi Venkataraman

Posts: 80
Nickname: raviv
Registered: Sep, 2004

Re: Java Enums want to be Classes Posted: Sep 21, 2005 10:02 AM
Reply to this message Reply
> If it's singletoned, there is no principal difference.
> Instead, if you use single class Direction with parameter,
> you can create 10 SOUTH directions (10 objects with the
> same parameter), while using enum pattern you can only get
> single instance.

True. But that is easily handled within the class using any one of several techniques.

For example: Make the constructor private, load the various Direction instances from data source/file, then provide access to the Directions by
 Direction north = Direction.get("North"); 
for example ensures that client code never creates the directions directly.

It is the class's responsibilty to ensure that there exists only one instance of "North" direction.

Does this look somewhat like the Flyweight pattern?

Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Re: Java Enums want to be Classes Posted: Sep 21, 2005 2:38 PM
Reply to this message Reply
> The idea of using enum for the Directions limits us to the
> specific Directions chosen at design time. If only these
> four directions are needed, then an enum works fine. If
> new Directions need to be added, we have to rewrite the
> enum class.
>
> If, instead of an enum, a class is used with North, South,
> etc., as sub-classes, then the design becomes divorced
> from reality.

Which is okay by me. To me, our job isn't modeling reality, it's making software that it is correct and changes well. And, it helps when names map well to some real concept, but often they bend and that's fine as long as it doesn't get too confusing. A good example is a method called pay() on a class named Employee. From a reality modeling point of view, that's ridiculous, you shouldn't be able to ask any employee to pay him or her self, but from an OO point of view it may make perfect sense.

> North, South, etc., can only be thought of as instances of
> specific directions, never as classes in themselves.
> Thinking of them as classes leads one to the strange
> situation where an application can have 10 "South"
> objects! How can there be 10 South directions in real
> life?

Yes, but, again, it isn't about modeling reality. In my opinion, the industry was poorly served by some early voices who touted the idea that OO involved modeling reality and that it was akin to data modeling and AI. It's taking us a long time to get over that.

> In this case, I think sub-classing Direction has its
> limitations. Strictly speaking, North, South, etc., are
> instances of Direction, not sub-classes.

Like everything else, it depends on the behavior. If North behaves differently than South, it can be a different class. And, with Java enums, essentially they are different classes, it's just a matter of whether we take advantage of them by creating specific methods for each enum value.

In the code examples, we had an abstract method for getOpposite in the enum which essentially makes each enum value a subclass. From what I've heard, SmallTalkers often use subclassing to do enum-ish things. One thing I know for sure is that False and True are subclasses of Boolean in many (if not all) SmallTalks.

> If we want a more flexible architecture with the ability
> ty to define new Directions (restricting ourselves to one
> Direction for each physical Direction), and get their
> opposites; and at the same time not have to recompile the
> Direction class whenever a new Direction is added, then we
> can not use enums. We must create a Direction class
> without sub-classes.

Agreed. The thing is, these decisions are really about what we need the behavior to be in the application, more than an a priori conception of what direction should be. If the things that Direction does deviate too far, maybe another name will be better. Or, maybe not.

Ravi Venkataraman

Posts: 80
Nickname: raviv
Registered: Sep, 2004

Re: Java Enums want to be Classes Posted: Sep 21, 2005 5:42 PM
Reply to this message Reply
I had written: If, instead of an enum, a class is used with North, South, etc., as sub-classes, then the design becomes divorced from reality.

To which Michael F. replied, Which is okay by me. To me, our job isn't modeling reality, it's making software that it is correct and changes well.

I fail to se how a class Direction with sub-classes East, West, North and South can change well when we have to add new sub-classes like North-West, South-East, etc., that are effectively identical to each other.

As mentioned earlier, if we know that the application is very, very unlikely to add other Directions, then an enum is just fine. I would still have the enum class Direction with two attributes: a name and a number representing the orientation (0 degrees, 90 degrees, etc., or alternative descriptions of the orientations.) Then, the getOpposite is a simple single method. There is no need to define the method in each of the enum sub-classes.

My main concern about this thread is that, at a superficial level, it seems to be about the limitations of enums in Java; while there are deeper problems with the solution suggested originally. Examining the problem a little more shows that the Directions have not been modelled as one thinks about them in real life. A Direction is not just a name, there is a direction (orientation) involved! The fact that a getOpposite method is required suggests that orientation is an important attribute of the problem domain.

Omitting this attribute creates problems. In particular, this leads to the necessity of over-riding the getOpposite method in each sub-class because the Direction had no concept of orientation.

With Direction modelled to have both orientation and name, it becomes obvious that there exists a simple algorithm for finding the opposite of a given Direction. Lo and behold, the original problem of having to write over-ridden methods in each of the sub-classes never arises! We do not have to access somewhat non-obvious features of enums as implemented in Java. Thus, this whole article would not have been necessary.

So, I do think that there is value in modelling things as close as possible to reality, as long as we do not go overboard. Here, it is easy to keep the model simple and close to reality at the same time. If this leads to less code and somewhat more flexible architecture, then it is even better.

Ravi Venkataraman

Posts: 80
Nickname: raviv
Registered: Sep, 2004

Re: Java Enums want to be Classes Posted: Sep 21, 2005 5:50 PM
Reply to this message Reply
Michael F. wrote: A good example is a method called pay() on a class named Employee. From a reality modeling point of view, that's ridiculous, you shouldn't be able to ask any employee to pay him or her self, but from an OO point of view it may make perfect sense.

Nitpicking here, but "pay" is usually a shorthand for "calculatePay" or "getPay". Pay is used as a noun, not as a verb. It seems to be reasonable to ask the Employee class to return a particular Employee's pay (salary).

Flat View: This topic has 25 replies on 2 pages [ 1  2 | » ]
Topic: Conference: JavaPosse Roundup, Feb 22-25 Previous Topic   Next Topic Topic: The Holistic Approach to Software Engineering as a Way to Handle Complexity

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use