Lesson Wednesday

As mentioned towards the end of the previous lesson, Interfaces are frequently used in combination with Inheritance. And inheritance is almost always mentioned in the same breath as polymorphism. Let's take a look at both of these concepts.

Polymorphism

The term polymorphism originally comes from the field of biology. It means a species can have many similar forms or stages. Interestingly enough, this concept is also applied to object-oriented programming. In programming, the definition is similar: It means objects can have many similar, yet slightly different forms.

In Java, this means creating classes that inherit properties from other classes. They're considered polymorphic because they're similar, yet still slightly different.

A class that inherits functionality from another class is called a subclass, or child class. A class that is inherited is called a superclass, or parent class. Subclasses can define their own unique behaviour, yet share functionality of the parent class. They also have the option to override any methods they inherit from the superclass.

Inheritance

When we are discussing child and parent, or sub and super classes, we are talking about Inheritance - the state of a class being able to inherit properties and methods from a different class. There is a lot to say about this topic. But to give a brief introduction, it means the following. Let's look at this POJO:

src/main/java/models/Student.java
public class Student {

   private String firstName;
   private String lastName;
   private String address;
   private String dateOfBirth;
   private String studentId;

   public Student(String firstName, String lastName, String address, String dateOfBirth, String studentId) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.address = address;
       this.dateOfBirth = dateOfBirth;
       this.studentId = studentId;
   }
}

The Student object here can hold all of the properties above. Great.

But what if we wanted to store more specific information about different kinds of students? Math students might have access to the math lab, but anthropology students would not. Also, Anthropology students have 24hr access to the library, but would not be able to take study abroad trips to France (French students can, but not Spanish students - they go to Spanish-speaking countries), and so on.

We could account for these different properties in our one big Student object, OR we could create smaller sub-models that inherit basic properties from our Student.java model, and add them to the unique qualities of a Math, Anthropology or French student.

This is inheritance.

Inheritance of Object

This may sound complex. However, we've actually already been using concepts of inheritance without even realizing it! Remember when we discussed overriding the equals method? We do this in order to successfully compare objects to one another. But why do we override this method instead of simply defining one ourselves?

Well, all Java classes inherit from the built-in Object class. Take a brief moment to look at the documentation for that class here. Notice, specifically, that it defines an equals() method. This is the equals() method we've been overriding, as you may already recognize.

Inheritance Rules

There are several rules to inheriting classes in Java:

  • In Java, a class can only inherit one other class. (Besides Object, which all classes inherit from by default).

  • To inherit a class, you must use the extends keyword (There is, however, one exception to this: The Object class is automatically inherited into all classes without using extends. That's simply how Java is built.)

  • When a parent (or super) class is inherited into a child class, the child class only has access to non-private fields and methods. Therefore, any information you want another class to inherit should be declared public.

  • If necessary, you can override a method inherited from the parent class in the child class, using the @Override annotation. However, the method must have the same method signature in order to do this. (reminder: the method signature is simply the unique combination of the method's name, return type, parameter names and types.)

We can therefore do something like this:

src/main/java/models/MathStudent.java
public class MathStudent extends Student {

   private boolean mathLabAccess = true;
   private String currentMathclass;
   private boolean mathLetesMember = true;


   public MathStudent(String firstName, String lastName, String address, String dateOfBirth, String studentId, boolean mathLabAccess, String currentMathclass, boolean mathLetesMember) {
       super(firstName, lastName, address, dateOfBirth, studentId);
       this.mathLabAccess = mathLabAccess;
       this.currentMathclass = currentMathclass;
       this.mathLetesMember = mathLetesMember;
   }
}

See the extends keyword? This indicates that this class is a child class of the class Student. See where it says super(firstName, lastName, address, dateOfBirth, studentId)?

This is an important and confusing moment, but it basically means that the constructor for MathStudent first calls the constructor for Student on the phone, and tells it to first make a Student object right there and then, and then we set the new, specific properties on that Student object that just got made.

Sweet. Now, we would have access to properties defined in Studentand MathStudent. This is awesome - it allows us to write code more flexibly and in a more focused way.

But what if a Student is a double major and is studying both French and Anthropology? Now, we would actually have a problem, as Java only allows a class to inherit from one parent class. Oh no! But we can simulate multiple inheritance by implementing an interface and inheriting a class. We won't worry too much about multiple inheritance for the purposes of our class, but this StackOverflow post contains a great example of this, for those interested in learning more. Here's what that might look like:

Implementing an Interface and Extending a Class

Interface:

src/main/java/TwoMajors.java
public interface twoMajors { //note I am NOT a class, I am an interface!

   public ArrayList<String> reportAccessCorrectly();
   public ArrayList<String> canDoStudyAbroadWhere();

}

To implement an interface into a class, we simply use the implement keyword like this:

src/main/java/TwoMajors.java
public class DoubleMajor extends Student implements twoMajors{ //class extends and implements!

   boolean doubleMajor = true;
   String majorOne;
   String majorTwo;
   String mathLabAccess;
   String travelAbroad;

   public DoubleMajor(String firstName, String lastName, String address, String dateOfBirth, String studentId, String majorOne, String majorTwo, String mathLabAccess, String travelAbroad) {
       super(firstName, lastName, address, dateOfBirth, studentId);
       this.majorOne = majorOne;
       this.majorTwo = majorTwo;
       this.mathLabAccess = mathLabAccess;
       this.travelAbroad = travelAbroad;
   }

   @Override
   public ArrayList<String> reportAccessCorrectly() { //i gotta be here
       ArrayList<String> accessList = new ArrayList<>();

       //logic to calculate list of places student has access to
       return accessList;
   }

   @Override
   public ArrayList<String> canDoStudyAbroadWhere() { //me too
       ArrayList<String> studyAbroad = new ArrayList<>();

       //logic to calculate all parts of world student can do study abroad.
       return studyAbroad;
   }
}

We can even do this:

src/main/java/TwoMajors.java
public class DoubleMajor extends Student implements twoMajors, Noisy{

   boolean doubleMajor = true;
   String majorOne;
   String majorTwo;
   String mathLabAccess;
   String travelAbroad;

   public DoubleMajor(String firstName, String lastName, String address, String dateOfBirth, String studentId, String majorOne, String majorTwo, String mathLabAccess, String travelAbroad) {
       super(firstName, lastName, address, dateOfBirth, studentId);
       this.majorOne = majorOne;
       this.majorTwo = majorTwo;
       this.mathLabAccess = mathLabAccess;
       this.travelAbroad = travelAbroad;
   }

   @Override
   public ArrayList<String> reportAccessCorrectly() {
       ArrayList<String> accessList = new ArrayList<>();

       //logic to calculate list of places student has access to
       return accessList;
   }

   @Override
   public ArrayList<String> canDoStudyAbroadWhere() {
       ArrayList<String> studyAbroad = new ArrayList<>();

       //logic to calculate all parts of world student can do study abroad.
       return studyAbroad;
   }

   @Override //remember our noisy() interface??
   public void angryNoise() {
       System.out.println("I AM SO OVERWORKED!!");
   }

   @Override
   public void happyNoise() {
       System.out.println("I AM SO INTELLECTUALLY STIMULATED!!");
   }

}

Boom! We have extended the Student class, and implemented first one, then two interfaces! We can implement as many interfaces as we want.

Interfaces vs. Inheritance

But how is an interface different than inheriting from another class? And how to do you choose between one or the other? The difference between inheriting a class and implementing an interface is such a common Java interview question that the entire lesson after next is dedicated to discussing this; in order to best prepare you for this inevitable interview question.

For more information on interfaces, check out this in-depth article from JavaWorld

Recap

  • Interfaces allow us to separate what functionality a class should have from how it is implemented.
  • Interfaces allow us to clarify which classes MUST implement functionality. If we slip up and don't write the methods we need to, we'll get a compiler error.
  • We can only inherit from one class at a time. But interfaces allow us to create symbolic links between classes, ensuring that we accurately provide functionality - by implementing Noisy for both the Elephant, the Dog, and the Double Major student, we are making a statement that each of these classes can and should have the same ability, to express themselves audibly in a loud way.

For our project this week, we will be creating and implementing an interface that will remind us which functionality we have to implement in order to successfully connect to our database and retrieve and write information.