Skip to content

Inheritance Abstract classes Inheritance-based polymorphism, encapsulation

Inheritance

  • Usage (Format):
    A class (subclass) inherits attributes and methods from another class (superclass) using the extends keyword.

// Superclass
public class Vehicle {
    double speed;
    void go() {
        System.out.println("This vehicle is moving");
    }
    void stop() {
        System.out.println("This vehicle is stopped");
    }
}

// Subclass
public class Car extends Vehicle {
    // Car inherits speed, go(), stop()
    int wheels = 4; // Additional attribute
    int doors = 4;  // Additional attribute
}

// Main usage
public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.go(); // Accesses inherited method
        System.out.println(car.speed); // Accesses inherited attribute
        System.out.println(car.doors); // Accesses Car's own attribute
    }
}
- Important Points:

  • Purpose: Enables code reusability. Common methods and fields don't need to be rewritten in multiple classes.
  • Terminology:

    • Subclass (or child class, derived class): The class that inherits (e.g., Car).
    • Superclass (or parent class, base class): The class being inherited from (e.g., Vehicle).
    • Benefits:

    • Reduces code duplication.

    • Subclasses can have their own unique additional attributes and methods.
    • Establishes an "is-a" relationship (e.g., a Car is a Vehicle).

Method Overriding

  • Usage (Format):
    A subclass provides a specific implementation for a method that is already defined in its superclass.

// Superclass
public class Animal {
    void speak() {
        System.out.println("The animal is speaking");
    }
}

// Subclass with overridden method
public class Dog extends Animal {
    @Override // Good practice annotation
    void speak() {
        System.out.println("The dog goes bark");
    }
}

// Main usage
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.speak(); // Calls Dog's speak() method

        Animal animal = new Animal();
        animal.speak(); // Calls Animal's speak() method
    }
}
- Important Points:

  • Definition: Declaring a method in a subclass with the same signature (name, parameters) as a method in its parent class.
  • Purpose: Allows a subclass to provide its own specific behavior for an inherited method.
  • @Override Annotation:

    • Not mandatory, but highly recommended.
    • Instructs the compiler to check if the method is actually overriding a superclass method. Helps catch errors (e.g., typos in method name or parameters).
    • The method in the subclass must have the same name, return type (or a subtype for covariant return types), and parameter list as the method in the superclass.
    • Access modifier of the overriding method cannot be more restrictive than the overridden method.

Super Keyword

  • Usage (Format):
    Refers to the immediate superclass of an object.

  • Calling Superclass Constructor:

    // Superclass
    public class Person {
        String name;
        int age;
        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String toString() {
            return this.name + "\n" + this.age + "\n";
        }
    }
    
    // Subclass
    public class Hero extends Person {
        String power;
        Hero(String name, int age, String power) {
            super(name, age); // Calls Person's constructor
            this.power = power;
        }
        @Override
        public String toString() {
            return super.toString() + this.power; // Calls Person's toString()
        }
    }
    
    - Important Points:

  • Similar to the this keyword, but this refers to the current object, while super refers to its superclass portion.

  • super() for Constructor:

    • Used to call a constructor of the superclass.
    • If used, it must be the very first statement in the subclass's constructor.
    • If no explicit super() call is made, the compiler implicitly inserts a call to the superclass's no-argument constructor (super();). If the superclass doesn't have a no-argument constructor, this will result in a compile error unless another superclass constructor is explicitly called.
    • super.memberName: Used to access methods or fields of the superclass, especially useful when a subclass has overridden a method or hidden a field and you still need to access the superclass's version.

Abstraction

  • Usage (Format):
    Hiding implementation details and showing only essential features. Achieved using abstract classes and abstract methods.

// Abstract class with an abstract method
public abstract class Vehicle {
    abstract void go(); // Abstract method: no implementation
    void stop() {        // Concrete method: has implementation
        System.out.println("Vehicle stopped.");
    }
}

// Concrete subclass implementing the abstract method
public class Car extends Vehicle {
    @Override
    void go() {
        System.out.println("The driver is driving a car");
    }
}

// Main usage
public class Main {
    public static void main(String[] args) {
        // Vehicle vehicle = new Vehicle(); // ERROR! Cannot instantiate abstract class
        Car car = new Car();
        car.go();    // Calls Car's implementation
        car.stop();  // Calls Vehicle's inherited implementation
    }
}
- Important Points:

  • abstract class:

    • Cannot be instantiated directly using new.
    • Meant to be subclassed.
    • Can contain both abstract methods (without implementation) and concrete methods (with implementation).
    • If a class contains one or more abstract methods, it must be declared abstract.
    • Can have constructors (called by subclass constructors via super()).
    • Used to define a common template or partial implementation for a group of related subclasses.
    • abstract method:

    • Declared with the abstract keyword and has no body (ends with a semicolon).

    • Example: abstract void go();
    • Forces subclasses to provide an implementation for this method, unless the subclass is also declared abstract.
    • Purpose: Prevents instantiation of overly general classes (e.g., "Vehicle" is too vague; "Car" or "Bicycle" are specific). Enforces a contract for subclasses.

Encapsulation

  • Usage (Format):
    Restricting direct access to some of an object's components (attributes) and providing access through public methods (getters and setters).

public class Car {
    private String make;  // private attribute
    private String model; // private attribute
    private int year;     // private attribute

    // Constructor
    public Car(String make, String model, int year) {
        this.setMake(make); // Use setter for potential validation
        this.setModel(model);
        this.setYear(year);
    }

    // Getter for make
    public String getMake() {
        return make;
    }

    // Setter for make
    public void setMake(String make) {
        // Can add validation logic here
        this.make = make;
    }

    // Getter for model
    public String getModel() {
        return model;
    }

    // Setter for model
    public void setModel(String model) {
        this.model = model;
    }

    // Getter for year
    public int getYear() {
        return year;
    }

    // Setter for year
    public void setYear(int year) {
        // Example validation
        if (year > 1885 && year <= java.time.Year.now().getValue() + 1) {
            this.year = year;
        } else {
            System.out.println("Invalid year.");
            // Or throw an exception
        }
    }
}

// Main usage
// Car myCar = new Car("Toyota", "Camry", 2020);
// System.out.println(myCar.getMake()); // Access via getter
// myCar.setYear(2021); // Modify via setter
- Important Points:

  • Definition: Bundling data (attributes) and methods that operate on the data within a class, and restricting direct access to the attributes from outside the class.
  • Access Modifiers:

    • private: Accessible only within the defining class. (Most common for attributes in encapsulation).
    • default (package-private): Accessible within the same package.
    • protected: Accessible within the same package and by subclasses in other packages.
    • public: Accessible from anywhere.
    • Getters (Accessors): Public methods used to read the values of private attributes (e.g., getMake()).
    • Setters (Mutators): Public methods used to modify the values of private attributes (e.g., setMake(String make)). Setters can include validation logic.
    • Benefits:

    • Data Hiding: Protects an object's internal state from unintended external modification.

    • Control: The class has full control over its data through getters and setters.
    • Flexibility & Maintainability: Implementation details can change without affecting classes that use it, as long as the public interface (getters/setters) remains the same.
    • Recommendation: Make attributes private by default unless there's a specific reason for them to be more accessible.

Copy Objects

  • Usage (Format):
    Creating a distinct copy of an object, rather than just copying its reference.
// Assuming Car class from Encapsulation example with getters/setters
// Car car1 = new Car("Honda", "Civic", 2019);
// Car car2 = new Car("Ford", "Focus", 2018);

// Incorrect way (reference copy):
// car1 = car2; // Now car1 and car2 point to the SAME Ford Focus object.
              // Changes to car1 affect car2 and vice-versa.

// Better way (shallow copy using setters and getters):
// Car car1 = new Car("Honda", "Civic", 2019);
// Car carToCopyFrom = new Car("Ford", "Focus", 2018);
// car1.setMake(carToCopyFrom.getMake());
// car1.setModel(carToCopyFrom.getModel());
// car1.setYear(carToCopyFrom.getYear());
// // Now car1 is a distinct object with values from carToCopyFrom.

(Note: The provided text is brief. A more robust way is often a copy constructor or implementing Cloneable and overriding clone(), but the prompt asks to stick to the text). - Important Points:

  • Reference vs. Value Copy:

    • A = B; (where A and B are object references) copies the memory address (reference). Both A and B will point to the same object.
    • Deep vs. Shallow Copy:

    • The method described (using getters and setters for primitive/immutable fields) performs a shallow copy if the object contains references to other mutable objects. A deep copy would involve recursively copying those referenced objects as well. The text implies a shallow copy of attributes.

    • Goal: To have two independent objects with the same (or derived) state, so modifications to one do not affect the other.
    • Common approaches (beyond the text's scope but for context):

    • Copy Constructor: A constructor that takes an object of the same class as an argument and initializes the new object's fields from the argument object.

    • Cloneable interface and clone() method: Java's built-in mechanism, though it has complexities.

Interface

  • Usage (Format):
    A contract defining methods that a class must implement. A class uses the implements keyword.

// Interface definition
public interface Prey {
    void flee(); // Abstract method (public abstract by default)
}

public interface Predator {
    void hunt(); // Abstract method
}

// Class implementing one interface
public class Rabbit implements Prey {
    @Override
    public void flee() {
        System.out.println("The rabbit is fleeing");
    }
}

// Class implementing multiple interfaces
public class Fish implements Prey, Predator {
    @Override
    public void hunt() {
        System.out.println("This fish is hunting smaller fish");
    }
    @Override
    public void flee() {
        System.out.println("This fish is fleeing from a larger fish");
    }
}

// Main usage
public class Main {
    public static void main(String[] args) {
        Rabbit rabbit = new Rabbit();
        rabbit.flee();

        Fish fish = new Fish();
        fish.hunt();
        fish.flee();
    }
}
- Important Points:

  • Definition: An abstract type that is used to specify a behavior that classes must implement. It's a template or a contract.
  • implements keyword: Used by a class to adopt an interface.
  • Multiple Interfaces: A class can implement multiple interfaces, separated by commas (e.g., class Fish implements Prey, Predator). This is a way Java achieves a form of multiple inheritance of type.
  • Inheritance vs. Interface:

    • A class can extend only one superclass (single inheritance of implementation).
    • A class can implement multiple interfaces (multiple inheritance of type/contract).
    • Contents:

    • Traditionally (pre-Java 8): Only abstract methods (implicitly public abstract) and constants (implicitly public static final).

    • Java 8+: Can also contain default methods (with implementation, can be overridden) and static methods (with implementation, belong to the interface, not instances).
    • Obligation: A concrete (non-abstract) class implementing an interface must provide an implementation for all abstract methods declared in that interface.
    • Purpose: To achieve abstraction, polymorphism, and loose coupling. Defines "what" a class can do, not "how" it does it.

Polymorphism

  • Usage (Format):
    The ability of an object to be treated as an instance of its own class, its superclass, or any interface it implements.

// Superclass
public class Vehicle {
    public void go() {
        System.out.println("The vehicle is generally moving");
    }
}

// Subclasses
public class Car extends Vehicle {
    @Override
    public void go() {
        System.out.println("The car begins moving");
    }
}
public class Bicycle extends Vehicle {
    @Override
    public void go() {
        System.out.println("The bicycle begins moving");
    }
}
public class Boat extends Vehicle {
    @Override
    public void go() {
        System.out.println("The boat begins moving");
    }
}

// Main usage
public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        Bicycle bicycle = new Bicycle();
        Boat boat = new Boat();

        // Polymorphic array: Vehicle reference holding Car, Bicycle, Boat objects
        Vehicle[] racers = {car, bicycle, boat};

        for (Vehicle x : racers) {
            x.go(); // Calls the specific go() method of the actual object type
                    // (Car's go(), Bicycle's go(), Boat's go())
        }
    }
}
- Important Points:

  • Definition: "Many forms." An object can be identified as more than one type (its own type, its superclass type, any interface type it implements).
  • Mechanism: Occurs when a superclass reference variable points to a subclass object (e.g., Vehicle v = new Car();).
  • Benefit: Allows for flexible and generic code. You can write code that operates on the superclass type, and it will work correctly with any subclass objects.
  • Requirement for Method Calls: For x.go() to be valid in the example, the go() method must be defined in the Vehicle class (the type of the reference x). The actual implementation that runs is determined by the object's true type at runtime.
  • Polymorphism with Object: Since all classes in Java implicitly inherit from Object, an Object reference can hold an instance of any class.

    • Object[] things = {car, bicycle, "hello"}; is valid. However, to call Car-specific methods, you'd need to cast.
    • Often used with method overriding to achieve different behaviors for the same method call depending on the actual object type.

Dynamic Polymorphism

  • Usage (Format):
    The specific method implementation to be executed is determined at runtime, based on the actual type of the object being referred to. This is a core aspect of polymorphism in OOP.

// Superclass
public class Animal {
    public void speak() {
        System.out.println("Animal goes brrrr");
    }
}

// Subclasses
public class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Dog goes bark");
    }
}
public class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("Cat goes meow");
    }
}

// Main usage
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Animal animal; // Superclass reference

        System.out.println("What animal do you want? (1=dog) or (2=cat):");
        int choice = scanner.nextInt();

        if (choice == 1) {
            animal = new Dog(); // animal refers to a Dog object
        } else if (choice == 2) {
            animal = new Cat(); // animal refers to a Cat object
        } else {
            animal = new Animal(); // animal refers to an Animal object
            System.out.println("That choice was invalid.");
        }
        // Dynamic dispatch: The speak() method of the actual object (Dog, Cat, or Animal)
        // is called at runtime.
        animal.speak();
        scanner.close();
    }
}
- Important Points:

  • Definition: Polymorphism where the decision of which method version to execute is made at runtime (dynamically), not at compile time.
  • Also known as: Runtime Polymorphism, Late Binding.
  • How it works: When a method is called through a superclass reference, the JVM checks the actual type of the object that the reference points to at runtime and executes the version of the method belonging to that actual type.
  • Prerequisite: Method overriding. The method must be defined in the superclass and overridden in the subclass(es).
  • Example Breakdown: animal.speak();

    • If animal points to a Dog object, Dog.speak() is executed.
    • If animal points to a Cat object, Cat.speak() is executed.
    • This decision is made "dynamically" when the program is running.
    • Key Idea: "A [superclass reference] is a: [subclass type], and a [superclass type], and an [Object type]". The behavior depends on the actual instance.