You are on page 1of 65

Java Puzzlers:

Scraping the Bottom of the Barrel

Josh Bloch and Bob Lee

Java Puzzlers Unbugged

Some people say seeing is believing

Java Puzzlers Unbugged

Fraser 1908
3 Java Puzzlers Unbugged

Fraser 1908
4 Java Puzzlers Unbugged

Fraser 1908
5 Java Puzzlers Unbugged

Logvinenko 1999
6 Java Puzzlers Unbugged

Logvinenko 1999
7 Java Puzzlers Unbugged

Logvinenko 1999
8 Java Puzzlers Unbugged

Kitaoka 1998
9 Java Puzzlers Unbugged

Kitaoka 1998
10 Java Puzzlers Unbugged

Kitaoka 1998
11 Java Puzzlers Unbugged

And Now, in Code!


Seven NEW Java programming language puzzles
Short program with curious behavior What does it print? (multiple choice) The mystery revealed How to fix the problem The moral

12

Java Puzzlers Unbugged

1. Glommer Pile
public class Glommer<T> { String glom(Collection<?> objs) { String result = ""; for (Object o : objs) result += o; return result; } int glom(List<Integer> ints) { int result = 0; for (int i : ints) result += i; return result; } public static void main(String args[]) { List<String> strings = Arrays.asList("1", "2", "3"); System.out.println(new Glommer().glom(strings)); }

}
13

Java Puzzlers Unbugged

What Does it Print?


public class Glommer<T> { String glom(Collection<?> objs) { String result = ""; for (Object o : objs) result += o; return result; } int glom(List<Integer> ints) { int result = 0; for (int i : ints) result += i; return result; }

(a) 6 (b) 123 (c) Throws exception (d) None of the above

}
14

public static void main(String args[]) { List<String> strings = Arrays.asList("1", "2", "3"); System.out.println(new Glommer().glom(strings)); }

Java Puzzlers Unbugged

What Does it Print?


(a) 6 (b) 123 (c) Throws exception: ClassCastException (d) None of the above

Raw types are dangerous

15

Java Puzzlers Unbugged

Another Look
public class Glommer<T> { String glom(Collection<?> objs) { String result = ""; for (Object o : objs) result += o; return result; } int glom(List<Integer> ints) { int result = 0; for (int i : ints) result += i; return result; } public static void main(String args[]) { List<String> strings = Arrays.asList("1", "2", "3"); System.out.println(new Glommer().glom(strings)); }

}
16

Java Puzzlers Unbugged

The Compiler Tried to Warn You!


javac Glommer.java Note: Glommer.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.

And hopefully:
javac -Xlint:unchecked Glommer.java Glommer.java:20: warning: [unchecked] unchecked call to glom(java.util.List<java.lang.Integer>) as a member of the raw type Glommer System.out.println(new Glommer().glom(strings)); ^

17

Java Puzzlers Unbugged

You Could Fix it Like This


Specify type parameter for Glommer
public class Glommer<T> { String glom(Collection<?> objs) { String result = ""; for (Object o : objs) result += o; return result; } int glom(List<Integer> ints) { int result = 0; for (int i : ints) result += i; return result; } public static void main(String args[]) { List<String> strings = Arrays.asList("1", "2", "3"); System.out.println(new Glommer<Long>().glom(strings)); }

}
18

Java Puzzlers Unbugged

Or you Could Fix it Like This

Remove extraneous type parameter from Glommer


public class Glommer { // Look Ma, no type parameter String glom(Collection<?> objs) { String result = ""; for (Object o : objs) result += o; return result; } int glom(List<Integer> ints) { int result = 0; for (int i : ints) result += i; return result; } public static void main(String args[]) { List<String> strings = Arrays.asList("1", "2", "3"); System.out.println(new Glommer().glom(strings)); }

}
19

Java Puzzlers Unbugged

But This is Even Better


public class Glommer { // Static utility class static String glom(Collection<?> objs) { String result = ""; for (Object o : objs) result += o; return result; } static int glom(List<Integer> ints) { int result = 0; for (int i : ints) result += i; return result; } public static void main(String args[]) { List<String> strings = Arrays.asList("1", "2", "3"); System.out.println(glom(strings)); // No Glommer! }

}
20

Java Puzzlers Unbugged

The Moral

Do not use raw types Raw types lose all type information
Can compromise overload resolution

Do not ignore compiler warnings


Unchecked warnings mean automatically generated casts can fail at runtime

21

Java Puzzlers Unbugged

2. Time for a Change If you pay $2.00 for a gasket that costs $1.10, how much change do you get?
public class Change { public static void main(String args[]) { System.out.println(2.00 - 1.10); } }

22

Java Puzzlers Unbugged

2. A Change is Gonna Come If you pay $2.00 for a gasket that costs $1.10, how much change do you get?
import java.math.BigDecimal; public class Change { public static void main(String args[]) { BigDecimal payment = new BigDecimal(2.00); BigDecimal cost = new BigDecimal(1.10); System.out.println(payment.subtract(cost)); } }

23

Java Puzzlers Unbugged

What Does It Print?

(a) .9 (b) .90 (c) 0.8999999999999999 (d) None of the above


import java.math.BigDecimal; public class Change { public static void main(String args[]) { BigDecimal payment = new BigDecimal(2.00); BigDecimal cost = new BigDecimal(1.10); System.out.println(payment.subtract(cost)); } }

24

Java Puzzlers Unbugged

What Does It Print?

(a) .9 (b) .90 (c) 0.8999999999999999 (d) None of the above: 0.89999999999999991118215802998 7476766109466552734375
We used the wrong BigDecimal constructor

25

Java Puzzlers Unbugged

Another Look
public BigDecimal(double val) Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value.
import java.math.BigDecimal; public class Change { public static void main(String args[]) { BigDecimal payment = new BigDecimal(2.00); BigDecimal cost = new BigDecimal(1.10); System.out.println(payment.subtract(cost)); } }

26

Java Puzzlers Unbugged

How Do You Fix It?

import java.math.BigDecimal; public class Change { public static void main(String args[]) { BigDecimal payment = new BigDecimal("2.00"); BigDecimal cost = new BigDecimal("1.10"); System.out.println(payment.subtract(cost)); } }

27

Java Puzzlers Unbugged

The Moral

Avoid float and double where exact


answers are required
For example, when dealing with money

Use BigDecimal, int, or long instead Use new BigDecimal(String),


not new BigDecimal(Double)

28

Java Puzzlers Unbugged

3. That Sinking Feeling (2010)


abstract class Sink<T> { abstract void add(T... elements); void addUnlessNull(T... elements) { for (T element : elements) if (element != null) add(element); } } public class StringSink extends Sink<String> { final List<String> list = new ArrayList<String>(); void add(String... elements) { list.addAll(Arrays.asList(elements)); } @Override public String toString() { return list.toString(); } public static void main(String[] args) { Sink<String> ss = new StringSink(); ss.addUnlessNull("null", null); System.out.println(ss); } }
29 Java Puzzlers Unbugged

What Does It Print?

abstract class Sink<T> { abstract void add(T... elements); void addUnlessNull(T... elements) { for (T element : elements) if (element != null) add(element); } } public class StringSink extends Sink<String> { final List<String> list = new ArrayList<String>(); void add(String... elements) { list.addAll(Arrays.asList(elements)); } @Override public String toString() { return list.toString(); } public static void main(String[] args) { Sink<String> ss = new StringSink(); ss.addUnlessNull("null", null); System.out.println(ss); } }
30 Java Puzzlers Unbugged

(a) StringSink@19821f (b) [null] (c) [null, null] (d) None of the above

What Does It Print?


(a) StringSink@19821f (b) [null] (c) [null, null] (d) None of the above: ClassCastException

Varargs and generics dont get along too well.

31

Java Puzzlers Unbugged

Another Look

Stack trace is very confusing!


abstract class Sink<T> { abstract void add(T... elements); void addUnlessNull(T... elements) { for (T element : elements) if (element != null) add(element); // (2) } } public class StringSink extends Sink<String> { // (3) Throws! final List<String> list = new ArrayList<String>(); void add(String... elements) { list.addAll(Arrays.asList(elements)); } @Override public String toString() { return list.toString(); } public static void main(String[] args) { Sink<String> ss = new StringSink(); ss.addUnlessNull("null", null); // (1) System.out.println(ss); } }
32 Java Puzzlers Unbugged

Whats Going On Under the Covers?


Varargs, erasure, and a bridge method
abstract class Sink<T> { abstract void add(T... elements); // Really (Object[]) void addUnlessNull(T... elements) { // Really (Object[]) for (T element : elements) if (element != null) add(element); // Makes Object[] } } class StringSink extends Sink<String> { /** synthetic bridge method invisible in source code */ @Override void add(Object[] a) { add((String[]) a); // Throws ClassCastException! } void add(String... elements) { list.addAll(Arrays.asList(elements)); } public static void main(String[] args) { Sink<String> ss = new StringSink(); ss.addUnlessNull("null", null); System.out.println(ss); } }
33 Java Puzzlers Unbugged

The Compiler Tried to Warn You!


javac StringSink.java Note: StringSink.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.

And hopefully:
javac -Xlint:unchecked StringSink.java StringSink.java:7: warning: [unchecked] unchecked generic array creation of type T[] for varargs parameter if (element != null) add(element); ^

34

Java Puzzlers Unbugged

How Do You Fix It?


abstract class Sink<T> { abstract void add(Collection<T> elements); void addUnlessNull(Collection<T> elements) { for (T element : elements) if (element != null) add(singleton(element)); } } public class StringSink extends Sink<String> { final List<String> list = new ArrayList<String>(); void add(Collection<String> elements) { list.addAll(elements); // No call to Arrays.asList } @Override public String toString() { return list.toString(); } public static void main(String[] args) { Sink<String> ss = new StringSink(); ss.addUnlessNull(Arrays.asList("null", null)); System.out.println(ss); } }
35 Java Puzzlers Unbugged

The Moral

Varargs provide a leaky abstraction


Arrays leak through the veneer of varargs

Generics and arrays don't get along well


So varargs and arrays don't get along well

Prefer collections to arrays


Especially in APIs

Don't ignore compiler warnings

36

Java Puzzlers Unbugged

4. The Match Game


public class Match { public static void main(String[] args) { Pattern p = Pattern.compile("(aa|aab?)+"); int count = 0; for(String s = ""; s.length() < 200; s += "a") if (p.matcher(s).matches()) count++; System.out.println(count); } }

37

Java Puzzlers Unbugged

What Does it Print?


public class Match { public static void main(String[] args) { Pattern p = Pattern.compile("(aa|aab?)+"); int count = 0; for(String s = ""; s.length() < 200; s += "a") if (p.matcher(s).matches()) count++; System.out.println(count); } }

(a) 99 (b) 100 (c) Throws an exception (d) None of the above

38

Java Puzzlers Unbugged

What Does It Print?


(a) 99 (b) 100 (c) Throws an exception (d) None of the above: runs for >1015 years before printing anything; the Sun wont last that long

The regex exhibits catastrophic backtracking

39

Java Puzzlers Unbugged

What is Catastrophic Backtracking?


(aa) (aa) (aa) (aa) (aab?) (aab?) (aab?) (aab?) (aa) (aa) (aab?) (aab?) (aa) (aa) (aab?) (aab?) (aa) (aab?) (aa) (aab?) (aa) (aab?) (aa) (aab?) fail fail fail fail fail fail fail fail

Heres how matcher tests "aaaaa" for "(aa|aab?)+"

This is exponential in length of string! O(2n/2)

40

Java Puzzlers Unbugged

How Do You Fix It?


public class Match { public static void main(String[] args) { Pattern p = Pattern.compile("(aa|aab)+"); // No ? int count = 0; for(String s = ""; s.length() < 200; s += "a") if (p.matcher(s).matches()) count++; System.out.println(count); } }

The regex "(aa|aab)+" matches exactly the same strings as "(aa|aab?)+" but doesnt exhibit catastrophic backtracking

41

Java Puzzlers Unbugged

Moral

Since regexes provide grouping information,

they cant just be compiled into optimal DFAs


Matcher must try all combinations of subpatterns

To avoid catastrophic backtracking, ensure


theres only one way to match each string This goes way beyond Java
Affects most regular expression systems Enables denial-of-service attacks

Just because you can express it concisely


doesnt mean computation is fast

42

Java Puzzlers Unbugged

5. Size Matters
public class Size { private enum Sex { MALE, FEMALE } public static void main(String[] args) { printSize(new HashMap<Sex, Sex>()); printSize(new EnumMap<Sex, Sex>(Sex.class)); } private static void printSize(Map<Sex, Sex> map) { map.put(Sex.MALE, Sex.FEMALE); map.put(Sex.FEMALE, Sex.MALE); map.put(Sex.MALE, Sex.MALE); map.put(Sex.FEMALE, Sex.FEMALE); Set<Map.Entry<Sex, Sex>> set = new HashSet<Map.Entry<Sex, Sex>>(map.entrySet()); System.out.println(set.size()); }

Thanks to Chris Dennis, via Alex Miller


43 Java Puzzlers Unbugged

What Does it Print?

public class Size { private enum Sex { MALE, FEMALE }

(a) 1 2 (b) 2 1 (c) 2 2 (d) None of the above

public static void main(String[] args) { printSize(new HashMap<Sex, Sex>()); printSize(new EnumMap<Sex, Sex>(Sex.class)); } private static void printSize(Map<Sex, Sex> map) { map.put(Sex.MALE, Sex.FEMALE); map.put(Sex.FEMALE, Sex.MALE); map.put(Sex.MALE, Sex.MALE); map.put(Sex.FEMALE, Sex.FEMALE); Set<Map.Entry<Sex, Sex>> set = new HashSet<Map.Entry<Sex, Sex>>(map.entrySet()); System.out.println(set.size()); }

44

Java Puzzlers Unbugged

What Does It Print?


(a) 1 2 (b) 2 1 (c) 2 2 (d) None of the above

Enumerating over entry sets works better for Some Map implementations than others
45 Java Puzzlers Unbugged

Another Look
public class Size { private enum Sex { MALE, FEMALE } public static void main(String[] args) { printSize(new HashMap<Sex, Sex>()); printSize(new EnumMap<Sex, Sex>(Sex.class)); } private static void printSize(Map<Sex, Sex> map) { map.put(Sex.MALE, Sex.FEMALE); map.put(Sex.FEMALE, Sex.MALE); map.put(Sex.MALE, Sex.MALE); map.put(Sex.FEMALE, Sex.FEMALE); Set<Map.Entry<Sex, Sex>> set = new HashSet<Map.Entry<Sex, Sex>>(map.entrySet()); System.out.println(set.size()); }

} Constructor iterates over entrySet and inserts entries into set


46 Java Puzzlers Unbugged

WTF? Is this a bug?

Perhaps, but its been around since 1997


Josh thought it was legal when he designed Collections But the spec is, at best, ambiguous on this point

Several Map implemenations did this


IdentityHashMap, ConcurrentHashMap, EnumMap ConcurrentHashMap was fixed a long time ago

47

Java Puzzlers Unbugged

How Do You Fix It?


public class Size { private enum Sex { MALE, FEMALE } public static void main(String[] args) { printSize(new HashMap<Sex, Sex>()); printSize(new EnumMap<Sex, Sex>(Sex.class)); } private static void printSize(Map<Sex, Sex> map) { map.put(Sex.MALE, Sex.FEMALE); map.put(Sex.FEMALE, Sex.MALE); map.put(Sex.MALE, Sex.MALE); map.put(Sex.FEMALE, Sex.FEMALE); Set<Map.Entry<Sex, Sex>> set = new HashSet<Map.Entry<Sex, Sex>>(); // Empty for (Map.Entry<Sex, Sex> e : map.entrySet()) set.add(new AbstractMap. SimpleEntry<Sex, Sex>(e.getKey(), e.getValue())); System.out.println(set.size()); }

}
48

Java Puzzlers Unbugged

Moral

Iterating over entry sets requires care


Entry may become invalid when iterator advances EnumMap and IdentityHashMap are the only JDK Map implementations with this (bad) behavior

For API designers


Dont violate the principle of least astonishment Dont worsen your API to improve performance (unless you absolutely must)

49

Java Puzzlers Unbugged

6. Long Division (2004)


public class LongDivision { private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; private static final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; public static void main(String[] args) { System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY); } }

50

Java Puzzlers Unbugged

What Does It Print?


public class LongDivision { private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; private static final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; public static void main(String[] args) { System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY); } }

(a) 5 (b) 1000 (c) 5000 (d) Throws an exception


51 Java Puzzlers Unbugged

What Does It Print?


(a) 5 (b) 1000 (c) 5000 (d) Throws an exception

Computation does overflow

52

Java Puzzlers Unbugged

Another Look
public class LongDivision { private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; private static final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; // >> Integer.MAX_VALUE public static void main(String[] args) { System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY); } }

53

Java Puzzlers Unbugged

How Do You Fix It?


public class LongDivision { private static final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000; private static final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000; public static void main(String[] args) { System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY); } }

54

Java Puzzlers Unbugged

The Moral

When working with large numbers, watch out

for overflowits a silent killer Just because variable can hold result doesnt mean computation wont overflow

When in doubt, use larger

type

55

Java Puzzlers Unbugged

7. Its Elementary (2004; 2010 remix)


public class Elementary { public static void main(String[] args) { System.out.println(12345 + 5432l); System.out.println(01234 + 43210); } }

56

Java Puzzlers Unbugged

What Does It Print?


public class Elementary { public static void main(String[] args) { System.out.println(12345 + 5432l); System.out.println(01234 + 43210); } }

(a) 17777 44444 (b) 17777 43878 (c) 66666 44444 (d) 66666 43878
57 Java Puzzlers Unbugged

What Does It Print?


(a) 17777 44444 (b) 17777 43878 (c) 66666 44444 (d) 66666 43878

Program doesnt say what you think it does! Also, leading zeros can cause trouble.

58

Java Puzzlers Unbugged

Another Look
public class Elementary { public static void main(String[] args) { System.out.println( 2345 + 5432 ); System.out.println(01234 + 43210); } }

1 - the numeral one l - the lowercase letter el


59 Java Puzzlers Unbugged

Another Look, Continued


public class Elementary { public static void main(String[] args) { System.out.print( 2345 + 5432 ); System.out.print(" "); System.out.print(01234 + 43210); } }

01234 is an octal literal equal to 1,2348, or 668

60

Java Puzzlers Unbugged

How Do You Fix It?


public class Elementary { public static void main(String[] args) { System.out.println(12345 + 54321); System.out.println(1234 + 43210); // No leading 0 } }

61

Java Puzzlers Unbugged

The Moral

Always use uppercase el (L) for long literals


Lowercase el makes the code unreadable 5432L is clearly a long, 5432l is misleading

Never use lowercase el as a variable name


Not this: List<String> l = ... ; But this: List<String> list = ...;

Never precede an int literal with 0 unless


you want to express it in octal (base 8)

62

Java Puzzlers Unbugged

Conclusion

Java platform is reasonably simple and elegant


But it has a few sharp cornersavoid them!

Keep programs clear and simple If you arent sure what a program does,
it probably doesnt do what you want

Use FindBugs and a good IDE Dont code like my brother

63

Java Puzzlers Unbugged

Shameless Commerce Division


95 Puzzles 52 Illusions Tons of fun

64

Java Puzzlers Unbugged

Java Puzzlers:
Scraping the Bottom of the Barrel

Josh Bloch and Bob Lee

65

Java Puzzlers Unbugged

You might also like