Java SE 8/11 Programmer II: Design Patterns and Principles

Introduction

This section covers the OCP Java SE 8 Programmer II exam objectives: design an interface, define functional interfaces, implement polymorphism, create and use singleton classes and immutable classes.

Designing an Interface

Let’s revise the interface rules in Java 8.

An interface may extend another interface, and in doing so it inherits all of the abstract methods

All interface methods are public and abstract by default, except for all non‐static and non‐default methods. On the other hand, the class implementing the interface must provide the proper modifiers.

An interface may include constant public static final variables, default methods, and static methods.

Default methods are optionally overridden in concrete classes. When you extend an interface that contains a default method, you can do the following (Java Docs):

  • Not mention the default method at all, which lets your extended interface inherit the default method.
  • Redeclare the default method, which makes it abstract.
  • Redefine the default method, which overrides it.

Interfaces cannot extend classes, nor can classes extend interfaces. They cannot be marked final or instantiated directly.

Functional Interfaces

The goal of this section is to identify valid and invalid functional interfaces. First, let’s look at the definition of a functional interface:

Each functional interface has a single abstract method to which the lambda expression’s parameter and return types are matched or adapted, Java Docs.

In this example, BodyMassIndex is a valid interface as it has a single abstract method.

 //User defined functional interface 
 @FunctionalInterface
 interface BodyMassIndex  { 
    double calculate(double weight, double height); 
 }

@FunctionalInterface annotation is used to ensure that the functional interface can’t have more than one abstract method. Applying the annotation to an interface, which has two abstract methods, would result in a compiler error

@FunctionalInterface interface BodyMassIndex  
{ 
  double getCalories();
  double calculate(double weight, double height);
}

Valid and invalid functional Interfaces

Interface A is a functional interface as it has a single abstract method

@FunctionalInterface
interface A {
    int run();
}

B is also a functional interface. It inherits run() method from the interface A. Therefore, it has a single abstract method.

@FunctionalInterface
   interface B extends A {
}

D is a valid functional interface for the same reasons above

@FunctionalInterface
interface D extends A, B { }

C is not a functional interface as it has “two” abstract methods: run() and lift(). run() is inherited from the interface A. If it is marked with the @FunctionalInterface, this will result in a compile error.

interface C extends A {
    void lift();
}

Polymorphism

Polymorphism is the ability of a single interface to support multiple underlying forms. It enables one object to take on many different forms.

Java object may be accessed using a reference with the same type as the object, a reference that is a superclass of the object, or a reference that defines an interface that the object implements, either directly or through a superclass.

Rules to distinguish between object and reference:

  1. The type of an object determines which properties exist within the object in memory.
  2. The type of the reference to the object determines which methods and variables are accessible to the Java program.

Casting

Here are some basic rules to keep in mind when casting variables:

  1. Casting an object from a subclass to a superclass doesn’t require an explicit cast.
  2. Casting an object from a superclass to a subclass requires an explicit cast.
  3. The compiler will not allow casts to unrelated types.
  4. Even when the code compiles without issue, an exception may be thrown at runtime if the object being cast is not actually an instance of that class

Let’s have a look at the third rule, which is quite important for the exam

Parent p = new Child(); // valid upcasting
Child c = (Child) p; // Valid downcasting

Parent p1 = new Parent();
Child c1 = (Child) p1; // Compiles but throws java.lang.ClassCastException

Casting is not without its limitations. Even though two classes share a related hierarchy, that doesn’t mean an instance of one can automatically be cast to another

class Child extends Parent{}

Parent p = new Child();
Child c = (Child) p; // Valid downcasting
 
Parent p = new Parent();
Child c = (Child) p; // Compiles but throws ClassCastException

Design Patterns

In this section, we will cover two design patterns: immutable object pattern and singleton pattern. You need to be able to create and use singleton classes and immutable classes for the OCP 8 exam.

Immutable Object Pattern

An object is considered immutable if its state cannot change after it is constructed. Since they cannot change state, they are thread-safe. The following rules define a simple strategy for creating immutable objects.

  1. Use a constructor to set all properties of the object
  2. Mark all of the instance variables private and final
  3. Don’t define any setter methods
  4. If the instance fields include references to mutable objects, don’t allow those objects to be changed
  5. Prevent methods from being overridden

Let’s create a mutable class and transform it into an immutable class. In this example, all rules are passed except for rule 4. Since the List<> interface is mutable, the client of the function can modify the state of the list. Note that the String class is immutable. Therefore, we don’t have to worry about rule 4.

public final class WorkoutMutable {

    private final String type;
    private final List<String> exercises;

    public WorkoutMutable(String type, List<String> exercises) {
        if (type.isEmpty() && exercises == null)
            throw new RuntimeException("Workout type and exercise list are    required");
        this.type = type;
        this.exercises = new ArrayList<String>(exercises);
    }

    //String class is immutable so we don't have to worry about rule 4
    public String getType() {
        return type;
    }
     
    //rule 4 failed
    public List<String> getExercises() {
        return exercises;
    }

}

We need to make sure that no references to the “exercises” object are publicly available. If the user does need access to the data in the List, we can create a one‐time copy of the data that is returned to the user. We can replace the getExercises with getCopyOfExercises.

public List<String> getCopyOfExercises() {
    List<String> copyOfExercises = new ArrayList<>(exercises);
    Collections.copy(copyOfExercises, exercises);
    return copyOfExercises;
}

Singleton Pattern

A singleton is simply a class that is instantiated exactly once. The main reason to use singleton is that we want a single instance of a particular object in the memory. The common approaches are based on creating three main elements in the class:

  • private constructor to guarantee exactly one instance will exist once the class is initialized
  • private static member to make sure that the instance is only accessible only within its own class
  • public static function to access the single instance

Approach 1: Instantiate the singleton object directly in the definition of the instance reference

In this approach, we create a private static final variable in the class, usually name it as “instance”. All calls to Workout.getInstance return the same object reference, and no other Workout instance will be created.

public class Workout {

    //Create private static final variable in the class
    private static final Workout instance = new Workout();

    //Create private constructor
    private Workout() { }

    //Access the instance via static method, name it as getInstance()
    public static Workout getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        Workout w1 = Workout.getInstance();
        Workout w2 = Workout.getInstance();
        System.out.println(w1.equals(w2));
    }
}

Approach 2: Static initialization block

In this approach, we create a singleton using a static initialization block instead of using private constructor. A static initialization block allows additional steps to be taken to set up the singleton after it has been created.

public class Workout {

    private static final Workout instance;

    //Create a singleton using a static initialization block
    static {
        instance = new Workout();
    }

    //Access the instance via static method
    public static Workout getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        Workout w1 = Workout.getInstance();
        Workout w2 = Workout.getInstance();
        System.out.println(w1.equals(w2));
    }
}

Approach 3: Lazy Instantiation

Lazy instantiation is different from previous approaches in that we delay the creation of the singleton until it is requested by the client. In other words, we do not create the singleton object when the class is loaded.

public class Workout {

    private static Workout instance;
    private Workout(){ }

    public static Workout getInstance() {
        if (instance == null) {
            instance = new Workout();
        }
        return instance;
    }

    public static void main(String[] args) {
        Workout w1 = Workout.getInstance();
        Workout w2 = Workout.getInstance();
        System.out.println(w1.equals(w2));
    }
}

References

  • OCP: Oracle Certified Professional Java SE 8 Programmer II Study Guide, by Jeanne Boyarsky
  • Effective Java, Third Edition, by Joshua Bloch
  • The Java Tutorials

Leave a Reply

Your email address will not be published. Required fields are marked *