Skip to main content

05 - Abstraction and Contracts: Interfaces and Abstract Classes

Inheritance, polymorphism, abstract classes and interfaces in Java. Default methods, multiple implementation and when to use what.

What do you think about this article?

At a Glance

Inheritance, polymorphism, abstract classes and interfaces in Java. Default methods, multiple implementation and when to use what.

Abstraction and Contracts in Java

Abstraction allows defining "contracts" that classes must adhere to, separating the "what" from the "how". In Java this is achieved with abstract classes and interfaces.

What You Will Learn

  • Inheritance and the extends keyword
  • Polymorphism and method overriding
  • Abstract classes and abstract methods
  • Interfaces and multiple implementation
  • Default methods in interfaces
  • When to use abstract classes vs interfaces

Inheritance

Inheritance allows a class to acquire attributes and methods from another class. The inheriting class is called subclass, the one being inherited from is the superclass.

Basic Inheritance
// Superclass (parent class)
public class Person {
    protected String firstName;
    protected String lastName;
    protected int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public void introduce() {
        System.out.println("My name is " + getFullName());
    }
}

// Subclass (child class) - inherits from Person
public class Student extends Person {
    private int studentId;
    private double gpa;

    public Student(String firstName, String lastName, int age, int studentId) {
        super(firstName, lastName, age); // calls superclass constructor
        this.studentId = studentId;
        this.gpa = 0;
    }

    // Additional method specific to Student
    public void study(String subject) {
        System.out.println(firstName + " is studying " + subject);
    }

    // Getter
    public int getStudentId() {
        return studentId;
    }
}

Polymorphism and Override

Polymorphism allows treating objects of different classes through a common type. Override allows redefining an inherited method.

Method Overriding
public class Student extends Person {
    private int studentId;

    public Student(String firstName, String lastName, int age, int studentId) {
        super(firstName, lastName, age);
        this.studentId = studentId;
    }

    // Override: redefines the superclass method
    @Override
    public void introduce() {
        System.out.println("Hi! I'm " + getFullName());
        System.out.println("Student ID: " + studentId);
    }
}

public class Professor extends Person {
    private String subject;

    public Professor(String firstName, String lastName, int age, String subject) {
        super(firstName, lastName, age);
        this.subject = subject;
    }

    @Override
    public void introduce() {
        System.out.println("Good morning, I'm Prof. " + lastName);
        System.out.println("I teach " + subject);
    }
}

// Polymorphism in action
Person[] people = new Person[3];
people[0] = new Student("John", "Smith", 20, 123456);
people[1] = new Professor("James", "Green", 45, "Programming");
people[2] = new Person("Anna", "White", 30);

for (Person p : people) {
    p.introduce(); // calls the correct method for each type
    System.out.println("---");
}

Override Rules

  • The @Override annotation is optional but recommended
  • Method must have the same signature (name and parameters)
  • Return type must be the same or a subtype
  • Cannot reduce visibility (from public to private)
  • Cannot override final or static methods

Abstract Classes

An abstract class cannot be instantiated directly and can contain abstract methods (without implementation) that subclasses must implement.

Abstract Class
// Abstract class: cannot be instantiated
public abstract class GeometricShape {
    protected String name;
    protected String color;

    public GeometricShape(String name, String color) {
        this.name = name;
        this.color = color;
    }

    // Concrete method: has implementation
    public void describe() {
        System.out.println("I am a " + color + " " + name);
    }

    // Abstract methods: no implementation, subclasses MUST implement them
    public abstract double calculateArea();
    public abstract double calculatePerimeter();
}

// Concrete subclass: MUST implement all abstract methods
public class Circle extends GeometricShape {
    private double radius;

    public Circle(String color, double radius) {
        super("circle", color);
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle extends GeometricShape {
    private double width;
    private double height;

    public Rectangle(String color, double width, double height) {
        super("rectangle", color);
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }

    @Override
    public double calculatePerimeter() {
        return 2 * (width + height);
    }
}

// Usage
GeometricShape[] shapes = {
    new Circle("red", 5),
    new Rectangle("blue", 4, 3)
};

for (GeometricShape shape : shapes) {
    shape.describe();
    System.out.printf("Area: %.2f, Perimeter: %.2f%n",
        shape.calculateArea(), shape.calculatePerimeter());
}

Interfaces

An interface defines a contract: a set of methods that implementing classes must provide. Unlike abstract classes, a class can implement multiple interfaces.

Defining Interfaces
// Interface: defines a contract
public interface Gradeable {
    // All methods are implicitly public abstract
    double getGrade();
    String getAssessment();
}

public interface Printable {
    void print();
}

public interface Exportable {
    String exportJSON();
    String exportCSV();
}

// A class can implement MULTIPLE interfaces
public class Exam implements Gradeable, Printable, Exportable {
    private String courseName;
    private int grade;
    private boolean honors;

    public Exam(String courseName, int grade, boolean honors) {
        this.courseName = courseName;
        this.grade = grade;
        this.honors = honors && grade == 100;
    }

    // Gradeable implementation
    @Override
    public double getGrade() {
        return grade;
    }

    @Override
    public String getAssessment() {
        if (grade >= 90) return "Excellent";
        if (grade >= 80) return "Good";
        if (grade >= 60) return "Satisfactory";
        return "Insufficient";
    }

    // Printable implementation
    @Override
    public void print() {
        String gradeStr = honors ? "100 with Honors" : String.valueOf(grade);
        System.out.println(courseName + ": " + gradeStr + " (" + getAssessment() + ")");
    }

    // Exportable implementation
    @Override
    public String exportJSON() {
        return String.format("{\"course\":\"%s\",\"grade\":%d,\"honors\":%b}",
            courseName, grade, honors);
    }

    @Override
    public String exportCSV() {
        return String.format("%s,%d,%b", courseName, grade, honors);
    }
}

Default Methods (Java 8+)

Default Methods
public interface Calculable {
    double calculate();

    // Default method: has a default implementation
    default String formatResult() {
        return String.format("%.2f", calculate());
    }

    // Static method in interface
    static double round(double value, int decimals) {
        double factor = Math.pow(10, decimals);
        return Math.round(value * factor) / factor;
    }
}

public class GradeAverage implements Calculable {
    private int[] grades;

    public GradeAverage(int... grades) {
        this.grades = grades;
    }

    @Override
    public double calculate() {
        int sum = 0;
        for (int g : grades) sum += g;
        return (double) sum / grades.length;
    }

    // Can override the default method if desired
    @Override
    public String formatResult() {
        return "Average: " + String.format("%.2f", calculate()) + "/100";
    }
}

Abstract Classes vs Interfaces

Comparison

Aspect Abstract Class Interface
Inheritance Single (extends) Multiple (implements)
Constructors Yes No
State (fields) Yes (any) Only constants (static final)
Concrete methods Yes Only default/static
Typical use Common base with state Behavior contract

When to use what

  • Abstract class: When you have common code to share, state, or an "is a type of" relationship
  • Interface: When you want to define behavior that different classes can implement
  • Both: You can use an abstract class that implements interfaces

Complete Example: University System

Interfaces
public interface Gradeable {
    double getAverage();
    String getAssessment();
}

public interface Identifiable {
    String getCode();
    String getType();
}
Base Abstract Class
public abstract class UniversityMember implements Identifiable {
    protected String firstName;
    protected String lastName;
    protected String code;

    public UniversityMember(String firstName, String lastName, String code) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.code = code;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    @Override
    public String getCode() {
        return code;
    }

    // Abstract method: each subclass defines its behavior
    public abstract void performRole();
}
Implementations
public class UniversityStudent extends UniversityMember implements Gradeable {
    private int[] grades = new int[20];
    private int examCount = 0;

    public UniversityStudent(String firstName, String lastName, int studentId) {
        super(firstName, lastName, "S" + studentId);
    }

    public void recordExam(int grade) {
        if (examCount < grades.length && grade >= 60 && grade <= 100) {
            grades[examCount++] = grade;
        }
    }

    @Override
    public String getType() {
        return "Student";
    }

    @Override
    public void performRole() {
        System.out.println(getFullName() + " studies for exams");
    }

    @Override
    public double getAverage() {
        if (examCount == 0) return 0;
        int sum = 0;
        for (int i = 0; i < examCount; i++) sum += grades[i];
        return (double) sum / examCount;
    }

    @Override
    public String getAssessment() {
        double average = getAverage();
        if (average >= 90) return "Excellent";
        if (average >= 80) return "Good";
        if (average >= 60) return "Satisfactory";
        return "In Progress";
    }
}

public class UniversityProfessor extends UniversityMember {
    private String subject;
    private int weeklyHours;

    public UniversityProfessor(String firstName, String lastName, String code, String subject) {
        super(firstName, lastName, "P" + code);
        this.subject = subject;
        this.weeklyHours = 20;
    }

    @Override
    public String getType() {
        return "Professor";
    }

    @Override
    public void performRole() {
        System.out.println("Prof. " + lastName + " teaches " + subject);
    }

    public void assignGrade(UniversityStudent student, int grade) {
        student.recordExam(grade);
        System.out.printf("Prof. %s assigned %d to %s%n",
            lastName, grade, student.getFullName());
    }
}
Test
public class UniversityTest {
    public static void main(String[] args) {
        // Create members
        UniversityStudent john = new UniversityStudent("John", "Smith", 123456);
        UniversityStudent anna = new UniversityStudent("Anna", "White", 789012);
        UniversityProfessor profGreen = new UniversityProfessor("James", "Green", "001", "Programming");

        // Assign grades
        profGreen.assignGrade(john, 85);
        profGreen.assignGrade(john, 92);
        profGreen.assignGrade(anna, 78);
        profGreen.assignGrade(anna, 82);

        // Polymorphism: array of base type
        UniversityMember[] members = {john, anna, profGreen};

        System.out.println("\n=== University Members ===");
        for (UniversityMember m : members) {
            System.out.println(m.getType() + ": " + m.getFullName());
            System.out.println("Code: " + m.getCode());
            m.performRole();

            // Check if implements Gradeable
            if (m instanceof Gradeable) {
                Gradeable g = (Gradeable) m;
                System.out.printf("Average: %.2f - %s%n", g.getAverage(), g.getAssessment());
            }
            System.out.println("---");
        }
    }
}

Conclusion

In this article we explored inheritance, polymorphism, abstract classes and interfaces - the advanced OOP pillars that enable creating flexible and reusable code.

In the next article we will cover Java Core APIs: String, StringBuilder, Wrapper classes and the Date/Time API.

Key Points to Remember

  • extends: Single inheritance from a class
  • implements: Interface implementation (multiple)
  • @Override: Redefines a superclass method
  • abstract: Method without implementation, non-instantiable class
  • interface: Behavior contract without state
  • default: Method with implementation in interfaces

Frequently asked questions

What is abstraction in Java?

Abstraction allows defining "contracts" that classes must adhere to, separating the "what" from the "how".

How does inheritance work in Java?

Inheritance allows a class to acquire attributes and methods from another class.

What is polymorphism in Java?

Polymorphism allows treating objects of different classes through a common type.

Can a class implement multiple interfaces in Java?

Yes, unlike abstract classes, a class can implement multiple interfaces.

Sources and further reading

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects