Saturday, January 1, 2011

Chapter 3: Declaring Enums

Java lets you restrict a variable to having one of only a few pre-defined values—in other words, one value from an enumerated list. (The items in the enumerated list are called, surprisingly, enums.)

Using enums can help reduce the bugs in your code. For instance, in your coffee shop application you might want to restrict your size selections to 3 specific sizes. If you let an order for a different size slip in, it might cause an error. Enums to the rescue. With the following simple declaration, you can guarantee that the compiler will stop you from assigning anything to a CoffeeSize except TALL, GRANDE, or VENTI:

enum CoffeeSize { TALL, GRANDE, VENTI };

From then on, the only way to get a CoffeeSize will be with a statement something like this:

CoffeeSize cs = CoffeeSize.TALL;

It’s not required that enum constants be in all caps, but borrowing from the Sun code convention that constants are named in caps, it’s a good idea. Anyone who sees your code will be able to figure out the simple fact that these values are constants.

The basic components of an enum are its constants (i.e., TALL, GRANDE, and VENTI), although in a minute you’ll see that there can be a lot more to an enum. Enums can be declared as their own separate class, or as a class member, however they must not be declared within a method!

Declaring an enum outside a class:

enum CoffeeSize { TALL, GRANDE, VENTI } // this cannot be
// private or protected

class Coffee {
CoffeeSize size;
}

public class CoffeeTest1 {
public static void main(String[] args) {
Coffee drink = new Coffee();
drink.size = CoffeeSize.TALL; // enum outside class
}
}

The preceding code can be part of a single file. The key point to remember is that an enum that isn’t enclosed in a class can be declared with only the public or default modifier, just like a non-inner class. Here’s an example of declaring an enum inside a class:

class Coffee2 {
enum CoffeeSize {TALL, GRANDE, VENTI }

CoffeeSize size;
}

public class CoffeeTest2 {
public static void main(String[] args) {
Coffee2 drink = new Coffee2();
drink.size = Coffee2.CoffeeSize.TALL; // enclosing class
// name required
}
}

The key points to take away from these examples are that enums can be declared as their own class, or enclosed in another class, and that the syntax for accessing an enum’s members depends on where the enum was declared.

The following is NOT a legal usage of enums:

public class CoffeeTest1 {
public static void main(String[] args) {
enum CoffeeSize { TALL, GRANDE, VENTI } // WRONG! Cannot declare enums
//in methods
Coffee drink = new Coffee();
drink.size = CoffeeSize.TALL;
}
}

To make it more confusing for you, the Java language designers made it optional to put a semicolon at the end of the enum declaration (when no other declarations for this enum follow):

public class CoffeeTest1 {

enum CoffeeSize { TALL, GRANDE, VENTI }; //This “;” is optional here
public static void main(String[] args) {
Coffee drink = new Coffee();
drink.size = CoffeeSize.TALL;
}
}

So what gets created when you make an enum? The most important thing to remember is that enums are not Strings or ints! Each of the enumerated CoffeeSize types are actually instances of CoffeeSize. In other words, TALL is of type CoffeeSize. Think of an enum as a kind of class, that looks something (but not exactly) like this:

// conceptual example of how you can think about enums

class CoffeeSize {
public static final CoffeeSize TALL = new CoffeeSize("TALL", 0);
public static final CoffeeSize GRANDE = new CoffeeSize("GRANDE", 1);
public static final CoffeeSize VENTI = new CoffeeSize("VENTI", 2);

public CoffeeSize(String enumName, int index) {
// More stuff here
}
public static void main(String[] args) {
System.out.println(CoffeeSize.TALL);
}
}

Notice how each of the enumerated values, TALL, GRANDE, and VENTI, are instances of type CoffeeSize. They’re represented as static and final, which in the Java world, is thought of as a constant. Also notice that each enum value knows its index or position. In other words, the order in which enum values are declared matters. You can think of the CoffeeSize enums as existing in an array of type CoffeeSize, and as you’ll see in a later chapter, you can iterate through the values of an enum by invoking the values() method on any enum type.

Declaring Constructors, Methods, and Variables in an enum

Because an enum really is a special kind of class, you can do more than just list the enumerated constant values. You can add constructors, instance variables, methods, and something really strange known as a constant specific class body. To understand why you might need more in your enum, think about this scenario: imagine you want to know the actual size, in ounces, that map to each of the three CoffeeSize constants. For ex, you want to know that TALL is 12 ounces, GRANDE is 16 ounces, and VENTI is 20 ounces.

You could make some kind of a lookup table, using some other data structure, but that would be a poor design and hard to maintain. The simplest way is to treat your enum values (TALL, GRANDE, and VENTI), as objects that can each have their own instance variables. Then you can assign those values at the time the enums are initialized, by passing a value to the enum constructor. This takes a little explaining, but first look at the following code:

enum CoffeeSize {
// 12, 16 & 20 are passed to the constructor
TALL(12), GRANDE(16), VENTI(20);
CoffeeSize(int ounces) { // constructor
this.ounces = ounces;
}

private int ounces; // an instance variable
public int getOunces() {
return ounces;
}
}

class Coffee {
CoffeeSize size; // each instance of Coffee has an enum

public static void main(String[] args) {
Coffee drink1 = new Coffee();
drink1.size = CoffeeSize.TALL;

Coffee drink2 = new Coffee();
drink2.size = CoffeeSize.VENTI;

System.out.println(drink1.size.getOunces()); // prints 8
for(CoffeeSize cs: CoffeeSize.values())
System.out.println(cs + " " + cs.getOunces());
}
}

Which produces:
12
TALL 12
GRANDE 16
VENTI 20

Tip: Every enum has a static method, values(), that returns an array of the enum’s values in the order they’re declared.

The key points to remember about enum constructors are
• You can NEVER invoke an enum constructor directly. The enum constructor is invoked automatically, with the arguments you define after the constant value. For example, TALL(12) invokes the CoffeeSize constructor that takes an int, passing the int literal 12 to the constructor. (Behind the scenes, of course, you can imagine that TALL is also passed to the constructor, but we don’t have to know—or care—about the details.)
• You can define more than one argument to the constructor, and you can overload the enum constructors, just as you can overload a normal class constructor. To initialize a CoffeeType with both the number of ounces and, say, a lid type, you’d pass two arguments to the constructor as TALL(12, "A"), which means you have a constructor in CoffeeSize that takes both an int and a String.

And finally, you can define something really strange in an enum that looks like an anonymous inner class. It’s known as a constant specific class body, and you use it when you need a particular constant to override a method defined in the enum.

Imagine a scenario:

You want enums to have two methods—one for ounces and one for lid code (a String). Now imagine that most coffee sizes use the same lid code, “B”, but the VENTI size uses type “A”. You can define a getLidCode() method in the CoffeeSize enum that returns “B”, but then you need a way to override it for VENTI. You don’t want to do some hard-to-maintain if/then code in the getLidCode() method, so the best approach might be to somehow have the VENTI constant override the getLidCode() method.

This looks strange, but you need to understand the basic declaration rules:

enum CoffeeSize {
TALL(12),
GRANDE(16),
VENTI(20) { // start a code block that defines
// the "body" for this constant

public String getLidCode() { // override the method
// defined in CoffeeSize
return "A";
}
}; // the semicolon is REQUIRED when more code follows

CoffeeSize(int ounces) {
this.ounces = ounces;
}

private int ounces;

public int getOunces() {
return ounces;
}
public String getLidCode() { // this method is overridden
// by the VENTI constant

return "B"; // the default value we want to return for
// CoffeeSize constants
}
}

As you can see, the Enums are a little complicated. You can expect some questions about these in the exam and so it is important that you understand enums properly.

Previous Chapter – Chapter 2 – Declarations

Next Chapter – Chapter 4 - Access Modifiers

5 comments:

  1. Nice Article. Will you be covering all topics required to take up the SCJP Exam?

    ReplyDelete
  2. @ Ajay

    Yes I am in the process of doing so. All topics related to SCJP will be available soon, one by one. Thanks.

    ReplyDelete
  3. Great article;)
    All add-enum immediately became clear after this article.
    Thanks to the author, a good blog)
    ILLYA LISWAY , Ukraine

    ReplyDelete
  4. A very good blog.
    This article has given answers to all questions about enum.
    All articles are very clear and full.
    You need to write my book;)
    ILLYA LISWAY
    Ukraine

    ReplyDelete

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers