Eccentric Java: oddities and corner cases
Java Puzzlers is a worthy complement to Effective Java. It provides a collection of puzzles that illustrate obscure (and some times bizarre) behavior of the Java programming language and its core libraries.
I've highlighted some of the book's puzzles below, grouped by Numerical, API, and OOP-related traps & solutions.
§ Numerical Traps
- Use the bitwise AND (&) instead of the MODULO (%) operator. The example in the book is a particular case of the more general equivalence:
i % 2n = i & (2n - 1)
, which I first read about in Hacker's Delight. The bitwise expression on the right is likely faster and works safely with negative operands. [#1, page 5] - Use integrals types (such as int or long) or BigDecimal for precise floating-point calculations, e.g.:
200 - 110 = 90
instead of2.00 - 1.10 = 0.89999...
The impossibility of representing 0.1 in binary floating-point is explained with all the detail one can handle in What Every Computer Scientist Should Know About Floating-Point Arithmetic. [#2, page 8] - Beware of hidden integer overflows. When working with large numbers add the suffix 'L' (uppercase to improve legibility) to promote every literal to a long, e.g.:
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000;
. [#3, page 9] - Watch out for loop guards <=
Integer.MAX_VALUE
. All integers are <=Integer.MAX_VALUE
and whenInteger.MAX_VALUE
is incremented it silently wraps around toInteger.MIN_VALUE
. [#26, page 57] - Beware of math operations at integer boundaries, e.g.:
Math.abs(Integer.MIN_VALUE);
If the argument isInteger.MIN_VALUE
orLong.MIN_VALUE
,Math.abs
returns its argument. [#64, page 149]
§ API Traps
- Pattern.quote(String) converts a String into a regex pattern that will match the string. Use
String.replaceAll(Pattern.quote("."), ",");
instead ofString.replaceAll("\\."), ",");
, for example, to prevent the accidental omission of escape characters. [#20, page 43] - The
finally
block is always executed, even if thetry
block returns. Err...except if thetry
block callsSystem.exit(0)
. @See #39 below. I first read about this in Peter Norvig's Java Infrequently Answered Questions. [#36, page 77] - The set of checked exceptions that a method can throw is the intersection of the set of checked exceptions that is declared to throw in all applicable types. [#37, page 79]
- The System.exit(0) method stops the execution of the current thread & all others, but does not cause
finally
blocks to execute.
We can use shutdown hooks to add behavior that must occur before the VM exits, e.g.:
[#39, page 83]
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello"); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { System.out.println(" world"); } }); System.exit(0); } }
- The
close
method of input & output streams can throwIOException
too; from JSE5, input and output streams implement the Closeable interface. This lets us write a generic close method for streams, e.g.:
[#41, page 87]
... InputStream in = null; try { in = new FileInputStream(src); ... } finally { closeAndIgnoreException(in); } private static void closeAndIgnoreException(Closeable c) { if (c != null) { try { c.close(); } catch(IOException ioe){ // ignored } } }
- The JVM has a stack depth limit; when the limit is exceeded a StackOverflowError is thrown, regardless of the infinite nature of the recursion. [#45, page 101]
- Use
Method m = <object variable>.class.getMethod(<method name>);
instead ofMethod m = <object variable>.getClass().getMethod(<method name>);
to prevent possible runtime exceptions caused by the runtime type returned by reflection. [#79, page 189]
§ OOP Traps
- Java's overload resolution process selects the most specific of the methods or constructor available. Supposing we declare the methods:
void f(float) { ... } void f(double) { ... }
Callingf
withf(42)
, the parameter types for the first method are assignable to the parameter types of the second method through a widening primitive conversion. That is,double = float
. By contrast,float = double
is not valid without a cast. Thus,f(double)
is removed from the set of possible methods to call andf(float)
is called. [#46, page 105] - A static member, such as a thread-safe counter, must be synchronized on the class object itself, e.g.:
[#55, page 127]
public class SynchronizedStatic { private static Long counter; public SynchronizedStatic() { synchronized (SynchronizedStatic.class) { counter++; } } }
- When a variable and a type have the same name and are both in scope, the variable name takes precedence, e.g. (from the book):
[#68, page 163]
public class ShadesOfGray { public static void main(String[] args){ System.out.println(X.Y.Z); // prints 'White' } } class X { static class Y { static String Z = "Black"; } static C Y = new C(); // variable Y takes precedence over type Y } class C { String Z = "White"; }
- Package-private methods cannot be directly overridden by a method in a different package (hence the name, I'd say.) [#70, page 168]
- The
final
modifier means different things on methods & fields:final
instance methods cannot be overridden;final static
methods may not be hidden;final
fields may not be assigned more than once. [#72, page 171] - Overriding = replacement with same name, signature, & exceptions thrown -without reducing visibility;
Hiding = static override; Overloading = supplement with same name but different signature & exceptions thrown;
Shadowing (textual scope) = enclosing scope is shadowed by local variables, methods, and types with the same name;
Obscuring = static shadowing. [#Glossary, page 180]
Resources:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
IEEE Standard 754 Floating Point Numbers
Why integer overflow "wraps around"
Hacker's Delight
Peter Norvig's Java Infrequently Answered Questions
JVM Overview
Overload Resolution
Algorithms for Overload Resolution
Function Overloading With Partial Ordering
The Java Language Specification
Effective Java: Second Edition
Java Puzzlers: Traps, Pitfalls, and Corner Cases
Advanced Topics in Programming Languages: Java Puzzlers, Episode VI
Java Puzzlers: Scraping the Bottom of the Barrel
1 comment:
For the non-regex String replacement issue (API traps #1), you should really use the string's replace method instead:
myString.replace(".", ",")
Post a Comment