DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality
  • Addressing Memory Issues and Optimizing Code for Efficiency: Glide Case
  • Singleton: 6 Ways To Write and Use in Java Programming

Trending

  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  • MySQL to PostgreSQL Database Migration: A Practical Case Study
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Why High-Performance AI/ML Is Essential in Modern Cybersecurity
  1. DZone
  2. Coding
  3. Languages
  4. Null Object Pattern in Java

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!

By 
Brijesh Saxena user avatar
Brijesh Saxena
DZone Core CORE ·
Aug. 22, 18 · Tutorial
Likes (47)
Comment
Save
Tweet
Share
89.5K Views

Join the DZone community and get the full member experience.

Join For Free

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.

  • Null Object Pattern in Java
  • Using the Adapter Design Pattern in Java

  • Using the Bridge Design Pattern in Java

  • Strategy vs. Factory Design Patterns in Java

  • Decorator Design Pattern in Java

  • How to Use Singleton Design Pattern in Java

  • Singleton Design Pattern: Making Singleton More Effective in Java

Some additional Articles:

  • Java Enums: How to Make Enums More Useful

  • Java Enums: How to Use Configurable Sorting Fields

Object (computer science) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Writing DTOs With Java8, Lombok, and Java14+
  • Redefining Java Object Equality
  • Addressing Memory Issues and Optimizing Code for Efficiency: Glide Case
  • Singleton: 6 Ways To Write and Use in Java Programming

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!