Java Keywords (Part V): Classes vs Interfaces

This is not a break in my Java Keyword series. This is a continuation of the series that focuses in two things. The primary goal is to explain what an interface is (which differentiates them from classes) and go over a few examples that illustrate why interfaces are not just needed, but essential for robust and reusable software solutions.

Because of new improvements introduced with Java 8, I will also introduced the concept of default methods; although will do so in a separate article... A second part to this one. I feel doing that is necessary for keeping the scope of this article as narrow as possible for the sake of simplicity and clarity. Interestingly enough, how to use interfaces will also be covered in a separate article where I will be discussing the Object-Oriented concept of inheritance. In the new article, I will be emphasizing in the keywords extends, implements, and super.

The Java keyword list has 18 keywords grayed out. This article will only cover the interface keyword. That puts us at 38% of keywords covered by these series of articles. Amazingly, that's almost sufficient to built simple applications. I suggest that if have not read any of the articles in Java Keyword series, you read them before proceeding further. Also, go back and read the one about Data Types. All of these articles are from September 2018. That should help you find them quickly.

Java keyword list

abstract continue for new switch
assert default goto* package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const* float native super while
Keyword marked with an asterisk (*) are keywords that, although valid, are not used by programmers.

Although the list above is arranged in alphabetical order, I will go through them in a different order.

What is an interface?

An interface is an abstract data type that is used to specify a behavior that classes must implement. Before delving into what this means, let us examine the most basic form of an interface in Java.


public interface Pet
{
    // internal details omitted (for the moment)
}
Just like classes, in Java, interfaces are declared using a specific keyword. In this case, the interface keyword is used as we can see from the example above. So, classes are declared using the class keyword and interfaces are declared using the interface keyword. Easy enough. However, there are a few other characteristics you have to be aware of. If you remember from Java Keywords (Part II) article, I discussed the concept of an abstract class. What I didn't mention at the time (and didn't mention in the Classes and Objects article), is that abstract classes CANNOT be instantiated at all... not with the new operator, and not with any other object construction mechanism. If you really think about this, it makes perfect sense. Something "abstract" cannot be easily defined. Something "abstract" is just a concept, not a thing and objects are specific things.

The interface I defined above is called "Pet." We can all agree that some animals can be pets. For the sake of simplicity, let's not get into the concept of breeds. Now, we can also agree that (for example) not all dogs are pets; for one reason or another. Since, by definition, an interface is used to specify behavior, we can use the concept of interfaces to "inject" certain behavior characteristics of pets that are not exhibited in other types of animals. This is all so confusing....


public class Dog
{
    // Details omitted
}

public class Cat
{
    // Details omitted
}

public interface Pet
{
    void learnTrick(String trickName);
    void doTrick(String trickName);
    void giveName(String name);
    void respondByName(String trickName);
    void dressUp();
}

The Pet interface illustrated above contain 4 abstract methods. However, differently than abstract classes, with interface the use of abstract keyword is not necessary when declaring abstract methods. Why? Because interfaces can only contain two types of methods: abstract methods (shown above) and default methods (covered in another article). I will also go in more detail regarding the use of access modifiers with interface methods at a later time. Additionally, methods in an interface are inherently public. Therefore, omitting the use of the keyword public does not make the methods package-private (or default access) as you learned in Part I of this series. This is only true of interface methods. Lastly, interfaces are incapable of containing variables like classes do. Only constants can be declared in interfaces and even this is currently considered bad practice.

In my example, I simply want to illustrate that there is a difference between animals (in this case dogs) that are not pets and those that are. For example, we often teach our pets tricks so that they could do those tricks later on for our amusement. We also give our pets names and they respond when we call them by that name. In contrast, a dog that has never been a pet, like a stray dog, does not know any tricks and do not come to us when we call them. But, being a pet is not something exclusive to dogs. Cats can also be pets too. And these pet cats can learn tricks, do tricks, be given a name, respond by its name, and be dressed up.


Pet myPet = new Dog();
Pet yourPet = new Cat();

In case I didn't mention this before, when creating object references, the portion to the left of the equals sign is used to declare the object's Type. The portion of the right of the equals sign defines the object being created. In order for this to be possible, the two must be compatible. This means that objects of type "Dog" must also be of type "Pet." The same goes for "Cat" objects. Why is this important? How will this help me create robust and reusable software solutions? I admit that this example is very trivial. But, I am glad you asked...

Suppose you have a requirement that you must maintain a list of names. In Java, you can use the class java.util.ArrayList which lists items in the order they are inserted and accept duplicate entries. Assuming that two people with the same name exist, the following is correct:


ArrayList myList = new ArrayList();
myList.add("John");
myList.add("Mary");
myList.add("Hector");
myList.add("John");

The above list contains four names: Hector, Mary, and John twice. Later on, this requirement changes and now your list must be sorted alphabetically AND cannot accept any duplicates. If this happens, your code will break because it depends EXCLUSIVELY on how a java.util.ArrayList behaves. For this reason, Java created another class called java.util.TreeSet that behaves differently. Now, in case you are wondering "what does that have to do with this example?", the answer is simple: Both classes implement the java.util.Collection interface. One of the methods in this interface is the add method.


Collection myList = new ArrayList();
myList.add("John");
myList.add("Mary");
myList.add("Hector");
myList.add("John");

Replacing ArrayList on the right of the equals sign with Collection allows me to refer to myList as a generic collection and not a strict ArrayList.


Collection myList = new TreeSet();
myList.add("John");
myList.add("Mary");
myList.add("Hector"); // Will be inserted before John
myList.add("John"); // Will not be inserted because it is a duplicate entry

Replacing ArrayList on the left of the equals sign with TreeSet allows me to change the behavior of myList without breaking the code that inserts values into the collection. The end result of this code is that, the ArrayList will have four elements shown in the order they were inserted, whereas the TreeSet collection will have three elements sorted in natural order (alphabetically in this case).

Summary

Interfaces makes it possible to inject behaviors that are exclusive to certain members of a given category and allows us to refer to objects by this generic type. The end result, as shown in the array list and tree set example is that we are able to change the implementation of a concrete class without affecting the code; thus allowing us to make a type replacement without modifying existing code. This means I can refer to array lists and tree sets as simple "collections" and the supporting code will work according to the specific implementation of this collection without breaking the code. Therefore, when going the opposite way, from "collection" to array list or tree set, the "myCollection" will behave as the specific type of "collection."

Next up, Part VI: If/Else Flow-Control Statements

Comments

  1. All I gather is that inheritance allow child class to define methods and functions within their boundaries, which doesn't affect the codes from the parent nor others child classes. Which is useful especially if the parent class is more general that it need multiple child classes.

    ReplyDelete
    Replies
    1. That is true. But there is much more to inheritance. Inheritance could also have adverse effects. One of such effects is the forcing changes down the hierarchy. For example, if you have to add an additional argument to a protected method, ALL of the sub-classes are adversely impacted. For this reason, composition is preferred over inheritance.

      https://professorfontanez.blogspot.com/2016/02/aggregation-and-composition.html

      With inheritance, the key question that should be answered is the following "is the subclass a type of the super class?" If the answer is yes, then it make sense to use inheritance. HOWEVER, I would try to implement an interface before inheriting from a super class. Also, avoid deep inheritance trees. Some people say it should never be more than 3 to 4 levels deep. And remember, the top level of all classes is Object.

      Delete

Post a Comment

Popular posts from this blog

Implementing Interfaces with Java Records

Customizing Java Records

Exception Handling: File CRUD Operations Example