java pitfalls and good-to-knows
DESCRIPTION
This slide set covers some Java tidbits you might not encounter on your day-to-day java development. Perhaps some of them will improve your coding and save you some debugging!TRANSCRIPT
Java Pitfalls and Good-to-Knows
Miquel Martin – [email protected] Benjamin Hebgen – [email protected]
What’s this?
Photo credits:
The splashing coffe cup at the title by 96dpi on flicker: http://www.flickr.com/photos/96dpi/
Coffee beans on the watermark by wiedmaier on flicker: http://www.flickr.com/photos/wiedmaier/
Padlock on the ReadWriteLock slide by darwinbell on flicker: http://www.flickr.com/photos/darwinbell/
Tree in the dune by suburbanbloke on flickr: http://www.flickr.com/photos/suburbanbloke/ 2
This slide set covers some Java tidbits you might not encounter on your day-to-day java development. Perhaps some of them will improve your coding and save you some debugging!
PermGen and Strings The Java Garbage Collector works
on generations. The permanent one hosts internalized Strings and classes.
When the compiler finds a literal String in your code, it allocates it (if not yet there) in the string pool at the PermGen heap. All instances of the same String will point to the same PermGen instance.
Too many literal Strings (bad design) or thinks like classloader leaks (http://goo.gl/LIodj) can lead to “Out of MemoryError: PermGen space”.
3
Young Generation: New objects
Tenured Generation: Multi Garbage Collection survivors
Permanent Generation: Class objects, Strings. Won’t be Garbage Collected
StringBuilders and StringBuffers String numberList = "";
for(int i = 0; i < 1000; i++){
numberList+= i + ", ";
}
Strings are immutable.
The example on the right will: 1. Create the numberList String 2. Create a newNumberList with the
numberList, I and a “, “ 3. Assign newNumberList to numberList 4. Repeat 1000 times 2 & 3
This is slow, fills up your heap and therefore forces more costly garbage collections
Use instead a mutable StringBuilder. If you have multiple threads accessing it, use the synchronized version, StringBuffer.
4
StringBuilder builder= new StringBuilder();
for(int i = 0; i < 1000; i++){
builder.append(i+", ");
}
String numberList = builder.toString();
From numbers to Strings int i = 42;
String str = i; //Error, i not a String // Case1: All time favorite (and slowest) String str = "" + i; String str = i + ""; // Case2: the fastest one String str = Integer.toString(myNumber); // Case3: Also fast (calls Case 2) String str = String.valueOf(myNumber);
How do you quickly convert a number to a String? Case 1: easy but slow, a new String is created that
concatenates the literal “” and i. Case 2: a new String is directly created for the
number. This is 2x faster than Case 1 Case 3: it internally calls Case 2, and has roughly
the same performance.
Exception: in “The number is “ + i, all three cases are similar since String concatenation must happen anyway, but Case 2 and 3 need to explicitly create an additional String.
This also works on: short, float, double, long, etc..
Take home lesson: ignore this and do whatever’s
more readable, unless performance is really critical
5
Comparing floating point numbers
//This is true (uses floats) boolean floatResult = 0.1f + 0.1f + 0.1f == 0.3f; //This is false (uses doubles) boolean doubleResult = 0.1 + 0.1 + 0.1 == 0.3; //Also false (also doubles) boolean doubleResult = 0.1d + 0.1d + 0.1d == 0.3d;
Floating point numbers are encoded using IEEE754-2008. Not all real numbers can be represented (e.g. 0.2)
Extra caution is needed when comparing floats (see examples)
Also, portability issues due to evolution:
Java ≤ 1.2: intermediate calculation steps done using single or double precision
Java > 1.2: to decrease rounding errors the highest platform supported precision is used for intermediate results.
Note: the strictfp keyword forces Java 1.2 behavior
6
float f = 25
In Hexadecimal:
0100 0001 1100 1000 0000 0000 0000 00002
Bit 0: the sign, 0 is positive
Bits 1 to 8: Exponent: 1000 00112 is 13110
and then: 131-127 = 4
Significand: bit ?: 1x1/1 = 1x1 = 1 bit 9: 1x1/2 = 1x0.5 = 0.5 bit 10: 0x1/4 = 0x0.25 = 0 bit 11: 0x1/8 = 0x0.125 = 0 bit 11: 1x1/16 = 0x0.0625 = 0.0625
. . Total = 1 + 0.5 + 0.0625 = 1.5625
Result: + 1.5625 x 24 = 25
Volatile variables package operators;
class Test extends Thread {
boolean done = false;
public void run() {
while (!done) {
}
System.out.println("Thread terminated.");
}
public static void main(String[] args) throws
InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.done = true;
System.out.println("done");
}
}
Depending on the JVM, threads may work on copies of variables and never re-check the original reference.
This does not happen if the variable is marked volatile, or the variable access is synchronized. Both tell the JVM that multi-thread access is likely and to check the original reference.
The code on the right will never finish unless (1) is volatile. The behavior can change between JVMs. Also, volatile does not solve concurrent modification issues. Don’t rely on volatile unless you know exactly what you’re doing.
7
1
Breaking out of the right loop
mytag: for(int i =0; i < 10; i++){
for(j =0; j < 10; j++){
if(done) {
break mytag;
}
}
}
The break keyword takes a label as a parameter and breaks out of the scope tagged for it
This works also on
switch, for, while and do-while
both for
continue and break
8
Preventing inheritance, overriding and instantiating Controlling how your class is used
comes in handy: singletons, factories, utility classes, etc…
The final keyword applied to a: Class: prevents extending method: prevents overriding primitive field: prevents changing
the value Object field: prevents changing
the reference (you can still change the object)
A private default constructor will additionally prevent external instantiation
A final Class with only one private constructor cannot be extended or overridden: it will never be externally instantiated
9
// A Utility class has only static methods
public class UtilityClass {
// Private constructor prevents
// external class instantiation private TestClass() { } public static int getInstance() {
return new TestClass(); } }
// Final class cannot be extended
public final class FinalClass { // Final primitive cannot be modified
final boolean something = false;
// Reference to final object cannot
//be modified final StringBuilder builder= new StringBuilder();
// Final method cannot be overriden
public final void someMethod() { } }
Static code blocks public class StaticBlocks { private static int number = 1; static { System.out.println(number); } static { number = 2; } static { System.out.println(number); } static { number = increaseNumber(number); } static { System.out.println(number); } public static int increaseNumber(int i) { return i + 1; } public static void main(String[] args) { System.out.println(number); //Aggregatted output: 1, 2, 3, 3 } }
Static blocks let you execute something at class loading time (e.g. assign a value to a static field)
Static blocks and static field assignment is done once at class loading
The execution order is the same as the order in the code
10
Finally we finalize the final. try {
System.out.println("In try");
throw new Exception();
} catch (Exception e) {
System.out.println("In catch");
} finally {
System.out.println("In finally");
}
// Outputs: In try, In catch, In finally
BufferedWriter writer = null;
try {
writer = Files.newBufferedWriter(file,
charset);
writer.write(s, 0, s.length());
} catch (IOException x) {
System.err.println("Error happened");
} finally {
if (writer != null) writer.close();
}
final is already covered
finalize will be called on an instance when (if!) it is garbage collected. You have no real control of when
the GC will pass. Plus it’s bad practice and often ignored by JVMs
Do not use it to free resources. It’s not the C++ destructor,
finally goes after a try…catch block:
First, run the code in the try If an exception is thrown, run the
appropriate code in catch Then, no matter what, run the code
in finally. Even after a return in catch or try.
It’s worth using finally even if no exceptions are expected. They might be thrown in a future code change.
11
Keywords you don’t see so often • assert: evaluate a boolean expression and throw an AssertionException if false. Useful to
write your assumptions in the code, and get notified if you were wrong
• continue: skip to the next iteration of a loop
• strictfp: ensures consistent floating point operations on different platforms
• transient: keep the field from being serialized when using standard java serialization
mechanisms
• volatile: prevents threads from working with a thread local copy of a variable when not
using synchronized access
• native: denotes that the method is provided by an external non-java library using JNI
• goto: it’s a reserved keyword but a valid keyword. The compiler will throw a syntax error
• const: same as goto
12
Protective copies
class Test {
private List<String> internalStuff = new
ArrayList<String>();
public void addStuff(String stuff) {
//Do internal logic
internalStuff.add(stuff);
}
public void removeStuff(String stuff) {
//Do internal logic
internalStuff.remove(stuff);
}
public List<String> getAllStuff(){
//Dangerous: return internalStuff;
//Better:
return new ArrayList<String>(internalStuff);
}
}
In this code, some internal logic occurs on adding/removing stuff.
If getAllStuff returns internalStuff, the caller can add/remove items without going through removeStuff
If this is (or could be) an issue, create a protective copy of internalStuff first
13
Shallow comparison and Arrays String[] a1 = new String[] { "0", "1", "2" }; String[] a2 = new String[] { "0", "1", "2" }; System.out.println(a1 == a2); // false System.out.println(a1.equals(a2)); // false System.out.println(Arrays.equals(a1, a2)); // true List<String> list1 = new ArrayList<String>(3); List<String> list2 = new ArrayList<String>(3); list1.add("0"); list2.add("0"); list1.add("1"); list2.add("1"); list1.add("2"); list2.add("2"); System.out.println(list1 == list2); // false System.out.println(list1.equals(list2)); // true if the elements in the list properly
implement equals
Comparison types:
Shallow: uses == and compares object references
Deep: uses equals and is as good as your equals implementation
Notable exception: equals is odd for arrays (1) and will perform a shallow comparison. Use Arrays.equals instead
14
1
Unchecked Exceptions
Checked VS Unchecked exceptions Unlike Unchecked exceptions,
Checked exceptions need to be caught or declared
Pros of unchecked exceptions:
Clearer code
You can still treat them like checked exceptions if you want
Cons: Easy to miss JsonParseException in Gson
NumberFormatException from new Integer(“3,4”) in some locales
15
Throwable
Exception Error
? extends Error
Runtime Exception
? extends Runtime
Exception
? extends Exception
Testing with Hamcrest Matchers public class RegexMatcher extends
BaseMatcher<String> { private final String regex; public RegexMatcher(String regex) { this.regex = regex; } @Override public boolean matches(Object s) { return ((String) s).matches(regex); } @Override public void describeTo(Description description)
{ description.appendText("matches " + regex); } public static RegexMatcher matches(String regex)
{ return new RegexMatcher(regex); } } //Allows you to do: assertThat(actual, matches(regex));
In JUnit, assertTrue, assertEquals, etc… perform specific assertions
For arbitrary assertions, you can use a Hamcrest Matcher and run it with assertThat
16
Type erasure public void doSomething(List<String>
list) {} public void doSomething(List<Integer>
list) { } // Compiler error: both methods can’t
be in the same class, since they have the same type erasure
The Java generics implementation does not change the compiler much, instead, it pre-processes away the generics
At compile time, type parameters are replaced with their bounds
Types are “erased” in the compiled bytecode
Famous pitfall: after type erasure, List<String> is the same as List<Integer>
There’s plenty more interesting gotchas in generics, check: http://goo.gl/0AeYW
17
Read/Write Locks Case: you don’t care how many
threads read your object, as long as no one is modifying it, and then, only one at a time.
Solution: replace your synchronized methods with ReadWriteLocks
Beware of the details:
in balanced read/write scenarios, the overhead of a ReadWriteLock will likely degrade performance.
Thread starvation and fairness are an issue. Check ReentrantLocks
A good discussionL http://goo.gl/zRjvL
18
Synchronized Collections
List<String> unsynchronizedList = new
ArrayList<String>();
List<String> synchronizedList =
Collections.synchronizedList(unsynchro
nizedList);
Map<String, String> unsynchronizedMap =
new LinkedHashMap<String, String>();
Map<String, String> synchronizedMap =
Collections.synchronizedMap(unsynchron
izedMap);
You’ll still find online that:
A Hashtable is like a HashMap but synchronized (there’s more to it, like null values)
A Vector is like an ArrayList but synchronized (it isn’t)
This hasn’t been true since Java 1.3 introduced the Synchronized Collections
Note that Read/Write locks are Java >=1.5 so there’s plenty room for optimizing if you need to.
Notable exception: ConcurrentHashMap is (probably) your high-performance map for multi-threaded access
19
Legacy Classes Legacy classes are not (yet) deprecated but their use is discouraged. They still pop up in plenty of tutorials and snippets.
Vector and Dictionary/Hashtable should not be used as synchronized List and Map. Use Collections.synchronizedList and Collections.synchronizedMap instead. Also consider ConcurrentHashMap.
Properties extends Hashtable with file writing capabilities, but there’s no replacement.
Stack is a revamp of Vector to work like a LinkedList. Use that instead.
StringTokenizer is better implemented by String.split and also handles encoding better
Enumeration is very close to an Iterator but does less (e.g. remove)
Most discouraged classes are parallel implementations of existing classes with a minor (often mis-implemented) delta.
20
equals, hashCode and Comparable // HashMap put public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); // Locate the bucket by hashCode int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // Check for equality only in the bucket if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
Different collections use different mechanisms to sort out objects, for example:
HashMap uses hashCode
TreeSet uses Comparable
If hashCode and Comparable are not consistent with equals, expect inconsistencies! Check the contract.
21
Java 7’s NIO.2 Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING); WatchService
watchService = new WatchService() { ... }; path.register(watchService, ENTRY_CREATE,
ENTRY_DELETE, ENTRY_MODIFY); Files.getPosixFilePermissions(path,
LinkOption.NOFOLLOW_LINKS); Files.isExecutable(path); Files.isReadable(path); Files.isWritable(path); Files.readAllLines(path,
Charset.forName("utf-8")); Files.write(path, "mytext".getBytes(Charset.
forName("utf-8")), StandardOpenOption.CREATE);
Java 7’s new file I/O makes file system interactions much easier
Implemented in java.nio.file.Files and java.nio.file.Path
It provides common file operations like copy or readAllLines
You can get notifications on Path modifications
Support for permission and owner handling and links
Fine grained exception handling
22
Where to store resources String winPath = System.getProperty("
user.home") + File.separator + "Application Data" + File.separator + "myApp";
String linuxPath = System.getProperty("user.home") + File.separator + ".myApp";
TestClass.class.getResourceAsStream("myresource.xml")
//This will find your class
MyClass.class.getProtectionDomain().getCodeSource().getLocation().getPath();
Best practice for user specific configuration options per OS:
Storing in your classpath (e.g. in your jar) using Class.getResourceAsStream
If you really really want, there is a way to find the path to your class, allowing for storage relative to the installation folder
23
(Avoid) Reinventing the wheel
There are a lot of 3rd party libraries for Java. Avoid reinventing the wheel! Also beware of very alpha libraries!
You will only use stuff you know exists. Consider browsing through the documentation of libraries like:
Apache Commons: for anything from logging to collections and much more http://commons.apache.org/
Google’s Guava: for a ton of convenience classes and very interesting containers http://code.google.com/p/guava-libraries/
OPS4J: for all sort of OSGi related helpers http://team.ops4j.org
24
25
Thanks and Happy Coding!