Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Null Object Pattern in Java

DZone's Guide to

Null Object Pattern in Java

Want to learn more about using the null object pattern in Java? Check out this tutorial to learn how to use the null object pattern with the optional class!

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

Today, I am discussing the less-used pattern called the null object pattern. In object-oriented programming, we deal very frequently with null objects. A null object refers to an object without any reference or an object defined with neutral/null functionality/behavior. These null objects need to be checked to ensure that they are not null while accessing any member or invoking any methods. This is because members or methods typically cannot be invoked on null objects.

Null Object Pattern 

The null object design pattern describes the uses of null objects and their behavior in the system.

  • Null object patterns deal with null objects.

  • Instead of checking for the null object, we define null behavior or call do-nothing behavior.

  • These null objects can also be used to provide default behavior in case data is unavailable.

  • The advantage of this approach over a working default implementation is that a null object is very predictable and has no side effects — it does nothing.

  • The null object pattern can also be used to act as a stub for testing, in case a resource is unavailable for testing.

Before We Use the Null Object Pattern, We Should Understand:

  • This pattern should be used carefully. It can make bugs appear as a normal program execution.

  • We should not implement this pattern just to avoid null checks and make the code more readable. Actually, it is harder to read code that is moved to another place, like the null object class.

  • We have to perform additional testing to make sure that there is nowhere we have to assign a null instead of the null object.

Let's take a look at an example to better understand this pattern.

Example of Null Objects

  • Create an abstract class (or interface) to specify various functionalities. I am using the shape interface for this example. Please note that I have created a method isNull()  as well in the interface. It's nice to have a method, and I like it because I can better identify and control null-defined objects. This method will return false for all of the concrete classes. And, it will return true only for the null object class.

package design.nullobject;

public interface Shape {

double area();
    double perimeter();
    void draw();
    // nice to have method to indicate null object
    boolean isNull();
}


  • You will need to create a concrete class that extends this class or implements the interface). Each concrete class will define a specific version of the functionalities. I am defining three types of the shape:  Circle , Rectangle , and Triangle. These concrete classes will define different types of shapes. Below is the code for the Circle class:

package design.nullobject;

public class Circle implements Shape {
  // sides
    private final double radius;

    public Circle() {
        this(1.0d);
    }   
    public Circle(double radius) {
        this.radius = radius;
    }

    @Override public double area() {
        // Area = π r^2
        return Math.PI * Math.pow(radius, 2);
    }

    @Override public double perimeter() {
        // Perimeter = 2πr
        return 2 * Math.PI * radius;
    }

    @Override public void draw() {
    System.out.println("Drawing Circle with area: " + area() + " and perimeter: " + perimeter());
    }

    @Override
    public boolean isNull() {
    return false;
    }
}


Below is the code for the Rectangle  class:

package design.nullobject;

public class Rectangle implements Shape {
// sides
    private final double width; 
    private final double length;

    public Rectangle() {
        this(1.0d ,1.0d);
    }
    public Rectangle(double width, double length) {
        this.width = width;
        this.length = length;
    }

    @Override
    public double area() {
        // A = w * l
        return width * length;
    }

    @Override
    public double perimeter() {
        // P = 2(w + l)
        return 2 * (width + length);
    }

    @Override 
    public void draw() {
    System.out.println("Drawing Rectangle with area: " + area() + " and perimeter: " + perimeter());
    }

    @Override
    public boolean isNull() {
    return false;
    }
}


Below is the code for the Triangle class:

package design.nullobject;

public class Triangle implements Shape {
    // sides
    private final double a;
    private final double b;
    private final double c;

    public Triangle() {
        this(1.0d, 1.0d, 1.0d);
    }

    public Triangle(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public double area() {
        // Using Heron's formula:
        // Area = SquareRoot(s * (s - a) * (s - b) * (s - c)) 
        // where s = (a + b + c) / 2, or 1/2 of the perimeter of the triangle 
        double s = (a + b + c) / 2;
        return Math.sqrt(s * (s - a) * (s - b) * (s - c));
    }

    @Override
    public double perimeter() {
        // P = a + b + c
        return a + b + c;
    }

    @Override public void draw() {
    System.out.println("Drawing Triangle with area: " + area() + " and perimeter: " + perimeter());
    }

    @Override
    public boolean isNull() {
    return false;
    }
}


  • Now, the most important step is to create a null object class that extends the abstract class or interface and define the do nothing behavior.  The do nothing behavior is like a default behavior in case data is not available.

package design.nullobject;

public class NullShape implements Shape {
// no sides

    @Override
    public double area() {
    return 0.0d;
    }

    @Override
    public double perimeter() {
    return 0.0d;
    }

    @Override
    public void draw() {
    System.out.println("Null object can't be draw");
    }

    @Override
    public boolean isNull() {
    return true;
    }

}


  • Now, we define the Factory class to create various types of shapes. Please refer to (Strategy vs Factory Design Pattern in Java) to understand the factory pattern. I am creating the ShapeFactory  class for this example.

package design.nullobject;

public class ShapeFactory {

public static Shape createShape(String shapeType) {
    Shape shape = null;
    if ("Circle".equalsIgnoreCase(shapeType)) {
    shape = new Circle();
    } else if ("Rectangle".equalsIgnoreCase(shapeType)) {
    shape = new Rectangle();
    } else if ("Triangle".equalsIgnoreCase(shapeType)) {
    shape = new Triangle();
    } else {
    shape = new NullShape();
    }
    return shape;
}
}


To keep the example simple, I have not received parameters for the shape sides in the ShapeFactory method. So, the factory is creating the different Shape object with the fixed side values.

  • And, at the last step, create a Main class to execute and test the code:

package design.nullobject;

import design.nullobject.ShapeFactory;

public class ShapeMain {

    public static void main(String[] args) {
        String[] shapeTypes = new String[] { "Circle", null, "Triangle", "Pentagon", "Rectangle", "Trapezoid"};
        for (String shapeType : shapeTypes) {
            Shape shape = ShapeFactory.createShape(shapeType);
            // no null-check required since shape factory always creates shape objects
            System.out.println("Shape area: " + shape.area());
            System.out.println("Shape Perimeter: " + shape.perimeter());
            shape.draw();
            System.out.println();
        }
    }

}


  • Below is the output of the code:

Shape area: 3.141592653589793
Shape Perimeter: 6.283185307179586
Drawing Circle with area: 3.141592653589793 and perimeter: 6.283185307179586

Shape area: 0.0
Shape Perimeter: 0.0
Null object can't be draw

Shape area: 0.4330127018922193
Shape Perimeter: 3.0
Drawing Triangle with area: 0.4330127018922193 and perimeter: 3.0

Shape area: 0.0
Shape Perimeter: 0.0
Null object can't be draw

Shape area: 1.0
Shape Perimeter: 4.0
Drawing Rectangle with area: 1.0 and perimeter: 4.0

Shape area: 0.0
Shape Perimeter: 0.0
Null object can't be draw


  • In Java 8, we have the  java.util.Optional class to deal with the null references. This class was originally from the Guava API.

Optional

The purpose of the class is to provide a type-level solution for representing optional values instead of using null references. To gain deeper knowledge on using Optional, please refer to Oracle's article Tired of Null Pointer Exceptions? Consider Using Java SE 8's.

Below, I am going to demonstrate some of the useful APIs of  java.util.Optional. There are several static APIs to creating Optional objects:

  •  Optional.empty() : To create an empty Optional object, use the empty API:

@Test
public void optionalEmptyTest() {
    Optional<Shape> empty = Optional.empty();
    assertFalse(empty.isPresent());
}


  •  Optional.of(): To create a non-empty Optional object, if we are sure that we have an object that we would like to make it Optional, use the API. If you call this API using null, it will throw the null pointer exception ( NullPointerException ).

@Test
public void optionalOfTest() {
  Shape shape = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.of(shape);
  assertTrue(nonEmpty.isPresent());
}

@Test(expected = NullPointerException.class)
public void optionalOfWithNullTest() {
  Shape shape = null;
  Optional.of(shape);
}


  •  Optional.ofNullable(): In the code above, both APIs are useful when we are on empty or non-empty. If we are not sure about the object, the object may or may not be null. For this, use the ofNullable API.

@Test
public void optionalOfNullableTest() {
Shape shape1 = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.ofNullable(shape1);
  assertTrue(nonEmpty.isPresent());

  Shape shape2 = null;
    Optional<Shape> empty = Optional.ofNullable(shape2);
  assertFalse(empty.isPresent());
}


Additional APIs

  •  isPresent(): This method returns true if, and only if, the object wrapped in the Optional is not-empty (not null).

@Test
public void optionalIsPresentTest() {
Shape shape = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.of(shape);
  assertTrue(nonEmpty.isPresent());

    Optional<Shape> empty = Optional.empty();
  assertFalse(empty.isPresent());
}


  •  ifPresent():  This enables us to run code on the wrapped value if it is found to be non-null. 

@Test
public void optionalIfPresentTest() {
Shape shape = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.of(shape);
  nonEmpty.ifPresent(circle -> circle.draw());

    Optional<Shape> empty = Optional.empty();
  empty.ifPresent(circle -> circle.draw());
}


  •  get(): This returns a value if the wrapped object is not null. Otherwise, it throws a no such element exception ( NoSuchElementException ).
@Test
public void optionalGetTest() {
Shape shape = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.ofNullable(shape);
  assertNotNull(nonEmpty.get());
}

@Test(expected = NoSuchElementException.class)
public void optionalGetWithNullTest() {
  Shape shape = null;
    Optional<Shape> empty = Optional.ofNullable(shape);
  empty.get();
}


  •  orElse(): This is used to retrieve the object wrapped inside the Optional instance. The method has one parameter as a default object. With orElse , the wrapped object is returned if it is present, and the argument given to  orElse is returned if the wrapped object is absent.

@Test
public void optionalOrElseTest() {
 Shape shape = ShapeFactory.createShape("Rectangle");
Shape shape1 = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.of(shape1);
  assertEquals(shape1, nonEmpty.orElse(shape));

    Optional<Shape> empty = Optional.empty();
  empty.ifPresent(circle -> circle.draw());
  assertEquals(shape, empty.orElse(shape));
}


  •  orElseGet() : This method is similar to orElse. The only difference is, instead of taking an object to return if the Optional  object is not present, it takes a supplier functional interface, which is invoked and returns the value of the invocation.

@Test
public void optionalOrElseGetTest() {
    Shape shape = ShapeFactory.createShape("Rectangle");
    Shape shape1 = ShapeFactory.createShape("Circle");   
    Optional<Shape> nonEmpty = Optional.of(shape1);
    assertEquals(shape1, nonEmpty.orElseGet(() -> ShapeFactory.createShape("Rectangle")));

    Optional<Shape> empty = Optional.empty();
    empty.ifPresent(circle -> circle.draw());
    // comparing the area of the shape since orElseGet will create another instance of rectangle
    assertEquals(shape.area(), empty.orElseGet(() -> ShapeFactory.createShape("Rectangle")).area(), 0.001d);
}


  •  orElseThrow(): This is similar to  orElse . The only difference is it adds a new approach for handling an absent value. Instead of returning a default value when the wrapped object is absent, it throws a given (as parameter) exception.

@Test(expected = IllegalArgumentException.class)
public void optionalOrElseThrowWithNullTest() {
  Shape shape = null;
  Optional<Shape> empty = Optional.ofNullable(shape);
  empty.orElseThrow(IllegalArgumentException::new);
}


Now, we can write the ShapeFactory and Main using Optional, which is demonstrated below:

package design.nullobject;

public class ShapeFactoryJava8 {

public static Optional<Shape> createShape(String shapeType) {
          Shape shape = null;
          if ("Circle".equalsIgnoreCase(shapeType)) {
              shape = new Circle();
          } else if ("Rectangle".equalsIgnoreCase(shapeType)) {
              shape = new Rectangle();
          } else if ("Triangle".equalsIgnoreCase(shapeType)) {
              shape = new Triangle();
          } else {
              // no need to have NullShape anymore
              shape = null;
          }
      // using ofNullable because shape may be not null.
          return Optional.ofNullable(shape);
}
}


And, the Main class to execute and test the code:

package design.nullobject;

import java.util.Arrays;
import java.util.Optional;

public class ShapeMainJava8 {

    public static void main(String[] args) {
        String[] shapeTypes = new String[] { "Circle", null, "Triangle", "Pentagon", "Rectangle", "Trapezoid"};
        Arrays.asList(shapeTypes).stream().forEach(shapeType -> {
            Optional<Shape> optionalShape = ShapeFactoryJava8.createShape(shapeType);
          optionalShape.ifPresent((shape) -> {
            // null-check is done by ifPresent of Optional
            System.out.println("Shape area: " + shape.area());
            System.out.println("Shape Perimeter: " + shape.perimeter());
            shape.draw();
            System.out.println();
            });
        });
    }

}


Below is the output of the code:

Shape area: 3.141592653589793
Shape Perimeter: 6.283185307179586
Drawing Circle with area: 3.141592653589793 and perimeter: 6.283185307179586

Shape area: 0.4330127018922193
Shape Perimeter: 3.0
Drawing Triangle with area: 0.4330127018922193 and perimeter: 3.0

Shape area: 1.0
Shape Perimeter: 4.0
Drawing Rectangle with area: 1.0 and perimeter: 4.0


Like this article? Don't forget to press the like button. Happy coding!


Need more articles on Design Patterns? Below are some of them I have shared with you.

Some additional Articles:

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
java ,null object pattern ,design patterns

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}