SEN1002 · Week 4 · Polymorphism II: Abstract Classes and Interfaces

Abstract classes and interfaces

abstract methods, interfaces, default methods, multiple interfaces and sorting with Comparable. The main source of compile-error questions.

abstract classabstract methodconcrete classinterfaceimplementsdefault methodstatic interface methodconstantmultiple interfacesComparablecompareToArrays.sort

Last week we wrote area() in the Shape class and returned 0. But the area of a “generic shape” is meaningless; the only job of that method was to tell the subclasses “you will write this”. Abstract classes and interfaces put this intention into the language: the compiler does not compile a class that does not fulfill the contract. That is why the exam has many “find the compile error” questions on this topic, plus “sort this array” writing questions with Comparable.

Golden rules:

  1. You cannot new an abstract class but you can use it as a type.
  2. A class with an abstract method must be abstract. A concrete subclass must write all of them.
  3. Interface methods are public. Do not forget to write public when you implement.
  4. A single extends, many implements.
  5. compareTo: negative, zero, positive. Arrays.sort uses this.

Abstract class

A class marked with the abstract keyword is an incomplete template. It can contain methods without a body (abstract). Like a normal class, it can also have fields, a constructor and methods with bodies.

abstract class Employee {
    private final String name;

    Employee(String name) {                 // an abstract class has a constructor too
        this.name = name;
        System.out.println("Employee(" + name + ")");
    }
    String getName() { return name; }

    abstract double earnings();             // no body, semicolon

    @Override
    public String toString() { return getName() + ": " + earnings(); }
}

class Salaried extends Employee {
    private final double monthly;
    Salaried(String n, double m) { super(n); monthly = m; }
    @Override double earnings() { return monthly; }
}

class Hourly extends Employee {
    private final double rate;
    private final int hours;
    Hourly(String n, double r, int h) { super(n); rate = r; hours = h; }
    @Override double earnings() {
        if (hours <= 40) return rate * hours;
        return rate * 40 + rate * 1.5 * (hours - 40);   // overtime
    }
}
Employee[] staff = { new Salaried("Ada", 3000), new Hourly("Bora", 100, 45) };
for (Employee e : staff) System.out.println(e);
Employee(Ada)
Employee(Bora)
Ada: 3000.0
Bora: 4750.0

If you had written new Employee("X"): Employee is abstract; cannot be instantiated. But an Employee[] array and an Employee e variable are fine; an abstract class is used as a common type for polymorphism.

Rules about abstract

SituationResult
new AbstractClass()Compile error
The class has an abstract method but is not abstractCompile error
A concrete subclass did not write an abstract methodCompile error: is not abstract and does not override abstract method
The subclass is abstract tooAllowed, it does not have to write the methods; the responsibility passes to the next concrete class
Constructor in an abstract classCan exist, called with super(...)
abstract + private / static / final methodCompile error (a method that cannot be overridden cannot be left without a body)

Interface

An interface is a contract that says what a class “can do”. It is looser than the “is-a” relationship of inheritance: a “can-do” relationship. Classes that have nothing to do with each other (invoice, employee, rent) can implement the same Payable interface.

The hidden meanings of what you write in an interface:

What you writeWhat the compiler understands
double amount();public abstract double amount();
double TAX = 0.2;public static final double TAX = 0.2; (constant)
default double afterTax() { ... }A method with a body, the class overrides it if it wants (Java 8+)
static Payable fixed(double v) { ... }Called only as Payable.fixed(...)
interface Payable {
    double TAX = 0.2;
    double amount();
    default double afterTax() { return amount() * (1 - TAX); }
}

class Invoice implements Payable {
    private final int qty;
    private final double price;
    Invoice(int q, double p) { qty = q; price = p; }
    public double amount() { return qty * price; }   // public REQUIRED
}

Payable p = new Invoice(3, 50);
System.out.println(p.amount() + " " + p.afterTax());
System.out.println(Payable.TAX);
150.0 120.0
0.2

PITFALL · forgetting public. Even if you do not write public in the interface, the method is public. If you do not write it in the class, it becomes package-private and the access narrows: attempting to assign weaker access privileges; was public. This is the most common interface compile error.

Trying to change an interface constant is also an error: Payable.TAX = 0.3; → cannot assign a value to static final variable TAX.

Multiple interfaces

A class can extend only one class but can implement as many interfaces as it wants. The order of writing matters: first extends, then implements.

class Robot extends Machine implements Payable, Comparable<Robot> { ... }

If two interfaces bring a default method with the same signature, Java cannot know which one to pick and gives a compile error. Fix: the class overrides the method, and if it wants, calls one (or both) with InterfaceName.super.method().

interface Flyer   { default String move() { return "fly"; } }
interface Swimmer { default String move() { return "swim"; } }

class Duck implements Flyer, Swimmer {
    public String move() { return Flyer.super.move() + "+" + Swimmer.super.move(); }
}
System.out.println(new Duck().move());
fly+swim

If you write class Duck implements Flyer, Swimmer { } without an override: types Flyer and Swimmer are incompatible; class Duck inherits unrelated defaults for move() from types Flyer and Swimmer.

Abstract class or interface?

Abstract classInterface
How many can you derive from?One (extends)Many (implements)
Instance fieldCan haveCannot (constants only)
ConstructorCan haveCannot
Method with a bodyNormal methodsOnly default, static, private
Method accessAnyPublic by default
Relationshipis-a (sharing common code)can-do (capability)
ExampleEmployee, ShapePayable, Comparable, Runnable

Practical rule: if the subclasses share common fields and code, use an abstract class. If there is only a shared capability, use an interface.

Comparable and compareTo

java.lang.Comparable<T> is an interface with a single method: int compareTo(T other). When a class implements it, it has defined a “natural order” for its objects. Arrays.sort and Collections.sort use this order.

The meaning of the value compareTo returns:

a.compareTo(b)Meaning
negativea comes before b
0they are considered equal
positivea comes after b
class Student implements Comparable<Student> {
    String name; double gpa;
    Student(String n, double g) { name = n; gpa = g; }

    @Override
    public int compareTo(Student o) { return Double.compare(gpa, o.gpa); }

    @Override
    public String toString() { return name + "(" + gpa + ")"; }
}

Student[] st = { new Student("Ece", 3.1), new Student("Ali", 3.8), new Student("Can", 2.5) };
Arrays.sort(st);
System.out.println(Arrays.toString(st));
System.out.println(st[0].compareTo(st[2]));
System.out.println("apple".compareTo("banana"));
System.out.println("b".compareTo("a"));
System.out.println(Integer.valueOf(5).compareTo(5));
[Can(2.5), Ece(3.1), Ali(3.8)]
-1
-1
1
0

TIP · reverse order. To sort from largest to smallest, swap the arguments: Double.compare(o.gpa, gpa). To sort by name, use String’s own compareTo: name.compareTo(o.name).

Ready-made classes such as String, Integer and Double are already Comparable. That is why you can sort a String[] array alphabetically directly with Arrays.sort. If your own class is not Comparable and you call Arrays.sort, you do not get a compile error, but a ClassCastException is thrown at run time.

Interface type and instanceof

An interface is also a type: Payable p = new Invoice(...) is valid, p instanceof Payable works. With a variable of an interface type you can call only the methods defined in the interface (last week’s static type rule applies exactly).

Object o = new Invoice(1, 1);
System.out.println(o instanceof Payable);
System.out.println(o instanceof Comparable);
true
false

Common mistakes

Wrong ideaThe truth
An abstract class has no constructorIt does, and the subclass calls it with super(...)
new Shape() works on an abstract class tooCompile error; new only on concrete subclasses
You can write { } on an abstract methodNo body, it ends with ;
A normal class with an abstract methodThe class must be abstract too
Writing an interface method in the class without publicThe access narrows, compile error
An interface field changes like a variableIt is public static final, cannot be changed
class A implements X extends BOrder: extends B implements X
If two interfaces bring the same default, the first is chosenCompile error, the class must override
compareTo returns true/falseIt returns an int: negative, 0, positive
return cents - other.cents; is always correctOverflows with very large values; Integer.compare

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

Find the compile error.

abstract class Shape { abstract double area(); }
// in main:
Shape s = new Shape();
Show answer
You cannot create an object of an abstract class

javac: Shape is abstract; cannot be instantiated. An abstract class can be used as a type (Shape s = new Circle(2); is valid) but it cannot be created with new itself.

Question 2easy

Find the compile error.

abstract class Shape { abstract double area(); }
class Circle extends Shape {
    double r;
}
Show answer
Circle is not abstract but does not implement area()

A concrete subclass must write, with a body, every abstract method it inherits. javac: Circle is not abstract and does not override abstract method area() in Shape. Fix: add double area() { return Math.PI * r * r; } or make Circle abstract too.

Question 3medium

Find the compile error.

interface Payable { double amount(); }
class Bill implements Payable {
    double amount() { return 10; }
}
Show answer
amount() must be written public

Interface methods are automatically public abstract. If no modifier is written in the class, it becomes package-private, which narrows the access. javac: attempting to assign weaker access privileges; was public. Fix: public double amount().

Question 4easy

Find the compile error.

class Shape {
    abstract double area();
}
Show answer
A class that contains an abstract method must be abstract

If a class has even a single abstract method, the class must be marked abstract. Here javac says Shape is not abstract and does not override abstract method area() in Shape. Fix: abstract class Shape.

Question 5medium

What is printed?

abstract class Employee {
    private final String name;
    Employee(String name) { this.name = name; }
    String getName() { return name; }
    abstract double earnings();
    public String toString() { return getName() + ": " + earnings(); }
}
class Hourly extends Employee {
    private double rate; private int hours;
    Hourly(String n, double r, int h) { super(n); rate = r; hours = h; }
    double earnings() {
        if (hours <= 40) return rate * hours;
        return rate * 40 + rate * 1.5 * (hours - 40);
    }
}
// in main:
System.out.println(new Hourly("Bora", 100, 45));
Show answer
Bora: 4750.0

toString is in the abstract class, and the earnings call goes to Hourly's version. 40 regular hours: 100 · 40 = 4000. 5 hours of overtime: 100 · 1.5 · 5 = 750. Total 4750.0. An abstract class has a constructor and it runs through super(n).

Question 6medium

What is printed?

interface Payable {
    double TAX = 0.2;
    double amount();
    default double afterTax() { return amount() * (1 - TAX); }
}
class Invoice implements Payable {
    private int qty; private double price;
    Invoice(int q, double p) { qty = q; price = p; }
    public double amount() { return qty * price; }
}
// in main:
Payable p = new Invoice(3, 50);
System.out.println(p.amount() + " " + p.afterTax());
Show answer
150.0 120.0

amount: 3 · 50.0 = 150.0. afterTax is a default method; since Invoice did not write it, the version in the interface runs, and the amount() inside it is still Invoice's: 150 · 0.8 = 120.0. Because TAX is defined in an interface, it is automatically public static final.

Question 7hard

Find the compile error.

interface Flyer   { default String move() { return "fly"; } }
interface Swimmer { default String move() { return "swim"; } }
class Duck implements Flyer, Swimmer { }
Show answer
Both interfaces bring the same default method, Duck must override move()

javac: types Flyer and Swimmer are incompatible; class Duck inherits unrelated defaults for move(). The class must decide which one applies: if you write public String move() { return Flyer.super.move() + "+" + Swimmer.super.move(); }, it returns fly+swim.

Question 8medium

What is printed?

class Student implements Comparable<Student> {
    String name; double gpa;
    Student(String n, double g) { name = n; gpa = g; }
    public int compareTo(Student o) { return Double.compare(gpa, o.gpa); }
    public String toString() { return name + "(" + gpa + ")"; }
}
// in main:
Student[] st = { new Student("Ece", 3.1), new Student("Ali", 3.8), new Student("Can", 2.5) };
Arrays.sort(st);
System.out.println(Arrays.toString(st));
Show answer
[Can(2.5), Ece(3.1), Ali(3.8)]

Arrays.sort uses compareTo to order from smallest to largest. compareTo compares gpa, so the order is ascending by GPA: 2.5, 3.1, 3.8. If you wanted largest to smallest, you would write Double.compare(o.gpa, gpa).

Question 9medium

What is printed?

System.out.println("apple".compareTo("banana"));
System.out.println("b".compareTo("a"));
System.out.println(Integer.valueOf(5).compareTo(5));
Show answer
-1, 1, 0

The compareTo contract: negative = comes first, 0 = equal, positive = comes after. For String, the code difference of the first differing characters is returned: 'a' − 'b' = −1, 'b' − 'a' = 1. Integer gives 0 when equal. In the exam, often only the sign is asked.

Question 10medium

What is printed by new Impl();?

abstract class Base {
    Base() { System.out.println("Base " + id()); }
    abstract String id();
}
class Impl extends Base {
    Impl() { super(); System.out.println("Impl"); }
    String id() { return "impl-id"; }
}
Show answer
Base impl-id / Impl

The abstract class's constructor runs while the subclass object is being built. The abstract id() call inside it goes to Impl's version, because the object is an Impl: Base impl-id. Then Impl's body: Impl.

Question 11easy

Which is TRUE? (a) A class can extend two abstract classes. (b) An interface can have instance fields. (c) A class can implement several interfaces. (d) An abstract class cannot have a constructor.

  1. (a)
  2. (b)
  3. (c)
  4. (d)
Show answer
(c)

In Java there is a single extends, but as many implements as you want. Fields in an interface are always public static final constants, they cannot be instance fields; (b) is wrong. An abstract class can have a constructor and the subclass calls it with super(...); (d) is wrong.

Question 12medium

Write a class Money that stores an amount in cents (int) and implements Comparable<Money> so that Arrays.sort orders Money objects from smallest to largest. toString() returns e.g. 120c.

Show answer
implements Comparable<Money> + Integer.compare

compareTo must return negative / 0 / positive. Subtraction (cents - other.cents) can overflow with very large numbers; Integer.compare is safe:

public class Money implements Comparable<Money> {
    private final int cents;

public Money(int cents) { this.cents = cents; }

    @Override
    public int compareTo(Money other) {
        return Integer.compare(cents, other.cents);
    }

    @Override
    public String toString() { return cents + "c"; }
}
The array {500c, 120c, 300c} becomes [120c, 300c, 500c] after Arrays.sort.

Verification: All code examples and answers were run with JDK 26 (java verify/SEN1002/W04.java, W04b.java); the compile errors were tried with javac (verify/SEN1002/err/E04a-g.java).

Review card

abstract class: CANNOT new, used as a type. Can have fields, constructors, concrete methods. abstract method: no body, ends with a semicolon: abstract double area(); If there is one abstract method, the class MUST be abstract too. A concrete subclass must write all abstract methods, otherwise "is not abstract and does not override". interface: methods are automatically public abstract; fields are public static final (constants). In a class that implements, methods are written PUBLIC (otherwise a weaker access error). default method: has a body, can be overridden. static method: called as Interface.method(). extends is single, implements is multiple: class D extends B implements X, Y If two interfaces bring the same default, the class must override: X.super.m() Comparable<T>: int compareTo(T o) → negative first, 0 equal, positive after. Arrays.sort(array) sorts ascending with compareTo. Reverse order: swap the arguments. Use Integer.compare(a,b), Double.compare(a,b); a-b can overflow.