Liskov Substitution Principle with Java Code Examples

This article describes what is Liskov Substitution Principle along with code examples in Java. Please feel free to comment/suggest if I missed mentioning one or more important points. Also, sorry for the typos.

In this post, you will learn some of the following:

  • What is Liskov Substitution Principle (LSP)?
  • Code Samples to Illustrate LSP
  • What is code quality characteristic that represents by LSP?

You may also want to check another code quality principle such as Single Responsibility Principle explained with Java Examples.



What is Liskov Substitution Principle (LSP)?

Take a look at this paper on Liskov Substitution Principle which provides great details on Liskov Substitution Principle. As per LSP, functions that use references to the base classes must be able to use objects of derived class without knowing it. In simple words, derived classes must be substitutable for the base class. To illustrate LSP, let’s take an example of Rectangle and Square. One tends to establish the ISA relationship, thus, calling Square as a rectangle (Square is a Rectangle). However, there arises a problem (hence, violation of LSP) which shall be demonstrated with the code sample. Take a look at code illustration below to understand LSP in detail.

Pictorially, the comic strip given below should help you understand the Liskov Substitution Principle in an easier manner. Once a class follows inheritance rule, it should be able to be substituted in place of the base class without any change required in the code using the reference of the derived class. This is unlike the comic strip below where a substitute is unable to perform activities of a music teacher for which he got substituted.

Figure 1. A substitute should be able to perform without conditions being applied

Java Code Samples to Illustrate LSP

Liskov Substitution principle is popularly explained using Square and Rectangle example. Let’s assume we try to establish ISA relationship between Square and Rectangle. Thus, we call “Square is a Rectangle”. Below code represents the relationship.

/**
 * Rectangle 
 * @author Ajitesh Shukla
 */public class Rectangle {
 
 private int length;
 private int breadth;
 
 public int getLength() {
  return length;
 }
 public void setLength(int length) {
  this.length = length;
 }
 public int getBreadth() {
  return breadth;
 }
 public void setBreadth(int breadth) {
  this.breadth = breadth;
 } 
 public int getArea() {
  return this.length * this.breadth;
 }
}

Below is the code for Square. Note that Square extends Rectangle.

/**
 * Square class; Square inherits from Rectangle; 
 * Represents ISA relationship - Square is a Rectangle
 * @author Ajitesh Shukla
 */public class Square extends Rectangle {
 
 @Override
 public void setBreadth(int breadth) {
  super.setBreadth(breadth);
  super.setLength(breadth);
 }
 @Override
 public void setLength(int length) {
  super.setLength(length);
  super.setBreadth(length);
 }
}
The code below represents the function which has Rectangle as an argument. As per the principle, the functions that use references to the base classes must be able to use objects of derived class without knowing it. Thus, in the example shown below, the function calculateArea which uses the reference of “Rectangle” should be able to use the objects of derived class such as Square and fulfill the requirement posed by Rectangle definition.

Look at the calculateArea method in the code below. One should note that as per the definition of Rectangle, the following must always hold true given the data below:

  • Length must always be equal to the length passed as the input to method, setLength
  • Breadth must always be equal to the breadth passed as input to method, setBreadth
  • Area must always be equal to product of length and breadth

In case, we try to establish ISA relationship between Square and Rectangle such that we call “Square is a Rectangle”, below code would start behaving unexpectedly if an instance of Square is passed. Assertion error will be thrown in case of check for area and check for breadth, although the program will terminate as the assertion error is thrown due to failure of Area check.

/**
 * The class demonstrates the Liskov Substitution Principle (LSP)
 * 
 * As per the principle, the functions that use references to the base classes must be able to use objects of derived class without knowing it.
 * Thus, in the example shown below, the function calculateArea which uses the reference of "Rectangle" should be able to use the objects of 
 * derived class such as Square and fulfill the requirement posed by Rectangle definition.
 *  
 * @author Ajitesh Shukla
 */public class LSPDemo {
 /**
  * One should note that as per the definition of Rectangle, following must always hold true given the data below:
  * 1. Length must always be equal to the length passed as the input to method, setLength
  * 2. Breadth must always be equal to the breadth passed as input to method, setBreadth
  * 3. Area must always be equal to product of length and breadth
  * 
  * In case, we try to establish ISA relationship between Square and Rectangle such that we call "Square is a Rectangle", 
  * below code would start behaving unexpectedly if an instance of Square is passed
  * Assertion error will be thrown in case of check for area and check for breadth, although the program will terminate as
  * the assertion error is thrown due to failure of Area check.
  *   
  * @param r Instance of Rectangle
  */ public void calculateArea(Rectangle r) {
  r.setBreadth(2);
  r.setLength(3);
  //
  // Assert Area
  // 
  // From the code, the expected behavior is that 
  // the area of the rectangle is equal to 6
  //
  assert r.getArea() == 6 : printError("area", r);
  //
  // Assert Length & Breadth
  //
  // From the code, the expected behavior is that 
  // the length should always be equal to 3 and
  // the breadth should always be equal to 2
  //
  assert r.getLength() == 3 : printError("length", r);
  assert r.getBreadth() == 2 : printError("breadth", r);
 }
 
 private String printError(String errorIdentifer, Rectangle r) {
  return "Unexpected value of " + errorIdentifer + "  for instance of " + r.getClass().getName();
 }
 
 public static void main(String[] args) {
  LSPDemo lsp = new LSPDemo();
  //
  // An instance of Rectangle is passed
  //
  lsp.calculateArea(new Rectangle());
  //
  // An instance of Square is passed
  //
  lsp.calculateArea(new Square());
 }
}

Given the above code, what is the problem with Square-Rectangle ISA relationship?

  • The Square class does not need method such as setBreadth or setLength as the sides of a square are equal. This is wasteful. Imagine of hundreds of thousands of Square objects.
  • LSPDemo class would need to know the details of derived classes of rectangle (such as Square) to code appropriately to avoid throwing error. The change in existing code to take care of derived class breaks the open-closed principle in first place.

What code quality characteristic is represented by LSP?

Following are some of the code quality characteristic which is represented by Liskov Substitution Principle.

  • It is only when derived types are completely substitutable for their base types that functions which use those base types can be reused with impunity, and the derived types can be changed with impunity.
  • LSP is also, at times, termed as “Design by Contract”. Using this scheme, methods of classes declare pre-conditions and post-conditions. The preconditions must be true in order for the method to execute. Upon completion, the method guarantees that the postcondition will be true.
  • Design by Contract does influence the declaration of “throws” exceptions as well as the throwing of runtime exceptions and try/catch in general.


Ajitesh Kumar

I have been recently working in the area of Data analytics including Data Science and Machine Learning / Deep Learning. I am also passionate about different technologies including programming languages such as Java/JEE, Javascript, Python, R, Julia, etc, and technologies such as Blockchain, mobile computing, cloud-native technologies, application security, cloud computing platforms, big data, etc. I would love to connect with you on Linkedin. Check out my latest book titled as First Principles Thinking: Building winning products using first principles thinking.

Recent Posts

Agentic Reasoning Design Patterns in AI: Examples

In recent years, artificial intelligence (AI) has evolved to include more sophisticated and capable agents,…

2 months ago

LLMs for Adaptive Learning & Personalized Education

Adaptive learning helps in tailoring learning experiences to fit the unique needs of each student.…

2 months ago

Sparse Mixture of Experts (MoE) Models: Examples

With the increasing demand for more powerful machine learning (ML) systems that can handle diverse…

3 months ago

Anxiety Disorder Detection & Machine Learning Techniques

Anxiety is a common mental health condition that affects millions of people around the world.…

3 months ago

Confounder Features & Machine Learning Models: Examples

In machine learning, confounder features or variables can significantly affect the accuracy and validity of…

3 months ago

Credit Card Fraud Detection & Machine Learning

Last updated: 26 Sept, 2024 Credit card fraud detection is a major concern for credit…

3 months ago