Abstract classes and interfaces
abstract methods, interfaces, default methods, multiple interfaces and sorting with Comparable. The main source of compile-error questions.
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:
- You cannot
newan abstract class but you can use it as a type. - A class with an abstract method must be abstract. A concrete subclass must write all of them.
- Interface methods are public. Do not forget to write
publicwhen you implement. - A single
extends, manyimplements. - 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
| Situation | Result |
|---|---|
new AbstractClass() | Compile error |
| The class has an abstract method but is not abstract | Compile error |
| A concrete subclass did not write an abstract method | Compile error: is not abstract and does not override abstract method |
| The subclass is abstract too | Allowed, it does not have to write the methods; the responsibility passes to the next concrete class |
| Constructor in an abstract class | Can exist, called with super(...) |
abstract + private / static / final method | Compile 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 write | What 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 writepublicin 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 class | Interface | |
|---|---|---|
| How many can you derive from? | One (extends) | Many (implements) |
| Instance field | Can have | Cannot (constants only) |
| Constructor | Can have | Cannot |
| Method with a body | Normal methods | Only default, static, private |
| Method access | Any | Public by default |
| Relationship | is-a (sharing common code) | can-do (capability) |
| Example | Employee, Shape | Payable, 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 |
|---|---|
| negative | a comes before b |
| 0 | they are considered equal |
| positive | a 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 idea | The truth |
|---|---|
| An abstract class has no constructor | It does, and the subclass calls it with super(...) |
new Shape() works on an abstract class too | Compile error; new only on concrete subclasses |
You can write { } on an abstract method | No body, it ends with ; |
| A normal class with an abstract method | The class must be abstract too |
| Writing an interface method in the class without public | The access narrows, compile error |
| An interface field changes like a variable | It is public static final, cannot be changed |
class A implements X extends B | Order: extends B implements X |
| If two interfaces bring the same default, the first is chosen | Compile error, the class must override |
| compareTo returns true/false | It returns an int: negative, 0, positive |
return cents - other.cents; is always correct | Overflows with very large values; Integer.compare |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
Find the compile error.
abstract class Shape { abstract double area(); }
// in main:
Shape s = new Shape();Show answer
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.
Find the compile error.
abstract class Shape { abstract double area(); }
class Circle extends Shape {
double r;
}Show answer
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.
Find the compile error.
interface Payable { double amount(); }
class Bill implements Payable {
double amount() { return 10; }
}Show answer
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().
Find the compile error.
class Shape {
abstract double area();
}Show answer
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.
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
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).
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
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.
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
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.
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
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).
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
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.
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
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.
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.
- (a)
- (b)
- (c)
- (d)
Show answer
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.
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
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).