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.
// 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.
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
@Overrideannotation 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
finalorstaticmethods
Abstract Classes
An abstract class cannot be instantiated directly and can contain abstract methods (without implementation) that subclasses must implement.
// 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.
// 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+)
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
public interface Gradeable {
double getAverage();
String getAssessment();
}
public interface Identifiable {
String getCode();
String getType();
}
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();
}
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());
}
}
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







