Java 1.5 Tiger A Developers Notebook [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Java 1.5 Tiger A Developers Notebook [Electronic resources] - نسخه متنی

David Flanagan, Brett McLaughlin

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








3.4 Switching on Enums


As you begin to integrate
enums into your own programs, one of the first
tasks you'll want to accomplish is using an enum with a switch statement.
This is a pretty obvious application; there's little value in using
enums if you can't easily react to the set of values available.


3.4.1 How do I do that?


Prior to Java 1.4, switch only worked with int, short, char, and byte values.
However, since enums have a finite set of values, Tiger adds switch
support for them. Here's an example of using an enum in a switch statement:


public void testSwitchStatement(PrintStream out) throws IOException {
StringBuffer outputText = new StringBuffer(student1.getFullName( ));
switch (student1.getGrade( )) {
case A:
outputText.append(" excelled with a grade of A");
break;
case B: // fall through to C
case C:
outputText.append(" passed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case D: // fall through to F
case F:
outputText.append(" failed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case INCOMPLETE:
outputText.append(" did not complete the class.");
break;
}
out.println(outputText.toString( ));
}

NOTE

This code assumes
that student1
has already been
created; this is
taken care of in
the test class,
"GradeTester".

The argument to switch must be an enumerated value; in this case, the
return type of getGrade( ) is Grade, which meets these requirements.
However, there is another requirement that makes this code a little odd--did you catch it? Note the format of each case clause:


case A:
case B:
case C:
case D:
case F:
case INCOMPLETE:

See anything missing? How about the enum class identifier:


case Grade.A:
case Grade.B:
case Grade.C:
case Grade.D:
case Grade.F:
case Grade.INCOMPLETE:

For those of you up on Tiger, this may make you think about the import
static
feature of the language, which I cover in Chapter 8. However, the
two have no relation (except perhaps on an implementation level)Tiger
simply requires that you not preface each enumerated type with the
enum class name. In fact, it's a compilation error if you do! Sort of a nice
convenience function, I think.

There's another issue you should be careful aboutnot handling every
enumerated type. In the following version of the switch, I've left out
handling of Grade.D:


switch (student1.getGrade( )) {
case A:
outputText.append(" excelled with a grade of A");
break;
case B: // fall through to C
case C:
outputText.append(" passed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case F:
outputText.append(" failed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case INCOMPLETE:
outputText.append(" did not complete the class.");
break;
}

NOTE

You can compile
the book's code
with warnings
turned on with
"ant checkcompile".
All
"-Xlint" warnings
will be displayed.

It's not completely clear as to if the compiler will be required to issue a
warning if all types aren't handled; however, it is clear that this is bad
coding. You need to be sure that every possible enumerated type is handled,
or get ready for some late-night debugging sessions.


As of this writing, the Tiger compiler did not issue a -Xlint warning in this situation.


3.4.2 What just happened?


The handling of an enumerated type by the compiler is a little different
than the handling of an integral type. That difference stems from enum
values not being compile-time constants; in other words, your code is not
turned into the following at compile-time:


switch (student1.getGrade( )) {
case 0:
outputText.append(" excelled with a grade of A");
break;
case 1: // fall through to C
case 2:
outputText.append(" passed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case 3: // fall through to F
case 4:
outputText.append(" failed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case 5:
outputText.append(" did not complete the class.");
break;
}

Instead, assuming that the enum and switch statement exist in the same
compilation unit, a jump table is
created, relating each enumerated type
to the value of ordinal( ), invoked on each type. That results in nearly
the same performance as the inlining shown above; the ordinal values
aren't inserted into the code, but the compiler can look them up in the
jump table extremely quickly. If the enum is changed and recompiled, the
jump table is updated, and there's no problem.

More often than not, though, the switch and enum are not in the same
compilation unit, and this is not possible. In these cases, most compilers
turn the switch statement into a series of if/else statements:


Grade tmp = student1.getGrade( );
if (tmp == Grade.A)
outputText.append(" excelled with a grade of A");
else if ((tmp == Grade.B) || (tmp == Grade.C))
outputText.append(" passed with a grade of ")
.append(student1.getGrade( ).toString( ));
else if ((tmp == Grade.D) || (tmp == Grade.F))
outputText.append(" failed with a grade of ")
.append(student1.getGrade( ).toString( ));
else if (tmp == Grade.INCOMPLETE)
outputText.append(" did not complete the class.");

This isn't efficient as a jump table, but this ensures that if the enum is
changed in one compilation unit, the switch statement (in a different
unit) continues to function properly. It also removes the need to worry
about reordering of an enum, which would affect a jump table.


3.4.3 What about...


...using the default keyword? It's perfectly legal, and in fact strongly
recommended. Since enum is a new type in Java, it would be easy for
someone to come along and add a new type to your enum without you
knowing about it:


public enum Grade { A, B, C, D, F, INCOMPLETE,
WITHDREW_PASSING, WITHDREW_FAILING };

NOTE

"default" is best
used in this way,
to catch
unexpected
valuesit's
generally good
programming
practice to specificallyhandle
every
known value,
though, as that
makes your code
much clearer.
"default" should
be for handling
unknown
conditions, and
not used as a
catch-all.

Now, your switch code will fail because it doesn't account for all the possible
Grade values. It's better to plan for this with a little more robust
code:


switch (student1.getGrade( )) {
case A:
outputText.append(" excelled with a grade of A");
break;
case B: // fall through to C
case C:
outputText.append(" passed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case D: // fall through to F
case F:
outputText.append(" failed with a grade of ")
.append(student1.getGrade( ).toString( ));
break;
case INCOMPLETE:
outputText.append(" did not complete the class.");
break;
default:
outputText.append(" has a grade of ")
.append(student1.getGrade( ).toString( ));
}

An even better idea would be to throw some sort of error on an unexpected
type--this will ensure that you and other programmers realize
that something is out of sync:


default: throw new AssertionError("Unexpected enumerated value!");


/ 131