Last year I passed the exam for the “Oracle Certified Professional Java Programmer”successfully and wanted to share my exam preparation notes about common problems and pitfalls to be aware of.
That’s why I have added some code and examples for my top 25 challenges below.
Exam Preparation
I won’t cover how to prepare yourself for the exam, how the exam is structured or what is the best way to process the exam’s questions – these questions are very well covered by the SCJP study guide, JavaRanch.com or other resources of choice – I have listed a few at the and of this article in the resources section.
The best resources for my own preparation were the following ones..
Resources
Kathy Sierra/Bert Bates: SCJP/OCP Study Guide
I definitely recommend you to buy Kathy Sierra’s and Bert Bates’ excellent book “SCJP Sun Certified Programmer for Java 6” or the newer version for the OCP. I’ve written a review about the first one – if you’re interested take a look at my article: Review: “SCJP Sun Certified Programmer for Java 6 Study Guide“.
The book not only covers all you have to know about the exam but also gives you a nice exam simulator and well structured lessons.
JavaRanch
**Not only detailed information about the exam and exam preparation is to be found here – in addition there is an excellent SCJP FAQ and a forum specialized on questions regarding the exam.
Certpal.com
**They offer some nice information about certification pitfalls, the usage of the console object or navigable sets.
Additional Mock Exams
I haven’t tested the following mock exams in detail – the one from the SCJP Book was sufficient for me – but If you’re afraid of failing and need additional exercise perhaps one of the following providers are able to help you ..
Gotchas and Common Pitfalls
I’ve created this list of common pitfalls and gotchas you might encounter during the exam and the test questions in the SCJP study guide and posted some examples for each one..
In real life, 99% of all situations covered would be caught by your IDE – but unfortunately you don’t have one in the exam :)
-
Calling non-static methods or variables from a static context
Watch out if you notice that a non-static variable or method is called from a static context .. e.g. the main method. That the main method is a part of the surrounding class does not mean that you may use the class’ non static variables without creating an instance of the class.package com.hascode.exam; public class Example1 { private final int x = 10; public static void main(String[] args) { x = 11; // fails System.out.println(getX()); // fails new Example1().getX(); // works } public int getX() { return x; } }
-
Trying to change references marked final
Final means final means you’re not allowed to change the reference of the variable marked finalpackage com.hascode.exam; public class Example2 { final Integer i = 10; public static void main(String[] args) { new Example2().doIncrement(); } public void doIncrement() { i++; // fails } }
-
Does the default constructor exist?
When you create a parameterized constructor – the default (non-parameterized) constructor won’t be created automaticallypackage com.hascode.exam; public class Father { public Father(String name) { } } package com.hascode.exam; public class Son extends Father { }
-
String object uses StringBuffer’s methods
That one got me several times when I fast-clicked through the exam simulator from the SCJP book – no the String class has no insert or append method! :)package com.hascode.exam; public class Example3 { public static void main(String[] args) { String str = "test"; str.insert(0, "ing is good"); // StringBuffer's method str.append(" indeed"); // StringBuffer's method } }
-
Usage of non-final objects in a switch statement
No strings until your using Java 7, only constant expressions allowed for the case label!package com.hascode.exam; public class Example4 { public static void main(String[] args) { int x = 4; int y = 5; switch (x) { case 4 : System.out.println("A"); break; case y : // this one does not work System.out.println("B"); break; default : System.out.println("Default"); } // working sample final int z = 5; switch (x) { case 4 : System.out.println("A"); break; case z : // now z is final .. System.out.println("B"); break; default : System.out.println("Default"); } final String str = "test"; switch (str) { // strings do not work at all .. wait for Java 7 :) // [..] } } }
-
Code between try and catch blocks
Putting code between the try and the catch block gives you a CTE.package com.hascode.exam; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; public class Example5 { public static void main(String[] args) { try { BufferedReader reader = new BufferedReader(new FileReader(new File("/tmp/test.dat"))); } System.err.println("test"); // this line is not allowed catch (final FileNotFoundException e) { } } }
-
String operations do not change the calling object
Watch out for string methods that are not reassigned to the string variablepackage com.hascode.exam; public class Example22 { public static void main(String[] args) { String t = "test"; t.replace("te", "this is a te"); // this won't change the String // referenced by t System.out.println(t); // the output is "test" } }
-
Using loop variables outside of the loop
Watch out for invalid variable scopespackage com.hascode.exam; public class Example6 { public static void main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println("iteration: " + i); } System.out.println("total iterations: " + i); } }
-
Do static and non-static methods with identical name and signature exist?
Something like might occur – sometimes the problem is hidden by inheritance e.g. the parent class possesses the static identical methodpackage com.hascode.exam; public class Example7 { public void calculateProgress() { } public static void main(String[] args) { new Example7().calculateProgress(); } public static void calculateProgress() { } }
-
Watch out for the long/short circuit operator
Watch out for the different meaning of the short circuit operator and the long version .. e.g. | vs ||package com.hascode.exam; public class Example19 { public static void main(String[] args) { int x = 3; if (x > 2 & ++x > 3) // no short circuit AND .. pre-increment on x will // be executed x++; if (x % 5 == 0 ^ true) x += 2; if (x > 1 || ++x > 0) // short circuit operator .. pre increment on x // won't be run x += 20; System.out.println(x); // result is 25 } }
-
Watch out for the type erasure in a method’s signature
Be aware of the generics’ type erasure .. after compiling a List<String> is just a List .. that is why the following code sample won’t work :)package com.hascode.exam; import java.util.List; public class Example9 { public void doSomething(List<String> names) { } public void doSomething(List<Integer> userIds) { } }
-
Look out for wait and notify without a synchronized context
Using wait and notify without a correct synchronized context leads to runtime exceptions. Take a look at the wrong example first:package com.hascode.exam; public class Worker extends Thread { private final RobotTask robot; public Worker(final RobotTask robot) { this.robot = robot; } @Override public void run() { System.out.println("Robot calculates .."); try { robot.wait(); } catch (InterruptedException e) { } System.out.println("Thread " + Thread.currentThread().getId() + " finished. result: " + robot.getComputationTime()); } } package com.hascode.exam; public class RobotTask extends Thread { private double computationTime; @Override public void run() { try { Thread.sleep(3000); computationTime = Math.random(); notifyAll(); } catch (InterruptedException e) { } } public double getComputationTime() { return computationTime; } } package com.hascode.exam; public class Example23 { public static void main(String[] args) throws InterruptedException { RobotTask robot = new RobotTask(); for (int i = 0; i < 5; i++) { Worker worker = new Worker(robot); worker.start(); } robot.start(); } }
If we add the correct synchronized context to Worker and RobotTask everything is fine again
package com.hascode.exam; public class Worker extends Thread { private final RobotTask robot; public Worker(final RobotTask robot) { this.robot = robot; } @Override public void run() { System.out.println("Robot calculates .."); synchronized (robot) { try { robot.wait(); } catch (InterruptedException e) { } System.out.println("Thread " + Thread.currentThread().getId() + " finished. result: " + robot.getComputationTime()); } } } package com.hascode.exam; public class RobotTask extends Thread { private double computationTime; @Override public void run() { synchronized (this) { try { Thread.sleep(3000); computationTime = Math.random(); notifyAll(); } catch (InterruptedException e) { } } } public double getComputationTime() { return computationTime; } } package com.hascode.exam; public class Example23 { public static void main(String[] args) throws InterruptedException { RobotTask robot = new RobotTask(); for (int i = 0; i < 5; i++) { Worker worker = new Worker(robot); worker.start(); } robot.start(); } }
The result from the execution could look like this one
Robot calculates .. Robot calculates .. Robot calculates .. Robot calculates .. Robot calculates .. Thread 13 finished. result: 0.010782800524771874 Thread 12 finished. result: 0.010782800524771874 Thread 11 finished. result: 0.010782800524771874 Thread 10 finished. result: 0.010782800524771874 Thread 9 finished. result: 0.010782800524771874
-
Static imports without the member
Take a look at the following sample codepackage com.hascode.exam; import static java.lang.Math; // this won't work /* * the correct way is one of those: * * import static java.lang.Math.*; * import static java.lang.Math.max; * */ public class Example20 { public static void main(String[] args) { max(2, 4); } }
-
Use “add” to add elements to lists,sets or queues but “put” to add elements to a map
The title stands for itself :)package com.hascode.exam; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class Example10 { public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); List<String> list = new ArrayList<String>(); Set<String> set = new HashSet<String>(); list.add("test"); // works set.add("test"); // works, too map.add("test", "test"); // does not work map.put("test", "foo"); // that is how it works } }
-
Don’t try to widen the wrapper classes, don’t widen then box – box and then widen
You must not widen wrapper classes because of the missing inheritance .. e.g.: Float does not extend Double
You’re not allowed to widen and then box .. e.g.: float→double→Double
What you’re able to do is to box first and then widen regarding the is-a relationpackage com.hascode.exam; public class Example13 { public static void main(String[] args) { final Example13 e = new Example13(); Integer i1 = 13; e.processLong(i1); // error: you cannot widen a wrapper into another // wrapper .. // this only works for is-a relations float f1 = 13f; e.processDouble(f1); // error: first widen (float->double), then box // (double->Double) is not possible int i2 = 13; e.processNumber(i2); // works: boxing, then widening is allowed } public void processLong(Long l) { } public void processDouble(Double d) { } public void processNumber(Number n) { } }
-
Know that class cast exceptions occur at runtime
The title says it all .. just take a look at the following example ..package com.hascode.exam; public class Example14 { public static void main(String[] args) { Integer i = 11; new Example14().dieAtRuntime(i); // At runtime we'll get: Exception in // thread "main" // java.lang.ClassCastException: // java.lang.Integer cannot be cast // to java.lang.String } public void dieAtRuntime(Object obj) { String s = (String) obj; } }
-
Learn in which order blocks are initialized: static, non-static, constructor
Watch out for examples using inheritance and a mixture of static/non-static blocks and constructors and ask for the order of execution. The output in the following example marks the order of executionpackage com.hascode.exam; public class Example24 { public static void main(String[] args) { System.out.println("1"); new Child(); System.out.println("18"); } } package com.hascode.exam; public class Child extends Dad { static { System.out.println("6"); } { System.out.println("15"); } public Child() { System.out.println("17"); } { System.out.println("16"); } static { System.out.println("7"); } } package com.hascode.exam; public class Dad extends Grampa { static { System.out.println("4"); } { System.out.println("12"); } public Dad() { System.out.println("14"); } { System.out.println("13"); } static { System.out.println("5"); } } package com.hascode.exam; public class Grampa { static { System.out.println("2"); } { System.out.println("8"); } public Grampa() { this("test"); System.out.println("11"); } public Grampa(String s) { System.out.println("10"); } { System.out.println("9"); } static { System.out.println("3"); } }
-
Watch out for method local inner classes using non-final variables
Method local inner classes are not able to refer to non-final variables inside the method .. common examples are Runnables, EventHandler etc..package com.hascode.exam; public class Example17 { public static void main(String[] args) { new Example17().doSomething(); } void doSomething() { String str1 = "test"; final String str2 = "test"; new Thread(new Runnable() { @Override public void run() { System.out.println(str1); // this one fails - cannot refer to // non final variable System.out.println(str2); // this one works } }).start(); } }
-
An array is an object
That means that an array like int[] is an object and a method with a signature like method(Object obj) is able to retrieve this array as parameter. This might be a problem in an example for overloaded methods .. keep in mind that an array is an object! You should also know that int[] is not the same type as int[][] so watch out for possible class cast exception scenariospackage com.hascode.exam; public class Example28 { public static void main(String[] args) { int[] values = new int[]{}; new Example28().process(values); // output is "Object" int[][] otherValues = new int[][]{}; if (values == otherValues) { // this won't compile - different operand // types } } public void process(long x) { System.out.println("long"); } public void process(Object obj) { System.out.println("Object"); } public void process(long... values) { System.out.println("int..."); } }
-
Watch for hashing collections and know the contract for hashCode() and equals()
Hashing classes from the Java collections API are HashMap, HashSet, LinkedHashMap, LinkedHashSet and Hashtable. You should know that the hashCode() method is used to put the hashed elements in a sort of “bucket”, the equals() method to determine if two objects are equivalent (their meaning, not their references! thats the difference to ==). In addition you should know that if two objects are equal, their hashcodes must be equal, too.
Watch our for a hashed collection that handles objects that do not have well defined hashCode method so that every element added goes into its own bucket and the equals method can’t kick in :)package com.hascode.exam; import java.util.HashSet; import java.util.Set; public class Example29 { public static void main(String[] args) { Set<User> users = new HashSet<User>(); User user1 = new User(1l); User user2 = new User(2l); User user3 = new User(1l); users.add(user1); users.add(user2); users.add(user3); System.out.println(users.size()); // set contains 3 elements // if we uncomment the hashCode method the set is going to contain 2 // elements } private static class User { private Long userId; public User(Long userId) { this.userId = userId; } public Long getUserId() { return userId; } public boolean equals(Object obj) { return getUserId().equals(((User) obj).getUserId()); } // public int hashCode() { // return 12345; // } } }
-
Watch out for non-initialized objects in a method
Take care of non-initialized variables in a method .. they won’t get default values as a classes’ fields might have ..package com.hascode.exam; public class Example18 { public static void main(String[] args) { int x; try { System.out.println(x + 2); } catch (Exception e) { } } }
-
TreeSet/TreeMap needs Comparable elements
Watch out for code that uses TreeSet or TreeMap without elements that implement Comparablepackage com.hascode.exam; public class SomeObject { private int x; public SomeObject(int x) { this.x = x; } } package com.hascode.exam; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; public class Example21 { public static void main(String[] args) { Map<SomeObject, SomeObject> map = new TreeMap<SomeObject, SomeObject>(); Set<SomeObject> set = new TreeSet<SomeObject>(); for (int i = 0; i < 30; i++) { SomeObject obj = new SomeObject(i); map.put(obj, obj); // Exception in thread "main" // java.lang.ClassCastException: // com.hascode.exam.SomeObject cannot be cast to // java.lang.Comparable set.add(obj); // Exception in thread "main" // java.lang.ClassCastException: // com.hascode.exam.SomeObject cannot be cast to // java.lang.Comparable } } }
-
Watch out for abstract methods with a body
This is a common pitfall often hidden in complex class hierarchiespackage com.hascode.exam; public abstract class Example11 { public abstract void doSomething(){}; // no body allowed for abstract methods }
-
Watch out for FileStreams’ close method executed in a finally-block without catching the exception
Unless you’re using Java 7 – and you won’t be using that in the exam – you must be aware that closing a FileInputStream might throw a checked exception. That’s why the close statement in the finally block won’t work this way :)package com.hascode.exam; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; public class Example12 { public static void main(String[] args) { FileInputStream is; try { is = new FileInputStream(new File("/tmp/test.data")); } catch (FileNotFoundException e) { } finally { is.close(); // unhandled exception of type IOException !! } } }
-
Know the rules for Overriding or Overloading
You should know the following rules for overriding and overloading and watch out for methods that disobey these rules.
_Overriding:
_-
The overriding method must have the same return type or (since Java 5) the covariant return
-
The overriding method must have the same argument list as the overriden methode
-
The overriding method must not have a more restrictive access modifier but may have a less restrictive access modifier
-
The overriding method must not throw broader or new checked exceptions but may throw fewer or narrower exceptions
Overloading:
-
The overloading method must have a different argument list
-
The overloading method may have different access modifiers and may throw different exceptions
-
The overloading method may have different return types (but the argument list must differ)
package com.hascode.exam; public class SomeParent { public void doSomething(String test) throws IllegalArgumentException { } public void doAnotherThing(String test) throws IllegalArgumentException { } protected void doAThirdThing(String test) throws IllegalArgumentException { } } package com.hascode.exam; public class Example26 extends SomeParent { /** * fails .. return type must match or be covariant return */ public int doSomething(String test) throws IllegalArgumentException { } /** * fails .. exception is broader than the original exception */ public void doAnotherThing(String test) throws Exception { } /** * works .. NumberFormatException is a narrower exception to the original * IllegalArgumentException and the less restrictive access modifier is * allowed */ public void doAThirdThing(String test) throws NumberFormatException { } }
-
Sources Download
If you want to play around with the examples above you may download them from my GitHub repository or check them out using Mercurial
git clone https://github.com/hascode/hascode-tutorials,git