SCJP/OCPJP Exam Preparation: Pitfalls to avoid

April 25th, 2011 by

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 :)

  1. 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;
     }
    }
  2. Trying to change references marked final
    Final means final means you’re not allowed to change the reference of the variable marked final

    package com.hascode.exam;
     
    public class Example2 {
     final Integer    i    = 10;
     public static void main(String[] args) {
     new Example2().doIncrement();
     }
     
     public void doIncrement() {
     i++; // fails
     }
    }
  3. Does the default constructor exist?
    When you create a parameterized constructor – the default (non-parameterized) constructor won’t be created automatically

    package com.hascode.exam;
     
    public class Father {
     public Father(String name) {
     }
    }
     
    package com.hascode.exam;
     
    public class Son extends Father {
    }
  4. 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
     }
    }
  5. 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 :)
     // [..]
     }
     }
    }
  6. 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) {
     }
     }
    }
  7. String operations do not change the calling object
    Watch out for string methods that are not reassigned to the string variable

    package 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"
     }
     
    }
  8. Using loop variables outside of the loop
    Watch out for invalid variable scopes

    package 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);
     }
    }
  9. 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 method

    package com.hascode.exam;
     
    public class Example7 {
     public void calculateProgress() {
     }
     
     public static void main(String[] args) {
     new Example7().calculateProgress();
     }
     
     public static void calculateProgress() {
     }
    }
  10. 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
     }
    }
  11. 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) {
     }
    }
  12. 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
  13. Static imports without the member
    Take a look at the following sample code

    package 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);
     }
    }
  14. 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
     }
    }
  15. 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 relation

    package 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) {
     }
    }
  16. 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;
     }
    }
  17. 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 execution

    package 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");
     }
    }
  18. 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();
     
     }
    }
  19. 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 scenarios

    package 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...");
     }
     
    }
  20. 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;
     // }
     }
    }
  21. 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) {
     }
     }
    }
  22. TreeSet/TreeMap needs Comparable elements
    Watch out for code that uses TreeSet or TreeMap without elements that implement Comparable

    package 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
     }
     }
     
    }
  23. Watch out for abstract methods with a body
    This is a common pitfall often hidden in complex class hierarchies

    package com.hascode.exam;
     
    public abstract class Example11 {
     public abstract void doSomething(){}; // no body allowed for abstract methods
    }
  24. 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 !!
     }
     }
    }
  25. 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 Bitbucket repository or check them out using Mercurial

hg clone https://bitbucket.org/hascode/hascode-tutorials

Resources

Tags: , , , , , , , ,

Search
Tags
Categories