- “An enumerated type is a type whose legal values consist of a fixed set of constants.”
// The int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
- “This technique, known as the
int
enum pattern, has many shortcomings. It provides nothing in the way of type safety and little in the way of expressive power. The compiler won’t complain if you pass an apple to a method that expects an orange, compare apples to oranges with the == operator, or worse:”
// Tasty citrus flavored applesauce!
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;
- “Programs that use
int
enums are brittle. Becauseint
enums are constant variables [JLS, 4.12.4], theirint
values are compiled into the clients that use them [JLS, 13.1]. If the value associated with anint
enum is changed, its clients must be recompiled. If not, the clients will still run, but their behavior will be incorrect.” - “There is no easy way to translate
int
enum constants into printable strings. If you print such a constant or display it from a debugger, all you see is a number, which isn’t very helpful. There is no reliable way to iterate over all theint
enum constants in a group, or even to obtain the size of anint
enum group.” - “Luckily, Java provides an alternative that avoids all the shortcomings of the
int
andstring
enum patterns and provides many added benefits. It is the enum type [JLS, 8.9].”- “The basic idea behind Java’s enum types is simple: they are classes that export one instance for each enumeration constant via a public static final field. Enum types are effectively final, by virtue of having no accessible constructors.”
- “In other words, enum types are instance-controlled (page 6). They are a generalization of singletons (Item 3), which are essentially single-element enums.”
- “Enum types with identically named constants coexist peacefully because each type has its own namespace. You can add or reorder constants in an enum type without recompiling its clients because the fields that export the constants provide a layer of insulation between an enum type and its clients: constant values are not compiled into the clients as they are in the
int
enum patterns. Finally, you can translate enums into printable strings by calling theirtoString
method.” - “In addition to rectifying the deficiencies of
int
enums, enum types let you add arbitrary methods and fields and implement arbitrary interfaces. They provide high-quality implementations of all theObject
methods (Chapter 3), they implementComparable
(Item 14) andSerializable
(Chapter 12), and their serialized form is designed to withstand most changes to the enum type.”
// Enum type with data and behavior
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; }
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
-
“It is easy to write a rich enum type such as
Planet
. To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields.”- “Enums are by their nature immutable, so all fields should be final (Item 17).”
-
“Fields can be public, but it is better to make them private and provide public accessors (Item 16).”
-
“Sometimes you need to associate fundamentally different behavior with each constant.”
- “There is a better way to associate a different behavior with each enum constant: declare an abstract
apply
method in the enum type, and override it with a concrete method for each constant in a constant-specific class body. Such methods are known as constant-specific method implementations”
- “There is a better way to associate a different behavior with each enum constant: declare an abstract
// Enum type with constant-specific class bodies and data
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
- “Enum types have an automatically generated
valueOf(String)
method that translates a constant’s name into the constant itself. If you override thetoString
method in an enum type, consider writing afromString
method to translate the custom string representation back to the corresponding enum.”
// Implementing a fromString method on an enum type
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect(
toMap(Object::toString, e -> e));
// Returns Operation for string, if any
public static Optional<Operation> fromString(String symbol) {
return Optional.ofNullable(stringToEnum.get(symbol));
}
- “A disadvantage of constant-specific method implementations is that they make it harder to share code among enum constants. ”
// The strategy enum pattern
enum PayrollDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) { this.payType = payType; }
PayrollDay() { this(PayType.WEEKDAY); } // Default
int pay(int minutesWorked, int payRate) {
return payType.pay(minutesWorked, payRate);
}
// The strategy enum type
private enum PayType {
WEEKDAY {
int overtimePay(int minsWorked, int payRate) {
return minsWorked <= MINS_PER_SHIFT ? 0 :
(minsWorked - MINS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
int overtimePay(int minsWorked, int payRate) {
return minsWorked * payRate / 2;
}
};
abstract int overtimePay(int mins, int payRate);
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minsWorked, int payRate) {
int basePay = minsWorked * payRate;
return basePay + overtimePay(minsWorked, payRate);
}
}
}
- “Switches on enums are good for augmenting enum types with constant-specific behavior.”
- “You should also use this technique on enum types that are under your control if a method simply doesn’t belong in the enum type.”
- “So when should you use enums? Use enums any time you need a set of constants whose members are known at compile time.”
- “It is not necessary that the set of constants in an enum type stay fixed for all time. The enum feature was specifically designed to allow for binary compatible evolution of enum types.”
- “In summary, the advantages of enum types over
int
constants are compelling. Enums are more readable, safer, and more powerful. Many enums require no explicit constructors or members, but others benefit from associating data with each constant and providing methods whose behavior is affected by this data. Fewer enums benefit from associating multiple behaviors with a single method. In this relatively rare case, prefer constant-specific methods to enums that switch on their own values. Consider the strategy enum pattern if some, but not all, enum constants share common behaviors.”
- “Many enums are naturally associated with a single
int
value. All enums have anordinal
method, which returns the numerical position of each enum constant in its type. You may be tempted to derive an associatedint
value from the ordinal”
// Abuse of ordinal to derive an associated value - DON'T DO THIS
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET,
SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() { return ordinal() + 1; }
}
- “Luckily, there is a simple solution to these problems. Never derive a value associated with an enum from its ordinal; store it in an instance field instead.”
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) { this.numberOfMusicians = size; }
public int numberOfMusicians() { return numberOfMusicians; }
}
- “The
Enum
specification has this to say aboutordinal
: “Most programmers will have no use for this method. It is designed for use by general-purpose enum-based data structures such asEnumSet
andEnumMap
.” Unless you are writing code with this character, you are best off avoiding theordinal
method entirely.”
- “If the elements of an enumerated type are used primarily in sets, it is traditional to use the
int
enum pattern (Item 34), assigning a different power of 2 to each constant.”
// Bit field enumeration constants - OBSOLETE!
public class Text {
public static final int STYLE_BOLD = 1 << 0; // 1
public static final int STYLE_ITALIC = 1 << 1; // 2
public static final int STYLE_UNDERLINE = 1 << 2; // 4
public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
// Parameter is bitwise OR of zero or more STYLE_ constants
public void applyStyles(int styles) { ... }
}
- “This representation lets you use the bitwise
OR
operation to combine several constants into a set, known as a bit field.”
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
- “But bit fields have all the disadvantages of
int
enum constants and more.”- “It is even harder to interpret a bit field than a simple
int
enum constant when it is printed as a number. ” - “There is no easy way to iterate over all of the elements represented by a bit field.”
- “Finally, you have to predict the maximum number of bits you’ll ever need at the time you’re writing the API and choose a type for the bit field (typically
int
orlong
) accordingly. Once you’ve picked a type, you can’t exceed its width (32 or 64 bits) without changing the API.”
- “It is even harder to interpret a bit field than a simple
- “The
java.util
package provides theEnumSet
class to efficiently represent sets of values drawn from a single enum type.”- “But internally, each
EnumSet
is represented as a bit vector.”
- “But internally, each
// EnumSet - a modern replacement for bit fields
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}
- “The
EnumSet
class provides a rich set of static factories for easy set creation.”
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
-
“Note that the
applyStyles
method takes aSet<Style>
rather than anEnumSet<Style>
. While it seems likely that all clients would pass anEnumSet
to the method, it is generally good practice to accept the interface type rather than the implementation type (Item 64). This allows for the possibility of an unusual client to pass in some otherSet
implementation.” -
“In summary, just because an enumerated type will be used in sets, there is no reason to represent it with bit fields.”
-
“The
EnumSet
class combines the conciseness and performance of bit fields with all the many advantages of enum types described in Item 34. The one real disadvantage ofEnumSet
is that it is not, as of Java 9, possible to create an immutableEnumSet
, but this will likely be remedied in an upcoming release. In the meantime, you can wrap anEnumSet
withCollections.unmodifiableSet
, but conciseness and performance will suffer.”
class Plant {
enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
final String name;
final LifeCycle lifeCycle;
Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override public String toString() {
return name;
}
}
- “Now suppose you have an array of plants representing a garden, and you want to list these plants organized by life cycle (annual, perennial, or biennial).”
- “To do this, you construct three sets, one for each life cycle, and iterate through the garden, placing each plant in the appropriate set.”
// Using ordinal() to index into an array - DON'T DO THIS!
Set<Plant>[] plantsByLifeCycle =
(Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycle.length; i++)
plantsByLifeCycle[i] = new HashSet<>();
for (Plant p : garden)
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
// Print the results
for (int i = 0; i < plantsByLifeCycle.length; i++) {
System.out.printf("%s: %s%n",
Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}
- “This technique works, but it is fraught with problems. ”
- “Because arrays are not compatible with generics (Item 28), the program requires an unchecked cast and will not compile cleanly.”
- “Because the array does not know what its index represents, you have to label the output manually.”
- “But the most serious problem with this technique is that when you access an array that is indexed by an enum’s ordinal, it is your responsibility to use the correct
int
value;ints
do not provide the type safety of enums. If you use the wrong value, the program will silently do the wrong thing or—if you’re lucky—throw anArrayIndexOutOfBoundsException
.”
- “There is a much better way to achieve the same effect. The array is effectively serving as a map from the enum to a value, so you might as well use a
Map
. More specifically, there is a very fastMap
implementation designed for use with enum keys, known asjava.util.EnumMap
.”
// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =
new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values())
plantsByLifeCycle.put(lc, new HashSet<>());
for (Plant p : garden)
plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);
- “Note that the
EnumMap
constructor takes theClass
object of the key type: this is a bounded type token, which provides runtime generic type information (Item 33).”
// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden)
.collect(groupingBy(p -> p.lifeCycle,
() -> new EnumMap<>(LifeCycle.class), toSet())));
- “You may see an array of arrays indexed (twice!) by ordinals used to represent a mapping from two enum values.”
// Using ordinal() to index array of arrays - DON'T DO THIS!
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
// Rows indexed by from-ordinal, cols by to-ordinal
private static final Transition[][] TRANSITIONS = {
{ null, MELT, SUBLIME },
{ FREEZE, null, BOIL },
{ DEPOSIT, CONDENSE, null }
};
// Returns the phase transition from one phase to another
public static Transition from(Phase from, Phase to) {
return TRANSITIONS[from.ordinal()][to.ordinal()];
}
}
}
- “This program works and may even appear elegant, but appearances can be deceiving.”
- “the compiler has no way of knowing the relationship between ordinals and array indices.”
- “If you make a mistake in the transition table or forget to update it when you modify the
Phase
orPhase.Transition
enum type, your program will fail at runtime. The failure may be anArrayIndexOutOfBoundsException
, aNullPointerException
, or (worse) silent erroneous behavior.” - “And the size of the table is quadratic in the number of phases, even if the number of non-null entries is smaller.”
- “Again, you can do much better with
EnumMap
. Because each phase transition is indexed by a pair of phase enums, you are best off representing the relationship as a map from one enum (the “from” phase) to a map from the second enum (the “to” phase) to the result (the phase transition).”
// Using a nested EnumMap to associate data with enum pairs
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
private final Phase from;
private final Phase to;
Transition(Phase from, Phase to) {
this.from = from;
this.to = to;
}
// Initialize the phase transition map
private static final Map<Phase, Map<Phase, Transition>>
m = Stream.of(values()).collect(groupingBy(t -> t.from,
() -> new EnumMap<>(Phase.class),
toMap(t -> t.to, t -> t,
(x, y) -> y, () -> new EnumMap<>(Phase.class))));
public static Transition from(Phase from, Phase to) {
return m.get(from).get(to);
}
}
}
- “In summary, it is rarely appropriate to use ordinals to index into arrays: use
EnumMap
instead. If the relationship you are representing is multidimensional, useEnumMap<..., EnumMap<...>>
. This is a special case of the general principle that application programmers should rarely, if ever, use Enum.ordinal (Item 35).”
- “For the most part, extensibility of enums turns out to be a bad idea. It is confusing that elements of an extension type are instances of the base type and not vice versa. There is no good way to enumerate over all of the elements of a base type and its extensions. Finally, extensibility would complicate many aspects of the design and implementation.”
- “That said, there is at least one compelling use case for extensible enumerated types, which is operation codes, also known as opcodes.”
- “An opcode is an enumerated type whose elements represent operations on some machine, such as the
Operation
type in Item 34, which represents the functions on a simple calculator.” - “Sometimes it is desirable to let the users of an API provide their own operations, effectively extending the set of operations provided by the API.”
- “An opcode is an enumerated type whose elements represent operations on some machine, such as the
// Emulated extensible enum using an interface
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
- “While the enum type (
BasicOperation
) is not extensible, the interface type (Operation
) is, and it is the interface type that is used to represent operations in APIs.” - “You can define another enum type that implements this interface and use instances of this new type in place of the base type.”
// Emulated extension enum
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
public double apply(double x, double y) {
return x % y;
}
};
private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
- “A minor disadvantage of the use of interfaces to emulate extensible enums is that implementations cannot be inherited from one enum type to another.”
- “If the implementation code does not rely on any state, it can be placed in the interface, using default implementations (Item 20).”
- “If there were a larger amount of shared functionality, you could encapsulate it in a helper class or a static helper method to eliminate the code duplication.”
- “In summary, while you cannot write an extensible enum type, you can emulate it by writing an interface to accompany a basic enum type that implements the interface.”
- “This allows clients to write their own enums (or other types) that implement the interface. Instances of these types can then be used wherever instances of the basic enum type can be used, assuming APIs are written in terms of the interface.”
- “Historically, it was common to use naming patterns to indicate that some program elements demanded special treatment by a tool or framework.”
- “For example, prior to release 4, the JUnit testing framework required its users to designate test methods by beginning their names with the characters
test
[Beck04]”
- “For example, prior to release 4, the JUnit testing framework required its users to designate test methods by beginning their names with the characters
- “This technique works, but it has several big disadvantages.”
- “First, typographical errors result in silent failures.”
- “A second disadvantage of naming patterns is that there is no way to ensure that they are used only on appropriate program elements.”
- “A third disadvantage of naming patterns is that they provide no good way to associate parameter values with program elements.”
- “Annotations [JLS, 9.7] solve all of these problems nicely, and JUnit adopted them starting with release 4.”
- “In this item, we’ll write our own toy testing framework to show how annotations work. Suppose you want to define an annotation type to designate simple tests that are run automatically and fail if they throw an exception.”
- “The
@Retention(RetentionPolicy.RUNTIME)
meta-annotation indicates thatTest
annotations should be retained at runtime. Without it,Test
annotations would be invisible to the test tool.” - “The
@Target.get(ElementType.METHOD)
meta-annotation indicates that theTest
annotation is legal only on method declarations: it cannot be applied to class declarations, field declarations, or other program elements.” - “he comment before the
Test
annotation declaration says, “Use only on parameterless static methods.” It would be nice if the compiler could enforce this, but it can’t, unless you write an annotation processor to do so.”
- “The
// Marker annotation type declaration
import java.lang.annotation.*;
/**
* Indicates that the annotated method is a test method.
* Use only on parameterless static methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
- “Here is how the
Test
annotation looks in practice. It is called a marker annotation because it has no parameters but simply “marks” the annotated element.”
// Program containing marker annotations
public class Sample {
@Test public static void m1() { } // Test should pass
public static void m2() { }
@Test public static void m3() { // Test should fail
throw new RuntimeException("Boom");
}
public static void m4() { }
@Test public void m5() { } // INVALID USE: nonstatic method
public static void m6() { }
@Test public static void m7() { // Test should fail
throw new RuntimeException("Crash");
}
public static void m8() { }
}
- “They serve only to provide information for use by interested programs. More generally, annotations don’t change the semantics of the annotated code but enable it for special treatment by tools such as this simple test runner.”
// Program to process marker annotations
import java.lang.reflect.*;
public class RunTests {
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
m.invoke(null);
passed++;
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
System.out.println(m + " failed: " + exc);
} catch (Exception exc) {
System.out.println("Invalid @Test: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n",
passed, tests - passed);
}
}
- “Now let’s add support for tests that succeed only if they throw a particular exception.”
// Annotation type with a parameter
import java.lang.annotation.*;
/**
* Indicates that the annotated method is a test method that
* must throw the designated exception to succeed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
- “Note that class literals are used as the values for the annotation parameter:”
// Program containing annotations with a parameter
public class Sample2 {
@ExceptionTest(ArithmeticException.class)
public static void m1() { // Test should pass
int i = 0;
i = i / i;
}
@ExceptionTest(ArithmeticException.class)
public static void m2() { // Should fail (wrong exception)
int[] a = new int[0];
int i = a[1];
}
@ExceptionTest(ArithmeticException.class)
public static void m3() { } // Should fail (no exception)
}
- “Now let’s modify the test runner tool to process the new annotation.”
if (m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (InvocationTargetException wrappedEx) {
Throwable exc = wrappedEx.getCause();
Class<? extends Throwable> excType =
m.getAnnotation(ExceptionTest.class).value();
if (excType.isInstance(exc)) {
passed++;
} else {
System.out.printf(
"Test %s failed: expected %s, got %s%n",
m, excType.getName(), exc);
}
} catch (Exception exc) {
System.out.println("Invalid @Test: " + m);
}
}
- “Taking our exception testing example one step further, it is possible to envision a test that passes if it throws any one of several specified exceptions. The annotation mechanism has a facility that makes it easy to support this usage. Suppose we change the parameter type of the
ExceptionTest
annotation to be an array ofClass
objects:”
// Annotation type with an array parameter
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Exception>[] value();
}
- “To specify a multiple-element array, surround the elements with curly braces and separate them with commas:”
// Code containing an annotation with an array parameter
@ExceptionTest({ IndexOutOfBoundsException.class,
NullPointerException.class })
public static void doublyBad() {
List<String> list = new ArrayList<>();
// The spec permits this method to throw either
// IndexOutOfBoundsException or NullPointerException
list.addAll(5, null);
}
- “As of Java 8, there is another way to do multivalued annotations. Instead of declaring an annotation type with an array parameter, you can annotate the declaration of an annotation with the
@Repeatable
meta-annotation, to indicate that the annotation may be applied repeatedly to a single element. This meta-annotation takes a single parameter, which is the class object of a containing annotation type, whose sole parameter is an array of the annotation type [JLS, 9.6.3].”
// Repeatable annotation type
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
Class<? extends Exception> value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
ExceptionTest[] value();
}
// Code containing a repeated annotation
@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doublyBad() { ... }
- “To detect repeated and non-repeated annotations with
isAnnotationPresent
, you much check for both the annotation type and its containing annotation type.”
// Processing repeatable annotations
if (m.isAnnotationPresent(ExceptionTest.class)
|| m.isAnnotationPresent(ExceptionTestContainer.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (Throwable wrappedExc) {
Throwable exc = wrappedExc.getCause();
int oldPassed = passed;
ExceptionTest[] excTests =
m.getAnnotationsByType(ExceptionTest.class);
for (ExceptionTest excTest : excTests) {
if (excTest.value().isInstance(exc)) {
passed++;
break;
}
}
if (passed == oldPassed)
System.out.printf("Test %s failed: %s %n", m, exc);
}
}
- “If you write a tool that requires programmers to add information to source code, define appropriate annotation types. There is simply no reason to use naming patterns when you can use annotations instead.”
- “That said, with the exception of toolsmiths, most programmers will have no need to define annotation types. But all programmers should use the predefined annotation types that Java provides (Items 40, 27).”
- “For the typical programmer, the most important of these is
@Override
. This annotation can be used only on method declarations, and it indicates that the annotated method declaration overrides a declaration in a supertype. If you consistently use this annotation, it will protect you from a large class of nefarious bugs.” - “Use the
Override
annotation on every method declaration that you believe to override a superclass declaration.” - “The
Override
annotation may be used on method declarations that override declarations from interfaces as well as classes.”- “With the advent of default methods, it is good practice to use
Override
on concrete implementations of interface methods to ensure that the signature is correct.” - “If you know that an interface does not have default methods, you may choose to omit
Override
annotations on concrete implementations of interface methods to reduce clutter.”
- “With the advent of default methods, it is good practice to use
- “In an abstract class or an interface, however, it is worth annotating all methods that you believe to override superclass or superinterface methods, whether concrete or abstract.”
- “In summary, the compiler can protect you from a great many errors if you use the
Override
annotation on every method declaration that you believe to override a supertype declaration, with one exception. In concrete classes, you need not annotate methods that you believe to override abstract method declarations (though it is not harmful to do so).”
-
“A marker interface is an interface that contains no method declarations but merely designates (or “marks”) a class that implements the interface as having some property.”
- “For example, consider the
Serializable
interface (Chapter 12). By implementing this interface, a class indicates that its instances can be written to anObjectOutputStream
(or “serialized”).”
- “For example, consider the
-
“Marker interfaces have two advantages over marker annotations.”
-
“First and foremost, marker interfaces define a type that is implemented by instances of the marked class; marker annotations do not.”
-
“The existence of a marker interface type allows you to catch errors at compile time that you couldn’t catch until runtime if you used a marker annotation.”
“Compile-time error detection is the intent of marker interfaces, but unfortunately, the
ObjectOutputStream.write
API does not take advantage of the Serializable interface: its argument is declared to be of typeObject
, so attempts to serialize an unserializable object won’t fail until runtime.”
-
-
“Another advantage of marker interfaces over marker annotations is that they can be targeted more precisely.”
-
-
“The chief advantage of marker annotations over marker interfaces is that they are part of the larger annotation facility. Therefore, marker annotations allow for consistency in annotation-based frameworks.”
-
“So when should you use a marker annotation and when should you use a marker interface? Clearly you must use an annotation if the marker applies to any program element other than a class or interface, because only classes and interfaces can be made to implement or extend an interface.”
-
“If the marker applies only to classes and interfaces, ask yourself the question “Might I want to write one or more methods that accept only objects that have this marking?” If so, you should use a marker interface in preference to an annotation.”
-
“If, additionally, the marking is part of a framework that makes heavy use of annotations, then a marker annotation is the clear choice.”
-
“If you find yourself writing a marker annotation type whose target is
ElementType.TYPE
, take the time to figure out whether it really should be an annotation type or whether a marker interface would be more appropriate.”