effective javatm still effective, after all these years...
TRANSCRIPT
Effective Java: Still Effective After All These Years 1
Effective JavaTM : Still Effective, After All These Years
(+ Appetizers and Dessert)
Joshua Bloch
Java Puzzlers: The Never-Ending Saga 2
Appetizers
Java Puzzlers: The Never-Ending Saga 3
Fraser 1908
Java Puzzlers: The Never-Ending Saga 4
Fraser 1908
Java Puzzlers: The Never-Ending Saga 5
Fraser 1908
Java Puzzlers: The Never-Ending Saga 6
Todorovic 1997
Java Puzzlers: The Never-Ending Saga 7
Todorovic 1997
Java Puzzlers: The Never-Ending Saga 8
Todorovic 1997
Java Puzzlers: The Never-Ending Saga 9
1. “Life’s Persistent Questions”
public class SimpleQuestion { static boolean yesOrNo(String s) { s = s.toLowerCase(); if (s.equals("yes") || s.equals("y") || s.equals
("t")) s = "true"; return Boolean.getBoolean(s); }
public static void main(String[] args) { System.out.println( yesOrNo("true") + " " + yesOrNo("YeS")); } }
Java Puzzlers: The Never-Ending Saga 10
What Does It Print?
public class SimpleQuestion { static boolean yesOrNo(String s) { s = s.toLowerCase(); if (s.equals("yes") || s.equals("y") || s.equals
("t")) s = "true"; return Boolean.getBoolean(s); }
public static void main(String[] args) { System.out.println( yesOrNo("true") + " " + yesOrNo("YeS")); } }
(a) false false (b) true false (c) true true (d) None of the above
Java Puzzlers: The Never-Ending Saga 11
(a) false false (b) true false (c) true true (d) None of the above
The Boolean.getBoolean method does not do what you think it does
What Does It Print?
Java Puzzlers: The Never-Ending Saga 12
What does Boolean.getBoolean do?
public static boolean getBoolean(String name)
Returns true if and only if the system property named by the argument exists and is equal to the string "true". (Beginning with version 1.0.2 of the Java platform, the test of this string is case insensitive.) A system property is accessible through getProperty, a method defined by the System class.
If there is no property with the specified name, or if the specified name is empty or null, then false is returned.
Java Puzzlers: The Never-Ending Saga 13
Another Look
public class SimpleQuestion { static boolean yesOrNo(String s) { s = s.toLowerCase(); if (s.equals("yes") || s.equals("y") || s.equals
("t")) s = "true"; return Boolean.getBoolean(s); // Ouch! }
public static void main(String[] args) { System.out.println( yesOrNo("true") + " " + yesOrNo("YeS")); } }
Java Puzzlers: The Never-Ending Saga 14
You Could Fix it Like This...
public class SimpleQuestion { static boolean yesOrNo(String s) { s = s.toLowerCase(); if (s.equals("yes") || s.equals("y") || s.equals
("t")) s = "true"; return Boolean.parseBoolean(s); }
public static void main(String[] args) { System.out.println( yesOrNo("true") + " " + yesOrNo("YeS")); } }
Java Puzzlers: The Never-Ending Saga 15
But This is Even Better...
public class SimpleQuestion { static boolean yesOrNo(String s) { s = s.toLowerCase(); return s.equals("yes") || s.equals("y") || s.equals("true")|| s.equals("t"); }
public static void main(String[] args) { System.out.println( yesOrNo("true") + " " + yesOrNo("YeS")); } }
Java Puzzlers: The Never-Ending Saga 16
The Moral
• Strange and terrible methods lurk in libraries ─ Some have innocuous sounding names
• If your code misbehaves ─ Make sure you’re calling the right methods ─ Read the library documentation
• For API designers ─ Don’t violate principle of least astonishment ─ Don’t violate the abstraction hierarchy ─ Don’t use similar names for wildly different behaviors
Java Puzzlers: The Never-Ending Saga 17
public class Searching { public static void main(String[] args) { String[] strings = { "0", "1", "2", "3", "4", "5" };
// Translate String array into List of Integer List<Integer> integers = new ArrayList<Integer>(); for (String s : strings) integers.add(Integer.valueOf(s));
System.out.println( Collections.binarySearch(integers, 1, cmp)); }
static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer i, Integer j) { return i < j ? -1 : (i == j ? 0 : 1); } }; }
2. “Searching for the One”
Java Puzzlers: The Never-Ending Saga 18
public class Searching { public static void main(String[] args) { String[] strings = { "0", "1", "2", "3", "4", "5" };
// Translate String array into List of Integer List<Integer> integers = new ArrayList<Integer>(); for (String s : strings) integers.add(Integer.valueOf(s));
System.out.println( Collections.binarySearch(integers, 1, cmp)); }
static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer i, Integer j) { return i < j ? -1 : (i == j ? 0 : 1); } }; }
What Does It Print?
(a) 0 (b) 1 (c) -2 (d) None of the above
Java Puzzlers: The Never-Ending Saga 19
(a) 0 (b) 1 (c) -2 (in practice) (d) None of the above: unspecified (in theory)
The Comparator is broken; autoboxing is tricky.
What Does It Print?
Java Puzzlers: The Never-Ending Saga 20
public class Searching { public static void main(String[] args) { String[] strings = { "0", "1", "2", "3", "4", "5" };
// Translate String array into List of Integer List<Integer> integers = new ArrayList<Integer>(); for (String s : strings) integers.add(Integer.valueOf(s));
System.out.println( Collections.binarySearch(integers, 1, cmp)); }
static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer i, Integer j) { return i < j ? -1 : (i == j ? 0 : 1); } }; }
Another Look
Java Puzzlers: The Never-Ending Saga 21
static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer i, Integer j) { return i < j ? -1 : (i > j ? 1 : 0) ; } };
static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer i, Integer j) { return i < j ? -1 : (i.equals(j) ? 0 : 1); } };
You Could Fix it in Either of These Ways
Java Puzzlers: The Never-Ending Saga 22
static Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer iBoxed, Integer jBoxed) { // Unbox arguments to force value comparison int i = iBoxed; int j = jBoxed; return i < j ? -1 : (i == j ? 0 : 1); } };
But This is (Arguably) Better
Java Puzzlers: The Never-Ending Saga 23
The Moral
• Autoboxing blurs but does not erase distinction between primitives and boxed primitives
• Only four of the six comparison operators work on boxed primitives ─ <, >, <=, and >= work ─ == and != do not work!
• It’s very hard to test for broken comparators • Even Josh Bloch and Neal Gafter make misteaks ─ We got this wrong in the solution to Puzzle 65 (Java
Puzzlers, Addison-Wesley, 2005, 1st Printing)
Java Puzzlers: The Never-Ending Saga 24
The Main Course
Java Puzzlers: The Never-Ending Saga 25
First Edition, 2001; Second Edition, 2008 What’s New in the Second Edition?
• Chapter 5: Generics • Chapter 6: Enums and Annotations • One or more items on all other Java 5 language features • Threads chapter renamed Concurrency ─ Completely rewritten for java.util.concurrent
• All existing items updated to reflect current best practices • A few items added to reflect newly important patterns • First edition had 57 items; second has 78
Java Puzzlers: The Never-Ending Saga 26
Agenda
• Generics Items 28, 29 • Enum types Item 40 • Varargs Item 42 • Concurrency Item 69 • Serialization Item 78
Java Puzzlers: The Never-Ending Saga 27
Item 28: Wildcards for API Flexibility
• Unlike arrays, generic types are invariant ─ That is, List<String> is not a subtype of List<Object> ─ Good for compile-time type safety, but inflexible
• Wildcard types provide additional API flexibility ─ List<String> is a subtype of List<? extends Object> ─ List<Object> is a subtype of List<? super String>
Java Puzzlers: The Never-Ending Saga 28
A Mnemonic for Wildcard Usage
• PECS—Producer extends, Consumer super ─ For a T producer, use Foo<? extends T> ─ For a T consumer, use Foo<? super T>
• Only applies to input parameters ─ Don’t use wildcard types as return types
Guess who?
Java Puzzlers: The Never-Ending Saga 29
Flex your PECS (1)
• Suppose you want to add bulk methods to Stack<E> void pushAll(Collection<E> src);
void popAll(Collection<E> dst);
Java Puzzlers: The Never-Ending Saga 30
Flex your PECS (1)
• Suppose you want to add bulk methods to Stack<E> void pushAll(Collection<? extends E> src);
– src is an E producer void popAll(Collection<E> dst);
Java Puzzlers: The Never-Ending Saga 31
Flex your PECS (1)
• Suppose you want to add bulk methods to Stack<E> void pushAll(Collection<? extends E> src);
– src is an E producer void popAll(Collection<? super E > dst);
– dst is an E consumer
Java Puzzlers: The Never-Ending Saga 32
Flex your PECS (1) What does it buy you?
void pushAll(Collection<? extends E> src);
void popAll(Collection<? super E> dst);
• Caller can now pushAll from a Collection<Long> or a Collection<Number> onto a Stack<Number>
• Caller can now popAll into a Collection<Object> or a Collection<Number> from a Stack<Number>
Java Puzzlers: The Never-Ending Saga 33
Flex your PECS (2)
• Consider this generic method: public static <E> Set<E> union(Set<E> s1, Set<E> s2)
Java Puzzlers: The Never-Ending Saga 34
Flex your PECS (2)
• Consider this generic method public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
• Both s1 and s2 are E producers • No wildcard type for return value
─ Wouldn’t make the API any more flexible ─ Would force user to deal with wildcard types explicitly ─ User should not have to think about wildcards to use your API
Java Puzzlers: The Never-Ending Saga 35
Flex your PECS (2) Truth In Advertising – It Doesn’t Always “Just Work”
• This code won’t compile Set<Integer> ints = ... ; Set<Double> doubles = ... ; Set<Number> numbers = union(ints, doubles);
• The compiler says Union.java:14: incompatible types found : Set<Number & Comparable<? extends Number & Comparable<?>>> required: Set<Number> Set<Number> numbers = union(integers, doubles); ^
• The fix – provide an explicit type parameter Set<Number> nums = Union.<Number>union(ints, doubles);
Java Puzzlers: The Never-Ending Saga 36
Summary, in Tabular Form
Input Parameter Produces T Instances?
Param
eter C
onsumes T
Instances?
Yes No
Yes Foo<T>
(Invariant in T) Foo<? super T>
(Contravariant in T)
No Foo<? extends T>
(Covariant in T) Foo<?>
(Independent of T)
Java Puzzlers: The Never-Ending Saga 37
Filling in The Blanks
Parameter Produces T Instances?
Param
eter C
onsumes T
Instances?
Yes No
Yes Foo<T>
(Invariant in T) Foo<? super T>
(Contravariant in T)
No Foo<? extends T>
(Covariant in T) Foo<?>
(Independent of T)
Java Puzzlers: The Never-Ending Saga 38
Item 29: How to Write A Container With an Arbitrary Number of Type Parameters
• Typically, containers are parameterized ─ For example: Set<E>, Map<K, V> ─ Limits you to a fixed number of type parameters
• Sometimes you need more flexibility ─ Consider a DatabaseRow class ─ You need one type parameter for each column ─ Number of columns varies from instance to instance
Java Puzzlers: The Never-Ending Saga 39
The Solution: Typesafe Heterogeneous Container Pattern
• Parameterize selector instead of container ─ For DatabaseRow, DatabaseColumn is selector
• Present selector to container to get data • Data is strongly typed at compile time • Allows for unlimited type parameters
Java Puzzlers: The Never-Ending Saga 40
Example: A Favorites Database API and Client
// Typesafe heterogeneous container pattern - API public class Favorites { public <T> void putFavorite(Class<T> type, T instance); public <T> T getFavorite(Class<T> type); }
// Typesafe heterogeneous container pattern - client public static void main(String[] args) { Favorites f = new Favorites(); f.putFavorite(String.class, "Java"); f.putFavorite(Integer.class, 0xcafebabe); f.putFavorite(Class.class, ThreadLocal.class);
String s = f.getFavorite(String.class); int i = f.getFavorite(Integer.class); Class<?> favoriteClass = f.getFavorite(Class.class); System.out.println("printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass); }
Java Puzzlers: The Never-Ending Saga 41
Example: A Favorites Database Implementation
public class Favorites { private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type, T instance) { if (type == null) throw new NullPointerException("Type is null"); favorites.put(type, instance); }
public <T> T getFavorite(Class<T> type) { return type.cast(favorites.get(type)); } }
Java Puzzlers: The Never-Ending Saga 42
Agenda
• Generics Items 28, 29 • Enum types Item 40 • Varargs Item 42 • Concurrency Item 69 • Serialization Item 78
Java Puzzlers: The Never-Ending Saga 43
Item 40: Prefer 2-element enums to booleans
• Which would you rather see in code, this: double temp = thermometer.getTemp(true);
• or this: double temp = thermometer.getTemp(TemperatureScale.FAHRENHEIT);
• With static import, you can even have this: double temp = thermometer.getTemp(FAHRENHEIT);
Java Puzzlers: The Never-Ending Saga 44
Advantages of 2-Element enums Over booleans
• Code is easier to read • Code is easier to write (especially with IDE) • Less need to consult documentation • Smaller probability of error • Much better for API evolution
Java Puzzlers: The Never-Ending Saga 45
Evolution of a 2-Element enum
• Version 1 public enum TemperatureScale { FAHRENHEIT, CELSIUS }
• Version 2 public enum TemperatureScale { FAHRENHEIT, CELSIUS, KELVIN }
• Version 3 public enum TemperatureScale { FAHRENHEIT, CELSIUS, KELVIN; public abstract double toCelsius(double temp);
... // Implementations of toCelsius omitted }
Java Puzzlers: The Never-Ending Saga 46
Agenda
• Generics Items 28, 29 • Enum types Item 40 • Varargs Item 42 • Concurrency Item 69 • Serialization Item 78
Java Puzzlers: The Never-Ending Saga 47
Item 42: Two Useful Idioms for Varargs
// Simple use of varargs static int sum(int... args) { int sum = 0; for (int arg : args) sum += arg; return sum; }
Java Puzzlers: The Never-Ending Saga 48
Suppose You Want to Require at Least One Argument
// The WRONG way to require one or more arguments! static int min(int... args) { if (args.length == 0) throw new IllegalArgumentException( "Too few arguments"); int min = args[0]; for (int i = 1; i < args.length; i++) if (args[i] < min) min = args[i]; return min; }
Fails at runtime if invoked with no arguments It's ugly – explicit validity check on number of args Interacts poorly with for-each loop
Java Puzzlers: The Never-Ending Saga 49
The Right Way
static int min(int firstArg, int... remainingArgs) { int min = firstArg; for (int arg : remainingArgs) if (arg < min) min = arg; return min; }
Won’t compile if you try to invoke with no arguments No validity check necessary Works great with for-each loop
Java Puzzlers: The Never-Ending Saga 50
Varargs when Performance is Critical
// These static factories are real Class EnumSet<E extends Enum<E>> { static <E> EnumSet<E> of(E e); static <E> EnumSet<E> of(E e1, E e2) static <E> EnumSet<E> of(E e1, E e2, E e3) static <E> EnumSet<E> of(E e1, E e2, E e3, E e4) static <E> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5); static <E> EnumSet<E> of(E first, E... rest) ... // Remainder omitted }
Avoids cost of array allocation if fewer that n args
Java Puzzlers: The Never-Ending Saga 51
Agenda
• Generics Items 28, 29 • Enum types Item 40 • Varargs Item 42 • Concurrency Item 69 • Serialization Item 78
Java Puzzlers: The Never-Ending Saga 52
Item 69: Use ConcurrentHashMap But Use it Right!
• Concurrent collections manage synchronization internally ─ Lock striping, non-blocking algorithms, etc.
• Combines high concurrency and performance • Synchronized collections nearly obsolete • Use ConcurrentHashMap, not Collections.synchronizedMap()
Java Puzzlers: The Never-Ending Saga 53
With Concurrent Collections, You Can't Combine Operations Atomically
private static final ConcurrentMap<String, String> map = new ConcurrentHashMap<String, String>();
// Interning map atop ConcurrentMap -- BROKEN! public static String intern(String s) { synchronized(map) { // ALWAYS wrong! String result = map.get(s); if (result == null) { map.put(s, s); result = s; } return result; } }
Java Puzzlers: The Never-Ending Saga 54
You Could Fix it Like This...
// Interning map atop ConcurrentMap - works, but slow! public static String intern(String s) { String previousValue = map.putIfAbsent(s, s); return previousValue == null ? s : previousValue; }
Calls putIfAbsent every time it reads a value Unfortunately, this usage is very common
Java Puzzlers: The Never-Ending Saga 55
But This is Much Butter
// Interning map atop ConcurrentMap - the right way! public static String intern(String s) { String result = map.get(s); if (result == null) { result = map.putIfAbsent(s, s); if (result == null) result = s; } return result; }
Calls putIfAbsent only if map doesn't contain entry 250% faster on my machine, and far less contention
Java Puzzlers: The Never-Ending Saga 56
One More “Solution” That Doesn’t Work
// Interning map atop ConcurrentMap - SLOW AND BROKEN public static String intern(String s) { map.putIfAbsent(s, s); // Ignores return value return s; // Fails if map already contained string! }
This bug is surprisingly common! We found 15% of putIfAbsent uses ignore result
Java Puzzlers: The Never-Ending Saga 57
Summary
• Synchronized collections are largely obsolete • Use ConcurrentHashMap and friends • Never synchronize on a concurrent collection • Use putIfAbsent (and friends) properly ─ Only call putIfAbsent if get returns null ─ And always check the return value of putIfAbsent
Java Puzzlers: The Never-Ending Saga 58
Agenda
• Generics Items 28, 29 • Enum types Item 40 • Varargs Item 42 • Concurrency Item 69 • Serialization Item 78
Java Puzzlers: The Never-Ending Saga 59
Item 74: Serialization is Fraught with Peril
• Implementation details leak into public API ─ Serialized form derived from implementation
• Instances created without invoking constructor ─ Constructors may establish invariants, and instance
methods maintain them, yet they can be violated
• Doesn't combine well with final fields ─ You’re forced to make them nonfinal or use reflection
• The result: increased maintenance cost, likelihood of bugs, security problems,
• There is a better way!
Java Puzzlers: The Never-Ending Saga 60
The Serialization Proxy Pattern The basic idea is very simple
• Don’t serialize instances of your class; instead, serialize instances of a small, struct-like class class that concisely represents it
• Then reconstitute instances of your class at deserialization time using only its public APIs!
Java Puzzlers: The Never-Ending Saga 61
The Serialization Proxy Pattern Step-by-step (1)
• Design a struct-like proxy class that concisely represents logical state of class to be serialized
• Declare the proxy as a static nested class • Provide one constructor for the proxy, which
takes an instance of the enclosing class ─ No need for consistency checks or defensive copies
Java Puzzlers: The Never-Ending Saga 62
The Serialization Proxy Pattern Step-by-step (2)
• Put writeReplace method on enclosing class // You can always use exactly this code private Object writeReplace() { return new SerializationProxy(this); }
• Put a readResolve method on the proxy ─ Use any methods in the public API of the enclosing
class to reconstitute the instance
Java Puzzlers: The Never-Ending Saga 63
A Real-Life Example EnumSet's Serialization Proxy
private static class SerializationProxy<E extends Enum<E>> implements Serializable { private final Class<E> elementType; private final Enum[] elements;
SerializationProxy(EnumSet<E> set) { elementType = set.elementType; elements = set.toArray(EMPTY_ENUM_ARRAY); }
private Object readResolve() { EnumSet<E> result = EnumSet.noneOf(elementType); for (Enum e : elements) result.add((E)e); return result; } private static final long serialVersionUID = ... ; }
Java Puzzlers: The Never-Ending Saga 64
Truth in Advertising The Serialization Proxy Pattern is not a Panacea
• Incompatible with extendable classes • Incompatible with some classes whose object
graphs contain circularities • Adds 15% to cost of serialization/deserialization • But when it’s applicable, it's the easiest way
to robustly serialize complex objects
Java Puzzlers: The Never-Ending Saga 65
Key Ideas to Take Home
• Remember the PECS mnemonic for wildcards • When a fixed number of type parameters won’t
do, use a Typesafe Heterogeneous Container • Prefer two-element enums to booleans • Never synchronize on a concurrent collection;
use putIfAbsent, and check the return value • When your plans call for serialization,
remember the Serialization Proxy pattern
Java Puzzlers: The Never-Ending Saga 66
Shameless Commerce Division
• There’s plenty more where that came from!
Java Puzzlers: The Never-Ending Saga 67
Dessert
Java Puzzlers: The Never-Ending Saga 68
3. “When Words Collide”
public class PrintWords { public static void main(String[] args) { System.out.println( Words.FIRST + " " + Words.SECOND + " " + Words.THIRD); } }
public class Words { // Compile PrintWords against this version public static final String FIRST = "the"; public static final String SECOND = null; public static final String THIRD = "set"; }
public class Words { // Run against this version public static final String FIRST = "physics"; public static final String SECOND = "chemistry"; public static final String THIRD = "biology"; }
Java Puzzlers: The Never-Ending Saga 69
What Does It Print?
public class PrintWords { public static void main(String[] args) { System.out.println( Words.FIRST + " " + Words.SECOND + " " + Words.THIRD); } }
public class Words { // Compile PrintWords against this version public static final String FIRST = "the"; public static final String SECOND = null; public static final String THIRD = "set"; }
public class Words { // Run against this version public static final String FIRST = "physics"; public static final String SECOND = "chemistry"; public static final String THIRD = "biology"; }
(a) the null set (b) physics chemistry biology (c) Throws exception (d) None of the above
Java Puzzlers: The Never-Ending Saga 70
(a) the null set (b) physics chemistry biology (c) Throws exception (d) None of the above: the chemistry set
Constant variables are inlined.
What Does It Print?
Java Puzzlers: The Never-Ending Saga 71
What exactly is a Constant Variable?
• Roughly speaking, a final primitive or String variable whose value is a compile-time constant ─ See JLS 4.12.4, 13.4.9, 15.28 for the gory details
• A final String variable initialized to null is not a constant variable
• Note that enums are not constant expressions
Java Puzzlers: The Never-Ending Saga 72
Another Look
public class PrintWords { public static void main(String[] args) { System.out.println( Words.FIRST + " " + Words.SECOND + " " + Words.THIRD); } }
public class Words { // Compile PrintWords against this version public static final String FIRST = "the"; // Constant public static final String SECOND = null; // Not a constant! public static final String THIRD = "set"; // Constant }
public class Words { // Run against this version public static final String FIRST = "physics"; public static final String SECOND = "chemistry"; public static final String THIRD = "biology"; }
Java Puzzlers: The Never-Ending Saga 73
How Do You Prevent Constants From Being Inlined?
// Returns its argument private static String ident(String s) { return s; }
// None of these fields are constant variables public class Words { public static final String FIRST = ident("the"); public static final String SECOND = ident(null); public static final String THIRD = ident("set"); }
Java Puzzlers: The Never-Ending Saga 74
The Moral
• Constant variables are inlined ─ Only primitives and strings can be constant ─ null is not a constant
• If you change the value of a constant variable without recompiling its clients, their behavior becomes very confusing
• Use constant variables only for entities whose value will never change ─ Use ident for final fields whose value may change
Java Puzzlers: The Never-Ending Saga 75
Au revoir
Effective Java: Still Effective After All These Years 76
Effective JavaTM : Still Effective, After All These Years
Joshua Bloch