The following post is a resumé of the best practices I understand when reading “Effective Java 3” of Joshua BLOCH.
Item 34 : Use Enums instead of int
constants
An enumerated type is a type whose legal values consist on fixed set of constants.
The following pattern (int enum pattern) has many shortcomings. It provides noting in the way of type safety and little in the way of expressive power.
private static final int APPLE = 755;
private static final int ORANGE = 673;
The enum
type helps to avoid those shortcomings. To associate data with enum
constants, declare instance fields and write a constructor that takes the data and stores it in the fields.
enum Fruits{
APPLE(755),ORANGE(673) ;
private final int value ;
Fruits(int value) {
this.value = value;
}
}
Enum properties
- Enum are classes that export one instance of each enumeration constant via
public static final
field, They are a generalization of singletons which are essentially single-element enums. - It is possible to use
==
operator to compareenum
. - Enum types with identically named constants coexist peacefully because each type has its own namespace
- You can add or reorder constants in
enum
without recompiling its client code - You can translate enums into printable strings by calling their
toString
method. - You can add arbitrary methods and fields and implements Interfaces
- Provides high quality implementation of
Object
method - Implements
Comparable
andSerializable
Constant specific method
Suppose you are writing an enum
that represents the basic operations :
public enum Operation {
PLUS, MINUS, TIMES, DIVIDE;
public double apply(double a, double b) {
switch (this) {
case PLUS:
return a + b;
case MINUS:
return a - b;
case TIMES:
return a * b;
case DIVIDE:
return a / b;
}
throw new AssertionError("Not handled operation : " + this);
}
}
The code works but it is not so pretty. Likely, there’s a better way to associate different behavior with each enum
constant.
public enum Operation {
PLUS {
@Override
public double apply(double a, double b) {
return a + b;
}
},
MINUS {
@Override
public double apply(double a, double b) {
return a - b;
}
},
TIMES {
@Override
public double apply(double a, double b) {
return a * b;
}
},
DIVIDE {
@Override
public double apply(double a, double b) {
return a / b;
}
};
public abstract double apply(double a, double b);
}
if you add a new behavior, you will provide an apply method.
An other solution is to use Function
.
public enum Operation {
PLUS ((a,b) -> a + b),
MINUS ((a,b) -> a - b),
TIMES ((a,b) -> a * b),
DIVIDE ((a,b) -> a / b);
private final BiFunction<Double,Double,Double> operation ;
Operation(BiFunction<Double,Double,Double> operation) {
this.operation = operation;
}
public Double compute(Double x, Double y) {
return operation.apply(x,y);
}
}
Enums are generally speaking, comparable in performance to int
constants. A minor performance disadvantage of enums is that there is a space and time cost to load and initialize enum types, but it is unlikely to be noticeable in practice.
Use enums any time you need a set of constants whose members are known at compile time.
Item 35 : Use instance fields instead of ordinals
Many enums are naturally associated with a single int
value. All enum
have ordinal
method which returns the numerical position of each enum constant in its type.
public enum Numbers {
ONE, TWO, THREE ;
public int value() {
// Do not do this !!
return ordinal() ;
}
}
Let’s print values
public static void main(String[] args) {
Arrays.stream(Numbers.values()).map(n -> n+" -> "+n.value()).forEach(System.out::println);
}
The output is
ONE -> 0
TWO -> 1
THREE -> 2
This enum works, but, it is a maintenance nightmare. If constants are reordered, the value
method will break. The solution is to store instance field.
public enum Numbers {
FOUR(4),ONE(1), TWO(2), THREE(3);
private final int value ;
Numbers(int value) {
this.value = value;
}
public int valueOrdinal() {
return ordinal() ;
}
public int valueStored() {
return value ;
}
}
The output :
Ordinal
FOUR -> 0
ONE -> 1
TWO -> 2
THREE -> 3
Stored
FOUR -> 4
ONE -> 1
TWO -> 2
THREE -> 3
Never derive a value associated with enum from its ordinal.
Item 36 : Use EnumSet
instead of bit fields
A bit field representation lets you use bitwise OR
operation to combine several constants into a set, know as bit field
.
public class Item36 {
private static final int UPPER_CASE = 1 << 0; // 1
private static final int LOWER_CASE = 1 << 1; // 2
private static final int TRIM = 1 << 2; // 4
public static String format(String value, int flags) {
String result = value;
if ((flags & UPPER_CASE) == UPPER_CASE) result = result.toUpperCase();
if ((flags & LOWER_CASE) == LOWER_CASE) result = result.toLowerCase();
if ((flags & TRIM) == TRIM) result = result.trim();
return result;
}
public static void main(String[] args) {
System.out.println(format("to upper case ", UPPER_CASE)+"--");
System.out.println(format("to upper case ", UPPER_CASE | TRIM)+"--");
System.out.println(format("to Lower case ", LOWER_CASE)+"--");
}
}
Output of this code
TO UPPER CASE --
TO UPPER CASE--
to lower case --
Use EnumSet
to efficiently represent set of values drawn from a single enum type. Here is how the previous example looks when modified to use enums and EnumSet
:
public class Item36 {
enum Operation {
UPPER_CASE,LOWER_CASE,TRIM;
}
public static String format(String value, Set<Operation> flags) {
String result = value;
if (flags.contains(Operation.UPPER_CASE)) result = result.toUpperCase();
if (flags.contains(Operation.LOWER_CASE)) result = result.toLowerCase();
if (flags.contains(Operation.TRIM)) result = result.trim();
return result;
}
public static void main(String[] args) {
System.out.println(format("to upper case ", EnumSet.of(Operation.UPPER_CASE))+"--");
System.out.println(format("to upper case ", EnumSet.of(Operation.UPPER_CASE,Operation.TRIM))+"--");
System.out.println(format("to Lower case ", EnumSet.of(Operation.LOWER_CASE))+"--");
}
}
Note that the method format
takes Set<Operation>
parameter rather than EnumSet
. The main
mehtod is calling format
with EnumSet.of(...)
.
The main disadvantage of EnumSet
is that it is not possible to create immutable EnumSet
. But, it will be remedied in an upcoming java release. Otherwise, you can wrap an EnumSet
in Collections.unmodifiableSet
.
Item 37 : Use EnumMap
instead of ordinal indexing
Given the following class :
enum Type {TUNISIAN, FRENCH}
static class Restaurant {
String name;
Type type;
Restaurant(String name, Type type) {
this.name = name;
this.type = type;
}
}
Suppose you have a List<Restaurant>
and you want to list restaurants organized by type. There is a very fast Map
implementation designed for use with enum keys which is EnumMap
.
List<Restaurant> restaurants = Lists.newArrayList(new Restaurant("A", Type.TUNISIAN), new Restaurant("B", Type.TUNISIAN), new Restaurant("C", Type.FRENCH));
// Use enum map
EnumMap<Type, Set<Restaurant>> restaurantByType = Maps.newEnumMap(Type.class);
// Fill all possible keys
for (Type type : Type.values()) restaurantByType.put(type, Sets.newHashSet());
// add objects
for (Restaurant restaurant : restaurants) restaurantByType.get(restaurant.type).add(restaurant);
The previous program can be further shortened by using stream
to manage the map :
Map<Type, List<Restaurant>> byType = restaurants.stream().collect(Collectors.groupingBy(Restaurant::getType));
In this case, the code chooses it own map implementation (class java.util.HashMap
in my case), and in practice, it won’t be an EnumMap
. to rectify this problem, use :
EnumMap<Type, Set<Restaurant>> collectToEnumMap = restaurants.stream().collect(Collectors.groupingBy(Restaurant::getType, () -> new EnumMap<>(Type.class), Collectors.toSet()));
Note that the EnumMap
makes a nested map for each Type
in the first case (restaurantByType
), while the stream-based versions only make a nested map of existing types.
Array of arrays
You may have seen an array indexed (twice!) by ordinals sued to represent a mapping from two enums.
Let’s take the example of transitions
Phase | SOLID | LIQUID |
---|---|---|
SOLID | null | MELT |
LIQUID | FREEZE | null |
An Array of arrays code will be like that :
public enum Phase {
SOLID, LIQUID ;
enum Transition {
MELT, FREEZE ;
private static final Transition[][] TRANSITIONS = {
{null,MELT},
{FREEZE,null}
} ;
public static final Transition from (Phase from, Phase to) {
return TRANSITIONS[from.ordinal()][to.ordinal()] ;
}
}
public static void main(String[] args) {
System.out.println(Transition.from(Phase.LIQUID,Phase.SOLID)); // prints FREEZE
System.out.println(Transition.from(Phase.SOLID,Phase.LIQUID)); // prints MELT
}
}
This program works, but the compiler have 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, your program will fail in runtime.
You can do much better with EnumMap
:
enum TransitionWithMap {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID);
static {
init() ;
}
private final Phase from;
private final Phase to;
private static EnumMap<Phase, EnumMap<Phase, TransitionWithMap>> transitions;
TransitionWithMap(Phase from, Phase to) {
this.from = from;
this.to = to;
}
public Phase getFrom() {
return from;
}
public Phase getTo() {
return to;
}
private static void init() {
transitions = Arrays.stream(TransitionWithMap.values()).collect(
groupingBy(
t -> t.from,
() -> new EnumMap<>(Phase.class),
toMap(t -> t.to, v -> v, (x, y) -> y, () -> new EnumMap<>(Phase.class)))
);
}
public static TransitionWithMap from(Phase from, Phase to) {
return transitions.get(from).get(to) ;
}
}
The code initialize EnumMap<Phase, EnumMap<Phase, TransitionWithMap>>
which contains transition by Phase by Phase.
Suppose now that you want to add a new Phase
to the system. Updating array-based program is risky. Ti update enum-based version, all you have to do is add elements in TransitionWithMap
, and the program takes care of everything.
Item 38 : Emulate extensible enums with interfaces
It is not possible to have on e enumerate type extend another. 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 Operation
in the following example :
interface Operation {
public Double compute(Double x, Double y);
}
public enum BasicOperation implements Operation {
// Some code ... see Item 34
public Double compute(Double x, Double y) {
return operation.apply(x,y);
}
}
While enum type BasicOperation
is not extensible, the interface type Operation
is, and it is the interface type that is used to represent operations in the APIs :
Operation plus = BasicOperation.PLUS ;
System.out.println(plus.compute(1d,2d));
Not only is it possible to pass a single instance of an extension enum anywhere a base enum is expected, but it is possible to pass in an entire extension enum type and use its elements in addition to or instead of those of the base type:
static <T extends Enum<T> & Operation> void genericCompute (Class<T> opEnumtype, double x, double y) {
for (Operation op : opEnumtype.getEnumConstants()) {
System.out.println(op + " : " + op.compute(x,y));
}
}
public static void main(String[] args) {
genericCompute(BasicOperation.class,1,2);
}
The output is
PLUS : 3.0
MINUS : -1.0
TIMES : 2.0
DIVIDE : 0.5
The T extends Enum<T> & Operation
ensures that the type T
represent both enum
and Operation
.
A second alternative is to use bounded wildcard type :
static void wildCardCompute (Collection<? extends Operation> ops, double x, double y) {
for (Operation op : ops) {
System.out.println(op + " : " + op.compute(x,y));
}
}
public static void main(String[] args) {
wildCardCompute(Lists.newArrayList(BasicOperation.values()),1,2);
}
A minor disadvantage of the use of interface to emulate extensible enums is that implementations cannot be inherited from one enum type to another.
Item 39 : Prefer annotations to naming patterns
Historically, it was common to use naming pattern to indicate that a some program elements demanded special treatment by a tool or a framework. For example, Junit 4 required test method beginning with test
.
this technique has many disadvantages :
- Typographical error result in silent failures
- No way to ensure that they are used only on appropriate program elements
- Do not provide a good way to associate parameter values with program elements
Annotations solve all this problems nicely. Generally, annotations don’t change the semantics of the annotated code that enable it for special treatment by tools such as this simple test runner :
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation{}
public class Item39 {
@MyAnnotation
static void methodWithAnnotation() {
System.out.println("With Annotation");
}
static void methodWithoutAnnotation() {
System.out.println("No Annotation");
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
for (Method m : Item39.class.getDeclaredMethods()) {
System.out.println(m.getName() + " " +m.isAnnotationPresent(MyAnnotation.class) );
if (m.isAnnotationPresent(MyAnnotation.class)) {
m.invoke(null) ;
}
}
}
}
the output
main false
methodWithoutAnnotation false
methodWithAnnotation true
With Annotation
The @Retention(RetentionPolicy.RUNTIME)
indicates that the annotation should be retained at runtime. The @Target(ElementType.METHOD)
indicates that the annotation is legal only on method declaration.
Annotations with parameter
Annotation type can have parameters :
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation{
Class<? extends Exception> value() ;
}
public class Item39 {
@MyAnnotation(ArithmeticException.class)
static void methodWithAnnotation() {
System.out.println("");
int a = 1/0 ;
}
@MyAnnotation(IllegalAccessException.class)
static void methodWithoutAnnotation() {
throw new ArrayIndexOutOfBoundsException("Error");
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
for (Method m : Item39.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(MyAnnotation.class)) {
Class<? extends Exception> expectedException = m.getAnnotation(MyAnnotation.class).value();
try {
m.invoke(null);
} catch (Throwable e) {
if (expectedException.isInstance(e.getCause())) {
System.out.println("Expected exception " + e.getCause().getClass());
} else {
System.out.println("Not expected exception " + e.getCause().getClass());
}
}
}
}
}
}
Muti valued annotations
It is possible to have annotation with an array of parameters
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation {
Class<? extends Exception>[] values();
}
Another way to do that is to annotate the annotation with @Repeatable
to indicate that the annotation my be applied repeatedly to a single element.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(AnnotationContainer.class)
@interface MyAnnotation {
Class<? extends Exception> value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface AnnotationContainer {
MyAnnotation[] value() ;
}
Item 40 : Consistently use the Override
annotation
The most important annotation of Java libraries is @Override
. If you consistently use this annotation, it will protect you from a large class of nefarious bugs.
Let’s take the following example :
public class Person {
private String name ;
public Person(String name) {
this.name = name;
}
public boolean equals(Person person) {
return Objects.equals(name, person.name);
}
public int hashCode() {
return Objects.hash(name);
}
public static void main(String[] args) {
HashSet<Person> people = Sets.newHashSet(new Person("A"), new Person("A"), new Person("A"));
System.out.println(people.size());
}
}
The program print 3
as size of the people set
. Why ?
It is because the equals
method of Object
has signature equals(Object o)
. In this case, adding @Override
annotation will help to detect this bug.
Therefore, you should use the operator @Override
annotation on every method declaration that you believe you override a superclass declaration.
If you enable the appropriate check, your IDE can generate a warning if you have a method that doesn’t have an @Override
annotation but does override a super class method.
Item 41 : Use marker interface to define types
A Marker interface is an interface that contains no method declaration but merely designates (or marks a class that implements the interface as having some property; For example Serializable
.
Marker interfaces have 2 advantages over marker annotation :
- Marker Interface define a type that is implemented by instances of the market class; marker annotations do not. You can then catch error at compile time.
- Marker Interface can be targeted more precisely
The chief advantage of marker annotations is that they are part of the larger annotation facility.
You must use an annotation if the marker applies to any program element other than a class or interface. If the marker applies only class or interfaces, ask yourself : “Might I want to write one or more methods that accept only objects that have this marking ?”. If so, you should use marker interface.