CSA Blog

Ian Wu

Overriding Methods and Using the super keyword

Introduction to Inheritance 9.1/9.2 9.3/9.4 9.5 9.6 9.7 Hacks

Method Overrides

Every class inherits methods from its parent class. These methods can be called in the child class and can also be overridden.

Shape Class

This is the same shape class from before.

public class Shape {
    protected String name;
    private int length;
    private int width;

    // Default constructor
    public Shape() {
        this.name = "shape";
        this.length = 10;
        this.width = 5;
    }

    // Parameterized constructor
    public Shape(String name, int length, int width) {
        this.name = name;
        this.length = length;
        this.width = width;
    }

    // Getter methods
    public String get_name() {
        return this.name;
    }

    public int get_length() {
        return this.length;
    }

    public int get_width() {
        return this.width;
    }

    // Setter methods
    public void set_name(String n) {
        this.name = n;
    }

    public void set_length(int a) {
        this.length = a;
    }

    public void set_width(int b) {
        this.width = b;
    }

    // Method to calculate the area
    public double calc_area() {
        return this.length * this.width;
    }

    // Method to calculate the perimeter
    public double calc_perimeter(){
        return 2*this.length + 2*this.width;
    }

    // Method to print the shape
    public void print_shape() {
        System.out.println("Shape: " + this.name);
    }

    // Additional method to print something
    public void print_something() {
        System.out.println("This is a shape");
    }
}

public class Driver {
    public static void main(String[] args) {
        Shape s1 = new Shape();
    }
}

Creating a Triangle:

Currently Our shape class only takes in length and width parameters. This works fine for squares and rectangles, but what if we wanted to make a different shape? To define a Triangle we can use the 3 side lengths. while still inheriting the same behavior from the base Shape class.

public class Triangle extends Shape {
    private int side1;
    private int side2;
    private int side3;


    public Triangle() {
        this.name = "triangle";
        this.side1 = 1;
        this.side2 = 2;
        this.side3 = 3;
    }

    // Constructor that takes a name and three side lengths
    public Triangle(String name, int s1, int s2, int s3) {
        super(name, 0, 0); // Call to Shape constructor to set the name
        this.name = "triangle";
        this.side1 = s1;
        this.side2 = s2;
        this.side3 = s3;
    }
}

Here’s our Triangle

As seen above, we’re creating a new child class: Triangle This class inherits name parameter from the parent shape class and takes in 3 new side length paramiters to define the triangle’s geometry.

Lets try to create a new triangle with the side lengths 3, 4, and 5. We can also check to see if we inherited correctly from shape by callign the calc_area and print_shape methods.

Triangle t1 = new Triangle(“triangle”, 3, 4, 5); t1.print_shape(); System.out.println(“Area of t1 = “ + t1.calc_area());

We have a problem…

We were able to make a new triangle from the inherited triangle class, however, our area is not being calculated correctly. We inherited the default shape class’ calc_area method:

area = length x width

This is defaulting to an area of 0 when we create a new triangle without specifying the length and width. Instead, we can use Heron’s formula to calculate the area of a triangle given 3 side lengths.

area = sqrt(s(s-s1)(s-s2)(s-s3)) where s is the semi-perimiter (s1+s2+s3)/2

Popcorn Hack 1

Lets re-define the Triangle class but this time override the default area method with the Heron’s formula

public class Triangle extends Shape {
    private int side1;
    private int side2;
    private int side3;


    public Triangle() {
        this.name = "triangle";
        this.side1 = 1;
        this.side2 = 2;
        this.side3 = 3;
    }

    // Constructor that takes a name and three side lengths
    public Triangle(String name, int s1, int s2, int s3) {
        super(name, 0, 0); // Call to Shape constructor to set the name
        this.name = "triangle";
        this.side1 = s1;
        this.side2 = s2;
        this.side3 = s3;
    }

    @Override
    public double calc_area() {
        double area = 0.0;
        //Write Code Here
        return area;
        //expected output 6
    }
    
}

The @Override annotation tells Java that the following method will be a method override. If the following method does not override an existing method the compiler will throw an error. This is useful to catch mistakes in method name spelling and ensuring that a method override takes place. The annotation is not necessary, but is good practice.

*Note it is essential to type the name of the function to be overridden exactly

Triangle t1 = new Triangle("triangle", 3, 4, 5);
t1.print_shape();
System.out.println("Area of t1 = " + t1.calc_area());
Shape: triangle
Area of t1 = 0.0

If done correctly the area of Triangle t1 should be 6.

Important things to note

  1. using the final keyword in the parent method will make that method unable to be overridden
  2. methods can be overidden to give more access but cannot restrict acces: private -> public, but not public -> private
  3. use of @Override is highly encouraged

Popcorn Hack 2

re-write the Triangle sublcass so that it also overrides the calc_perimeter()

public class Triangle extends Shape {
    private int side1;
    private int side2;
    private int side3;


    public Triangle() {
        this.name = "triangle";
        this.side1 = 1;
        this.side2 = 2;
        this.side3 = 3;
    }

    // Constructor that takes a name and three side lengths
    public Triangle(String name, int s1, int s2, int s3) {
        super(name, 0, 0); // Call to Shape constructor to set the name
        this.name = "triangle";
        this.side1 = s1;
        this.side2 = s2;
        this.side3 = s3;
    }

    @Override
    public double calc_area() {
        double area = 0;
        double s = (this.side1 + this.side2 + this.side3) / 2;
        area = Math.sqrt((s)*(s - this.side1)*(s - this.side2)*(s - this.side3));
        return area;
    }

    // Add perimeter method override here
    //expected output 12
    @Override
    public double calc_perimeter() {
        double perimeter = 0;
        perimeter = this.side1 + this.side2 + this.side3;
        return perimeter;
    }
    
}
Triangle ti84 = new Triangle("triangle", 3, 4, 5);
System.out.println(ti84.calc_area());
System.out.println(ti84.calc_perimeter());
6.0
12.0

Super keyword

What if we wanted to create our child triangle class, but also use overridden methods from the parent shape class? For example, what if we wanted to print out not only that our shape is a triangle, but also that it is a shape. We could accomplish this with the super keyword.

public class Triangle extends Shape {
    private int side1;
    private int side2;
    private int side3;


    public Triangle() {
        this.name = "triangle";
        this.side1 = 1;
        this.side2 = 2;
        this.side3 = 3;
    }

    // Constructor that takes a name and three side lengths
    public Triangle(String name, int s1, int s2, int s3) {
        super(name, 0, 0); // Call to Shape constructor to set the name
        this.name = "triangle";
        this.side1 = s1;
        this.side2 = s2;
        this.side3 = s3;
    }
    public void print_shape() {
        super.print_something();
        print_something();
        System.out.println("Shape: " + this.name);
    }


    @Override
    public void print_something() {
        System.out.println("This is a triangle");
    }

}

Triangle t2 = new Triangle("triangle", 3, 4, 5);
t2.print_shape();
This is a shape
This is a triangle
Shape: triangle

The Super Keyword

As seen above, using the super.method calls the parent class’ method. Something to note is that calling a super method inside annother method will first complete the parent method before moving on to the next line.

As seen above, the parent print something is called first, then the triangle’s print something, and lastly print shape.

Something to be aware of is not to call a method in itself without the super keyword. This will cause an error/

public class Triangle extends Shape {
    private int side1;
    private int side2;
    private int side3;


    public Triangle() {
        this.name = "triangle";
        this.side1 = 1;
        this.side2 = 2;
        this.side3 = 3;
    }

    // Constructor that takes a name and three side lengths
    public Triangle(String name, int s1, int s2, int s3) {
        super(name, 0, 0); // Call to Shape constructor to set the name
        this.name = "triangle";
        this.side1 = s1;
        this.side2 = s2;
        this.side3 = s3;
    }
    public void print_shape() {
        print_shape();
    }


    @Override
    public void print_something() {
        System.out.println("This is a triangle");
    }

}

Triangle t2 = new Triangle("triangle", 3, 4, 5);
t2.print_shape();

As seen above, this results in an infinite loop

Creating and Writing Constructors for Superclasses and Subclasses

Introduction to Inheritance 9.1/9.2 9.3/9.4 9.5 9.6 9.7 Hacks

Introduction

  • 9.1

In Java, object-oriented programming allows developers to structure programs as a collection of classes and objects. One key concept in OOP is inheritance, where a class (subclass) inherits properties and methods from another class (superclass). This allows code to be reused and models relationships between objects.

Superclass: A superclass in Java is a general class that contains common fields and methods that can be shared by its subclasses. It is also sometimes referred to as a parent class.

Subclass: A subclass (or child class) is a class that extends the functionality of a superclass. A subclass inherits all the attributes and methods of the superclass and can also have its own additional properties and methods.

You might be wondering why are subclasses and super classes useful? Well, let’s take a look at an example.

Shapes

What if we wanted to solve the problem of managing different types of shapes, each with unique properties and behaviors, but still wanted to avoid repetitive code? Well, let’s plan out a solution using inheritance. By creating a general Shape class with shared attributes, and then extending it into specific subclasses like Rectangle and Triangle, we can reuse common logic while allowing each shape to override methods like calc_area() for their specific needs. This approach simplifies the design.

How are we setting this up?

Let’s think about attributes that all shapes have. For simplicity, we can think of name, length, and width as attributes of a shape.

Let’s start setting up our main class. Here, we have a default and parametrized constructor. People can create a default shape, or pass in values to characterize their own shape.

We can also add additional methods to calculate area/print a shape.

public class Shape {
    protected String name;
    private int length;
    private int width;

    // Default constructor
    public Shape() {
        this.name = "Shape";
        this.length = 10;
        this.width = 5;
    }

    // Parameterized constructor
    public Shape(String name, int length, int width) {
        this.name = name;
        this.length = length;
        this.width = width;
    }

}



Let’s add setters (to set variables) and getters (to get a shapes attributes like name, length, and width.)

public class Shape {
    protected String name;
    private int length;
    private int width;

    // Default constructor
    public Shape() {
        this.name = "Shape";
        this.length = 10;
        this.width = 5;
    }

    // Parameterized constructor
    public Shape(String name, int length, int width) {
        this.name = name;
        this.length = length;
        this.width = width;
    }

    // Getter methods
    public String get_name() {
        return this.name;
    }

    public int get_length() {
        return this.length;
    }

    public int get_width() {
        return this.width;
    }

    // Setter methods
    public void set_name(String n) {
        this.name = n;
    }

    public void set_length(int a) {
        this.length = a;
    }

    public void set_width(int b) {
        this.width = b;
    }
}

Now we can have two subclasses, Rectangle and Triangle.

Screenshot 2024-09-18 at 12 04 51 PM

public class Rectangle extends Shape {
    public Rectangle() {
        super();
    }

    public Rectangle(String name, int length, int width) {
        super(name, length, width);
    }


}

public class Triangle extends Shape {
    public Triangle() {
        super();
    }

    public Triangle(String name, int length, int width) {
        super(name, length, width);
    }


}

You might notice the super() keyword being used a lot here. But what does it mean?

This leads us to our next big idea.

9.2 Constructors for Subclasses

  • Writing a Constructor for Subclass: When a subclass is created, it inherits the fields and methods from its superclass. However, a subclass does not inherit constructors from the superclass. If a subclass needs to initialize its own fields in addition to the fields of the superclass, it must provide its own constructor and explicitly call the constructor of the superclass using super().

  • Understanding super(): When writing a constructor for a subclass, you often need to call the constructor of the superclass to initialize the fields inherited from it. The super() call must be the first statement in the subclass constructor. If the superclass constructor requires arguments, those arguments must be passed in the super() call.

Obviously, the code blocks above are pretty basic and have a lot to be improved. For example, what if we wanted to model more complex shapes, like a circle or a hexagon? How could we extend our current Shape class to do that?

Popcorn Hack

Here’s a challenge for you: Implement two new subclasses, Circle and Hexagon, extending from the Shape class. Follow the same structure as the Rectangle and Triangle classes!

Screenshot 2024-09-18 at 12 21 38 PM

public class Hexagon extends Shape {
    public Hexagon()) {
        super();
    }

    public Hexagon(String name, int length, int width) {
        super(name, length, width);
    }


}

public class Circle extends Shape {
    public Circle() {
        super();
    }

    public Circle(String name, int length, int width) {
        super(name, length, width);
    }


}

You might notice the super() keyword being used a lot. But what does it mean?

This leads us to our next lesson.

9.2 Constructors for Subclasses

  • Writing a Constructor for Subclass: When a subclass is created, it inherits the fields and methods from its superclass. However, a subclass does not inherit constructors from the superclass. If a subclass needs to initialize its own fields in addition to the fields of the superclass, it must provide its own constructor and explicitly call the constructor of the superclass using super().

  • Understanding super(): When writing a constructor for a subclass, you often need to call the constructor of the superclass to initialize the fields inherited from it. The super() call must be the first statement in the subclass constructor. If the superclass constructor requires arguments, those arguments must be passed in the super() call.

Popcorn Hack 2 Optional: Mastering super()

What if we wanted to understand how to properly use the super() keyword in subclasses to ensure that our shapes are correctly initialized?

Here’s a fun challenge:

  1. Create a new subclass called Ellipse that extends Shape.

Constructor: Implement a constructor for Ellipse that accepts parameters for name, length, and width. This constructor should call the superclass constructor using super().

  1. Update Your Driver Code

Test the Ellipse: Instantiate an Ellipse object and print its area. Verify that the constructor correctly initializes the shape and that the super() keyword is used properly. Hints:

Ellipse Constructor: Use super(name, length, width) to initialize inherited fields. Check Order: Remember, super() must be the first statement in your subclass constructor.

Full Running Code Cell

public class Shape {
    protected String name;
    private int length;
    private int width;

    // Default constructor
    public Shape() {
        this.name = "Shape";
        this.length = 10;
        this.width = 5;
    }

    // Parameterized constructor
    public Shape(String name, int length, int width) {
        this.name = name;
        this.length = length;
        this.width = width;
    }

    // Getter methods
    public String get_name() {
        return this.name;
    }

    public int get_length() {
        return this.length;
    }

    public int get_width() {
        return this.width;
    }

    // Setter methods
    public void set_name(String n) {
        this.name = n;
    }

    public void set_length(int a) {
        this.length = a;
    }

    public void set_width(int b) {
        this.width = b;
    }

    // Method to calculate the area
    public double calc_area() {
        return this.length * this.width;
    }

    // Method to print the shape
    public void print_shape() {
        System.out.println("Shape: " + this.name);
    }

    // Additional method to print something
    public void print_something() {
        System.out.println("This is a shape");
    }
}

public class Rectangle extends Shape {
    public Rectangle() {
        super();
    }

    public Rectangle(String name, int length, int width) {
        super(name, length, width);
    }

    @Override
    public double calc_area() {
        return (this.get_length() * this.get_width());
    }

    @Override
    public void print_something() {
        System.out.println("This is a rectangle");
    }
}

public class Triangle extends Shape {
    public Triangle() {
        super();
    }

    public Triangle(String name, int length, int width) {
        super(name, length, width);
    }

    @Override
    public double calc_area() {
        return (get_length() * get_width() * 0.5);
    }
}

public class Driver {
    public static void main(String[] args) {
        Shape s1 = new Shape();
        Shape s2 = new Rectangle("rectangle", 4, 10);
        Shape s3 = new Triangle("triangle", 5, 20);

        System.out.println("Area of s1 = " + s1.calc_area());
        System.out.println("Area of s2 = " + s2.calc_area());
        System.out.println("Area of s3 = " + s3.calc_area());

        s1.print_shape();
        s2.print_shape();

        // Using the overridden method
        s1.print_something();
        s2.print_something();
    }
}

// Run the driver code
Driver.main(new String[0]);

Area of s1 = 50.0
Area of s2 = 40.0
Area of s3 = 50.0
Shape: Shape
Shape: rectangle
This is a shape
This is a rectangle

Unit 9 - Java Inheritance

Introduction to Inheritance 9.1/9.2 9.3/9.4 9.5 9.6 9.7 Hacks

Introduction to Inheritance in Java

Inheritance is a fundamental concept in object-oriented programming (OOP) that allows one class (the subclass) to inherit fields and methods from another class (the superclass). This promotes code reusability and establishes a natural hierarchical relationship between classes.

Key Concepts of Inheritance:

  1. Superclass and Subclass:
    • Superclass (Parent Class): The class whose features are inherited. It provides common attributes and behaviors that can be shared by subclasses.
    • Subclass (Child Class): The class that inherits from the superclass. It can extend or modify the behavior of the superclass and add its own unique features.
  2. Basic Syntax:
    • To declare a subclass, use the extends keyword. ```java public class Subclass extends Superclass { // Subclass-specific fields and methods }

Topics to Explore: To learn more, you can explore the tabs on the navigation bar to explore additional units within the CollegeBoard curriculum.

public class Vehicle {
    float miles = 0;
    float milesTraveled(float milesToAdd){
        this.miles += milesToAdd;
        return this.miles;
    }
    boolean hasMovement = true;
}

public class Car extends Vehicle {
    boolean canFly = true;
}

public class Plane extends Vehicle {
    boolean canFly = true;
}

Car myCar = new Car();
System.out.println("As you can see my Car also has the inherited varible hasMovement");
System.out.println("myCar.hasMovement: "+(new Boolean(myCar.hasMovement)).toString());
As you can see my Car also has the inherited varible hasMovement
myCar.hasMovement: true