TIL: Inheritance pt2

We’re going to continue where we left off yesterday and discuss a few more things related to inheritance.

Now that we have a good understanding of how inheritance works there’s a few more things to cover to round out our understanding.

Final

When defining your class and methods, you have a variety of keywords available to tag onto definition. This typically would go in front of the type declaration. One of these keywords is final.

What does final do? It tells the compiler that either the class or the method cannot be extended or overwritten, respectively. This can be handy if you’re trying to lock down the class and prevent someone from using it outside of your defined purpose.

Let’s take the examples from the lessons yesterday. Let’s suppose I decide that I don’t want someone to be able to extend the Dog class. I can lock this down by adding a final to the class definition:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
final public class Dog extends Animal {
   private String breed;

   public Dog() {
       setSpecies("K9");
       setLegs(4);
       setSound("Woof!");
   }

   public Dog(String breed) {
       this();
       this.breed = breed;
   }

   public String getBreed() {
       return breed;
   }

   public void setBreed(String breed) {
       this.breed = breed;
   }
}

By adding the final to the entire class, our Shiba class will no longer be able to extend Dog and now starts to throw errors.

We can do the same thing with individual methods as well. Recall from before that we had defined our Shiba class to @Override the makeSound() method from the base Animal class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Shiba extends Dog {
   public Shiba() {
       super("Shiba-Inu");
   }

   @Override
   public String makeSound() {
      return "The Shiba-Inu goes REEEEEE!";
   }

   public String originalSound() {
       return super.makeSound();
   }
}

If I were to go into the base Animal class and mark the makeSound() method as final, we’ll see that we get an error present in the Shiba class for the @Override:

1
2
3
final public String makeSound() {
   return "The " + getSpecies() + " goes " + getSound();
}

Abstract

So just like you can prevent a class from being extended or class being overridden, you can also require that a class be extended or that a method be overridden. That’s where the abstract keyword comes in.

When using abstract, there’s two rules to follow:

  1. If marking a method as abstract you must mark the class as abstract
  2. If marking a class as abstract you DO NOT need to mark any methods as abstract

Number two above is not very common, but it is possible to do.

Using our examples from before, we could change both our Animal and Dog classes to be abstract and our tests would still continue to pass. But what about methods?

When defining an abstract method, you will need to define it without any body. You can define the name, the return types and the accepted parameters, but you cannot define any logic to it. This means that every class that inherits this method will need to define their own logic.

Let’s add in a new abstract method into our base Animal class:

1
abstract public boolean hasLegs();

Notice how there is no body (curly braces) in the definition. If you try to even put them in, even if they’re empty, you will get errors.

Now let’s write our method inside of the Dog class:

1
2
3
public boolean hasLegs() {
   return getLegs() > 0;
}

That will clean up all of errors and allows the method to be specific to the inherited class.

Object Comparisons

This is more of a “good to know” rather than directly related to the inheritance that we’ve been talking about.

As mentioned in yesterday’s post, everything inherits from the Object class. Along with Object comes the equals() method. By default, this equals method will only validate whether two references point to the same object in memory.

That’s to say, if I were to create two different instances to two different Dog objects and then see if they are equal, it would return false:

1
2
3
Dog a = new Dog();
Dog b = new Dog();
a.equals(b); // False!

However, if we tweaked this a little bit and had b pointing to a, then this would be true:

1
2
3
Dog a = new Dog();
Dog b = a;
a.equals(b); // True!

Using @Override like we learned yesterday, we can actually change the equals() method on a per class basis so that it does exactly what we want it to do. So instead of comparing whether or not it’s the exact same object, we could check to see if it’s two Dog classes with the same breed or name or something like that.

Conclusion

That wraps up everything I wanted to talk about regarding inheritance. That should be a pretty good start to get you going. As always, the code from this lesson is available on my github page.

💚 A.B.L.