Setup

One of the main goals of learning inheritance is to teach you how to structure your code. So, we're going to leave a lot of the hacks up to your creativity, and you can make anything you want as long as it follows the structure of inheritance.

Open up a new notebook in your personal fastpages (you can do this in pairs or groups). The hacks in this lesson will be performed from scratch in this notebook.

What is Inheritance, and why is it useful?

Imagine you wanted to create a racing game. You would need a bunch of different cars-bugattis, lamborghinis, rolls royce. Each of these cars would have different features; they would have different fuel types, tires, and engines. A car like an Aston Martin would have a spoiler inside the car for aerodynamics. Lambos have cools doors for when you pull up to a race. But all of these cars would also have a lot in common; all cars have gear shifts, gas, breaks, and steering.

We could model each car with its own class, with attributes and methods specific to each car. But we would find that we're repeating a lot of the same code over and over - it doesn't really make sense to redefine the methods for gas and breaks for every single car, when it will do the same thing.

This is a great use case for inheritance. We can define a base "Car Class" that has the methods and attributes common to every car - steering methods, gas and break methods, and attributes like speed and miles per gallon.

Each car will "extend" from this base class. This means that it "inherits" the methods and attributes in the base Car Class (this is why it's called Inheritance). But each of the new car classes, for example a Bugatti Class, will have its own special methods and attributes.

SuperClasses and Subclasses with Cars

Let's first look at the base "Car Class" we talked about, with the generic methods all cars would have. This is called the Super Class.

Some vocab:

protected is an access modifier so that the attribute isn't affected by outside modifiers.

public class Car {
    protected String brandName;
    protected double range;
    protected double doorNumber;
    protected double maxSpeed;
    
    // Constructor for the attributes present in the superclass
    public Car(String brandName, double range, double doorNumber, double maxSpeed) {
        this.brandName = brandName;
        this.range = range;
        this.doorNumber = doorNumber;
        this.maxSpeed = maxSpeed;
    }
    
    public void gas () {
        System.out.println("Go!");
    }
    
    public void brake () {
        System.out.println("Stop!");
    }
    
    public void gearShift () {
        System.out.println("Use the stick");
    }
    
    public void steer () {
        System.out.println("turning left...");
    }
    
    public void horn () {
        System.out.print("honking... ");
    }
}
public class TeslaModelS extends Car {
    // Additional attribute not present in the superclass
    protected String hornSound; 
    
    // Constructor for Subclass
    public TeslaModelS(String brandName, double range, double doorNumber, double maxSpeed, String hornSound) {
        // We use the Superclass constructor for the shared attributes through the keyword "super"
        super(brandName, range, doorNumber, maxSpeed);
        // hornSound is not in the Superclass, so we add it separately in the constructor
        this.hornSound = hornSound;
    }
    
    // We use override to change the functionality in the subclass of an existing method in the superclass
    @Override
    public void gearShift () {
        System.out.println("Use the gear selector next to the wheel");
    }
    public void steer () {
        System.out.println("turning right...");
    }
    
    // Here, we don't fully change the functionality of the existing horn method in the superclass
    // Instead, we take all of the functionality of the superclass method, and then add on to it
    public void horn () {
        super.horn();
        System.out.println(hornSound);
    }
    
    public static void main(String[] args) {
        // 5 argument constructor
        TeslaModelS modelS = new TeslaModelS("Tesla", 396, 4, 200, "eugh");
        // Example of late binding
        Car car = new TeslaModelS("Tesla", 396, 4, 200, "brrr");
        // We can still use the methods from the child class, even though we didn't mention them in the subclass!
        modelS.gas();
        // Using the overridden method
        modelS.gearShift();
        modelS.steer();
        // Using the method we added on to
        modelS.horn();
        car.horn();
    }
    
    
}
TeslaModelS.main(null);
Go!
Use the gear selector next to the wheel
turning right...
honking... eugh
honking... brrr

Writing Constructors for Subclasses

In the previous example, the TeslaModelS class was a subclass. Recall that a constructor is what initializes the values of the attributes of a class when a new object of the class is created. How do we write constructors for subclasses?

Well, if the attributes of the subclass are exactly the same as the attributes of the superclass, then we can just use the constructor of the superclass. Take a look at the superclass, the Car class. We have defined a constructor within that class already.

To use the constructor of the superclass in our subclass, we need to use some specific syntax. Namely, we need to make use of the super keyword. This allows us to use constructors that we define in the superclass.

Look again at the constructor in the TeslaModelS class. We have passed in 4 attributes to the super constructor, meaning that we are making use of the 4 argument constructor in the superclass.

But our subclass also has an additional attribute: hornSound. We don't include this in the superclass since this isn't customizable for most cars. This attribute needs to be included in the constructor for our subclass, but it doesn't make sense to make a completely new constructor for all 5 attributes when 4 of the attributes are shared with the superclass.

So, what we do is call the superclass constructor with 4 attributes, but then simply add an additional assignment for the 5th, unique, attribute.

Hack 1

In your own notebook, make any class with 2 attributes and 0 methods. Create a 2 argument constructor for that class. This will be your superclass. Now, create a subclass that extends from the superclass you made. Create 1 additional attribute in your subclass that was not present in the superclass.

Then, create a constructor for the subclass that uses the superclass constructor with the super keyword, and then adds an additional assignment for the third attribute.

Overriding Methods

Overriding allows a subclass or child class to provide a specific implementation of a method that has already been provided by a super-classes or parent classes. When a method in a subclass has the same name, same parameters or signature, and same return type (or sub-type) as a method in its super-class, then the method in the subclass will override the method in the super-class.

override

// the existing method in the superclass
    public void gearShift () {
        System.out.println("Use the stick");
    }

    public void steer () {
        System.out.println("turning left...");
    }

    // We use override to change the functionality in the subclass of an existing method in the superclass
    @Override
    public void gearShift () {
        System.out.println("Use the gear selector next to the wheel");
    }
    public void steer () {
        System.out.println("turning right...");
    }

Hack 2

Add a method to the superclass you created before. This method should be very general; it should only have functionality that you know for sure will be needed in almost every single subclass. In your subclass, override this method. Remember that overriding the method will give your subclass the specific functionality it needs from that method.

Super Keyword

The two main uses of the super keyword are to use constructors in the superclass and methods in the superclass in a child class.

In this example child class TeslaModelS, the super keyword can be found in the constructor and the method horn. When it says, super(brandName, range, doorNumber, maxSpeed) in the constructor, the super keyword is used to utilize the super class constructor inside the child class. Next, in the horn() method, super.horn() is called. This line utilizes the super keyword to call the method horn() from the parent class Car.

Creating References Using Inheritance Hierarchies

Type Diagram

It is similar to looking at it like a family tree. A is the superclass, or the head of the family. The descendants are the subclasses.

So

public class A public class B extends A public class C extends B

A Reference refers to an object of the class, or the object of an inherited class. So an Inheritance Hierarchy can create references

image

Polymorphism

public class ToyotaCamry extends Car {
    
    public ToyotaCamry (String brandName, double range, double doorNumber, double maxSpeed) {
        super(brandName, range, doorNumber, maxSpeed);
    }
    
    @Override
    public void gearShift () {
        System.out.println("Manual shift!");
    }
    
    public void turbo (int a) {
        System.out.println("Engaging turbo " + a);
    }
    
    public void turbo (int a, int b) {
        System.out.println("Engaging turbo " + a + " and nitro " + b);
    }
    
        
    public static void main(String[] args) {
        // 4 superclass argument constructor
        ToyotaCamry camry = new ToyotaCamry("Toyota", 348, 4, 145);
        // Using the overridden method
        camry.gearShift();
        // Using the overloaded method
        camry.turbo(1);
        camry.turbo(1, 1);

    }
}
ToyotaCamry.main(null);
Manual shift!
Engaging turbo 1
Engaging turbo 1 and nitro 1

Polymorphism means "many forms". It means that we do one thing in many ways through inheritance.

For example, the gearShift method defined in the superclass simply says to use the stick. But in manual cars and a Tesla, you need to do different things. So, we show the method to shift gears being used in two different ways in two different classes. We can have two different implementations through overriding methods.

Look at the different outputs for the method in the ToyotaCamry and TeslaModelS class.

This method of polymorphism is called Runtime Polymorphism. It is also called Dynamic Binding or Dynamic Method Dispatch. When you hear any of these things, think of method overriding.

Imagine if you want to do a single method in a single class, but in different ways. For example, in the ToyotaCamry class, we need to either engage a turbo by itself, or a turbo with a nitro. So, we have two methods called turbo. When the method is called, the actual functionality is resolved at compile-time.

When we call camry.turbo(1), then the single argument turbo method is called. When we call camry.turbo(1, 1), the two argument turbo method is called.

Having two methods with the same name but different arguments and functionalities is called method overloading.

This is another form of polymorphism called Compile Time Polymorphism or Static Polymorphism. When you see these terms, think of method overloading.

Early binding has to do with when the compiler decides the method to be called. Relate this to Static Polymorphism or method overloading. Late binding has to do with when the method is decided at runtime. Relate this to Runtime Polymorphism or method overriding.

Hack 3

Create another subclass from your original superclass. Now, implement method overloading in this subclass. Remember that this means having two methods with the same name, but with different arguments. The method you are using for method overloading doesn't have to exist in the superclass. This will implement Static Polymorphism.

Next, override the method in your superclass in your new subclass. Make sure it has different functionality than your method in the other subclass. This will implement Runtime Polymorphism.

Object Superclass

Important Things to Know

  • Object class is the superclass of all other classes in Java
  • Object is part of the java.lang package (know for imports)
  • Important Object class methods include: boolean equals(Object x) & String toString()
  • Object class' subclasses override the equals and toString methods

toString Method

  • Prints out the attributes of an object
  • Converts string object into a string

equals Method

  • Compares two strings
  • Returns a boolean value of true if equal, otherwise returns false
// Demonstration of toString method
// Utilized toString methods in all FRQs so far, here's an example from FRQ1
public String dayOfWeekToString() {
    return ("{ \"month\": " + this.month + ", " + "\"day\": " + this.day + ", " + "\"year\": " + this.year + ", "
          + "\"dayOfWeek\": " + this.dayOfWeek + " }");
 }


public String toString() {
    return dayOfWeekToString();
 }
// Demonstration of equals method
// Outputs boolean value of true or false
// If one object equals another
public class Student
{
   private String name;

   public Student(String name)
   {
      this.name = name;
   }

   public static void main(String[] args)
   {
      Student student1 = new Student("Bob");
      Student student2 = new Student("Jeff");
      Student student3 = student1;
      Student student4 = new Student("A");
      Student student5 = student4;
      System.out.println(student1.equals(student2));
      System.out.println(student2.equals(student3));
      System.out.println(student1.equals(student3));
      System.out.println(student3.equals(student4));
      System.out.println(student3.equals(student4));
      System.out.println(student5.equals(student4));


   }
}
Student.main(null);
false
false
true
false
false
true

Final Homework

Polish up all of the hacks from this lesson. Completed hacks will earn you 0.9/1.

For 1/1, submit a complete blog. This means highlighting all key vocabulary, and including original comments in your code to show understanding. This will also help you for Mr. Mort's live review on Thursday.

Grades

Team Grade Reason
1: Lily Wu, William Wu, Vidhi Kulkarni, Riya Patil, Saathvika Ajith 1/1 Hacks are performed correctly with interesting use case of Cats. Really good blog with vocab definitions and some code comments.
2: Serafina 1/1 Vocabulary is included along with candy inheritance example. All hacks done correctly
3: Sanjay, Karthik, Evan, Pranav, Jay 0.95/1 Good work completing all hacks using countries for inheritance example. No vocabulary is highlighted, but code comments are included.
4: Avinh 0.95/1 Good work implementing inheritance with chess pieces. Also included vocabulary. Only thing that is missing is implementation of Runtime Polymorphism by overriding in 2 separate subclasses.
5: Kinish Sathish, Ritvik Keerthi, Samuel Wang, Everitt Cheng 1/1 Hacks are all done with inheritance use case of animals. Good work on adding comments in code that show understanding, as well as complete blog with vocab.
7: Shraddha, Bria, Calissa, Evan 1/1 Hacks are performed correctly with candy inheritance example. Also included notes on key terms.
8: Bailey Say, Andrew Meng, Nicholas Mounier, Rohan Juneja, Aarav Arora 0.95/1 Good work on identifying a use case for inheritance and creating a detailed blog with vocabulary. The only thing missing is finishing Hack 3, which would require two subclasses to implement Runtime Polymorphism.
9: Hetvi Trivedi, Brian Tang, Iris Yang, and Ellen Xu 1/1 Hacks are performed correctly with interesting use case of Iphones. Covered all of the important vocabulary in blog as well.