VOOZH about

URL: https://dzone.com/articles/problems-with-inheritance-in-java

⇱ Problems With Inheritance in Java


Related

  1. DZone
  2. Coding
  3. Java
  4. Problems With Inheritance in Java

Problems With Inheritance in Java

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

By Jul. 10, 18 · Tutorial
Likes
Comment
Save
35.7K Views

Join the DZone community and get the full member experience.

Join For Free

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


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.

Inheritance (object-oriented programming) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • A Guide to Constructor Chaining in Java
  • Achieving Inheritance in NoSQL Databases With Java Using Eclipse JNoSQL
  • Top Java Security Vulnerabilities and How to Prevent Them in Modern Java
  • A Spring Boot App With Half the Startup Time

Partner Resources

×

Comments

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

Let's be friends: