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

Problems With Inheritance in Java

DZone's Guide to

Problems With Inheritance in Java

Having trouble with proper subclass inheritance in Java? Click here to figure out how to avoid broken code.

· Java Zone ·
Free Resource

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Inheritance is one of the fundamental principles in an object-oriented paradigm, bringing a lot of values in software design and implementation. However, there are situations where even the correct use of inheritance breaks the implementations. This post is about looking at them closely with examples.

Fragility of Inheritance

The improper design of the parent class can leads subclasses of a superclass to use the superclass in unexpected ways. This often leads to broken code, even when the IS-A criterion is met. This architectural problem is known as the fragile base class problem in object-oriented programming systems.

The obvious reason for this problem is that the developer of a base class has no idea of the subclass design. When they modify the base class, the subclass implementation could potentially break.

For example, the following program shows how seemingly an inheriting subclass can malfunction by returning the wrong value.

class Rectangle {
    private int length;
    private int breadth;

    public Rectangle(int length, int breadth) {
        this.length = length;
        this.breadth = breadth;
    }

    public int calculateArea() {
        return length * breadth;
    }

    // getters and setters
}

// Square IS-A Rectangle
class Square extends Rectangle {
    public Square(int side) {
        super(side, side);
    }
}


Now, the following shows how to the test to ensure that the inheritance works fine. 

@Test
public void testSquare() {    
  Square square = new Square(5);    
  assertEquals("Area of square", 25, square.calculateArea());   // fine

  // set breadth of square :(    
  square.setBreadth(9);    
  assertEquals("Area of new square", 81, square.calculateArea());  // ohh nooo
}


Obviously, if I create the instance of Square  and call a method calculateArea, it will give the correct value. But, if I set any of dimension of the square, since the square is a rectangle, it gives the unexpected value for the area and the second assertion fails as below:

java.lang.AssertionError: Area of new square 
Expected :81
Actual :45


Is There Any Solution?

There is no straightforward solution to this problem because this is all about following the best practices while designing architecture. According to Joshua Bloch, in his book Effective Java, programmers should "design and document for inheritance or else prohibit it."

If there is a breakable superclass, it is better to prohibit inheritance by labeling a declaration of a class or method, respectively, with the keyword "final." And, if you are allowing your class to be extended, it is best to only use one way to populate fields.

Here, use either constructors  or setters  but not both.

So, if I remove the setters from the parent class as below:

class Rectangle {
    private int length;
    private int breadth;

    public Rectangle(int length, int breadth) {
        this.length = length;
        this.breadth = breadth;
    }

    public int calculateArea() {
        return length * breadth;
    }

    // getters
}


Then, the child classes can't misuse the setter avoiding fragility issue as:

@Test
public void testSquare() {    
  Square square = new Square(5);    
  assertEquals("Area of square", 25, square.calculateArea());   // fine

  // square.setBreadth(9);    //no way to set breadth :)
}


Inheritance Violates Encapsulation

Sometimes, your private data gets modified and violates encapsulation. This will happen if you are extending features from an undocumented parent class — even though the IS-A criterion is met.

For example, let us suppose A overrides all methods in B by first validating input arguments in each method (for security reasons). If a new method is added to B and A  and is not updated, the new method introduces a security hole.

For example, I have created new HashSet implementation to count the numbers of elements added to it as:

class MyHashSet<T> extends HashSet<T> {
    //The number of attempted element insertions since its creation --
    //this value will not be modified when elements are removed
    private int addCount = 0;

    public MyHashSet() {}

    @Override
    public boolean add(T a) {
        addCount++;
        return super.add(a);
    }

    @Override
    public boolean addAll(Collection<? extends T> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}


Everything looks good. So, it is time to test this extension!

@Test
public void testMyHashSet() {    
  MyHashSet<String> mhs = new MyHashSet<>();    
  mhs.add("A");    
  mhs.add("B");    
  mhs.add("C");    
  assertEquals("Number of attempted adds so far", 3, mhs.getAddCount());

  mhs.remove("B");    
  assertEquals("Number of attempted adds so far even after removal", 3, mhs.getAddCount());  

  mhs.addAll(Arrays.asList("D", "E", "F"));    
  assertEquals("Size of Elements in current set", 5, mhs.size());    
  assertEquals("New number of attempted adds so far", 6,  mhs.getAddCount());  // fails
}


The test fails with a failure in the last assertion as below:

java.lang.AssertionError: New number of attempted adds so far 
Expected :6
Actual :9
Inheritance


The cause of the problem is that in the implementation of  HashSet, addAll  calls the add  method. Therefore, we are incrementing  addCount  too many times in calls to  addAll. 

How to Fix This Issue?

The principle is the same as in an earlier fix: "Design and document for inheritance or else prohibit it." The proper documentation while designing features would reduce the chances of issues like this. 

Fix specific to this issue is not to increment  addCount  in  addAll  operations, since the value is getting updated in add  operation, which gets called from  addAll  as:

class MyHashSet<T> extends HashSet<T> {
    // . . .
    @Override
    public boolean addAll(Collection<? extends T> c) {
        return super.addAll(c);
    }
    // . . .
}


So, this is it! Until next time, happy learning!

As usual, all the source code presented in the above examples is available on GitHub.

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Topics:
java ,inheritance ,tutorial ,fragile base class problem ,constructors ,class ,setters

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}