Lesson 25Phase 3: Abstraction and design
Interface vs abstract class
When each one fits, the is-a versus can-do distinction, and how to choose with confidence.
By BasithUpdated 50 min
- is-a vs can-do
- design choice
- single inheritance
- multiple interfaces
- trade-offs
You now know two ways to write a type that leaves blanks for other classes to fill in: an abstract class and an interface. From a distance they look alike, and people new to Java often reach for the wrong one. This lesson adds no new syntax. It gives you a test for choosing between the two, and shows what each choice costs you later, when the program grows.
- Say in one sentence what an abstract class is for and what an interface is for.
- Use the “is-a” and “can-do” sentences to decide which one a design needs.
- Name the differences that settle most cases: per-object fields and constructors, and one parent class against many interfaces.
- Write a class that extends an abstract class and implements interfaces in the same header.
- Explain what breaks when an interface gains a method, and add one safely with a default method.
extends, and on polymorphism, where one variable type can hold many kinds of object. Every keyword below is one you have already used.A quick recap of both tools#
Start with each tool on its own. The difference is easiest to feel in a few sentences about each.
An abstract class is a partial class. It is a real class that you cannot build objects from directly, because some of its methods are left unfinished. It can hold fields, a constructor, and finished methods that every subclass inherits. It exists to say: all of these things are the same kind of thing, and here is the part I have already written for them. A subclass extends it and fills in the blanks.
An interface is a contract. It lists methods that a class promises to provide, usually with no bodies. It has no constructor, and it keeps no data of its own for each object. It does not ask what kind of thing you are, only whether you can do these actions. Any class, however unrelated to the others, can say implements and make that promise. An interface describes a capability.
Key ideaAn abstract class answers “what kind of thing is this?” An interface answers “what can this thing do?” Most good choices follow from keeping those two questions apart.
extends as is-a: a Dog is-an Animal. An interface gets a different verb, can-do: a Dog can-do Soundmaker, meaning it can make a sound. These two verbs are the test you will use to decide.The differences that matter#
A list of differences is easy to memorize and still leads to wrong choices, because it makes every row look equally important. Read the table, then the two rows under it that settle most real cases.
String name. Every object gets its own copy.public, static and final: one fixed value, shared by all.super(...).default method is the exception you met: it has a body, but no per-object fields to work with.public, protected, private, or have no access word at all.public, so the class that keeps the promise must write public too.extends.implements.The two rows that settle most cases
If you remember only two rows, make it these. The first is about state, the values an object keeps in its fields. Only an abstract class can carry fields, a constructor, and methods that work on those fields. If several classes really share data and the code that uses it, an interface cannot give it to them, so the answer is an abstract class.
The second is about how many. A class extends exactly one class but can implement many interfaces. If one thing needs several unrelated capabilities at once, only interfaces let it have them all.
The deciding test: is-a or can-do#
Here is how to turn the table into a decision you can make in seconds. For the thing you are designing, finish two sentences in plain English and see which one sounds true.
- 1Try the is-a sentence: “A
Circleis-aShape.” If it reads naturally, and the classes really are the same kind of thing sharing data and code, lean toward an abstract class. - 2Try the can-do sentence: “A
Circlecan-doDrawable,” meaning it can be drawn. If that reads better, and the classes are otherwise unrelated but all support one action, lean toward an interface. - 3Ask how many: does this thing need several independent capabilities at once? If yes, that settles it. Use interfaces, because a class can implement many.
One class can pass both tests, and that is fine. A Dog is-an Animal: animals share state such as a name and code such as eat, which suits an abstract class. A Dog also can-do Soundmaker, a capability that a robot or a car horn could have too, which suits an interface. A class can extend one abstract class and implement several interfaces in the same line, as you will see shortly.
Paymentis-a... what?” If you cannot name a natural parent kind, stop forcing an abstract class. “A Payment can-do... be refunded?” That sounds right, so Refundable wants to be an interface.Knight, an Archer and a Wizard. Each has a name and health points, and all three lose health in the same way when they are hit. Only the way each one attacks is different. Which design fits best?When the abstract class fits: shapes#
Think about drawing shapes. A circle and a rectangle really are both shapes. They share a piece of state, a name, and a finished method, describe, that reads the same for every shape. Only the way each one works out its area is different. That is the pattern an abstract class is made for: shared state, shared code, and one blank that each subclass fills. Step through it and watch super("Circle") jump into Shape's constructor.
public class Main { public static void main(String[] args) { Circle c = new Circle(2.0); Rectangle r = new Rectangle(3.0, 4.0); Shape[] shapes = { c, r }; for (int i = 0; i < shapes.length; i++) { System.out.println(shapes[i].describe()); } }} abstract class Shape { String name; Shape(String name) { this.name = name; } abstract double area(); String describe() { return name + " has area " + area(); }} class Circle extends Shape { double radius; Circle(double radius) { super("Circle"); this.radius = radius; } double area() { return 3.14 * radius * radius; }} class Rectangle extends Shape { double width; double height; Rectangle(double width, double height) { super("Rectangle"); this.width = width; this.height = height; } double area() { return width * height; }}The output is Circle has area 12.56, then Rectangle has area 12.0. The abstract class gave you three things here. It holds state: every shape has a name. It has a constructor, which the subclasses call with super(...). And it holds finished code, describe, written once and inherited by both. An interface could not have given you the name field or the constructor.
An abstract class can have a constructor even though you can never write new Shape(...). Here each constructor prints a line. Decide what the program prints before you run it.
public class Main { public static void main(String[] args) { Shape s = new Square(3); System.out.println(s.describe()); }} abstract class Shape { String name; Shape(String name) { System.out.println("Shape constructor: " + name); this.name = name; } abstract int area(); String describe() { return name + " has area " + area(); }} class Square extends Shape { int side; Square(int side) { super("Square"); System.out.println("Square constructor: side " + side); this.side = side; } int area() { return side * side; }}You can never build a plain Shape. What do the three lines print?
It prints Shape constructor: Square, then Square constructor: side 3, then Square has area 9. Building a Square builds the Shape part of the object first: super("Square") runs Shape's constructor before the rest of Square's. So the abstract class's constructor does run, as part of building a subclass object. An interface has nothing like it, because it has no constructor at all.
Shared code in an abstract class can also fix the order in which things happen. Below, HotDrink owns a recipe with three steps. It writes the two steps that are the same for every drink and leaves brew blank, so each drink fills in only the step that differs.
Tap a line, then tap where it should go. Or use the arrows.
When the interface fits: unrelated printables#
Now a different problem. A bank invoice and a holiday photo have nothing in common: one is a money document, the other is a picture. Try the is-a sentence, “an invoice is-a...”, and nothing sensible fits, because no natural parent kind covers both. They do share one capability: both can be sent to a printer. That is a can-do, so the right tool is an interface.
public class Main { public static void main(String[] args) { Printable[] things = { new Invoice(250), new Photo("sunset") }; for (int i = 0; i < things.length; i++) { things[i].print(); } }} interface Printable { void print();} class Invoice implements Printable { int total; Invoice(int total) { this.total = total; } public void print() { System.out.println("Invoice total: " + total); }} class Photo implements Printable { String title; Photo(String title) { this.title = title; } public void print() { System.out.println("Photo: " + title); }}The output is Invoice total: 250, then Photo: sunset. Invoice and Photo keep their own, completely different fields, total and title, and nothing is forced on them. They are not relatives; they only keep the same promise. The for loop treats both as Printable and calls print without knowing which is which. That is polymorphism again, this time over a capability instead of a family.
Printable an abstract class instead. Invoice would have to say extends Printable, spending its one and only superclass on it. If Invoice later needed to extend a real Document base class too, it could not, because a class gets only one parent. With Printable as an interface, Invoice stays free to extend something real and still be printable.Both tools in one design#
You do not have to pick one tool for a whole design. Often you use both: an abstract class for the family, and interfaces for the capabilities. Here a Dog is-an Animal, so Animal is an abstract class with a shared name field and a shared eat method. A Dog can-do Soundmaker too, and so can a Robot, which is no kind of animal.
public class Main { public static void main(String[] args) { Dog rex = new Dog("Rex"); Robot bot = new Robot(); rex.eat(); Soundmaker[] noisy = { rex, bot }; for (int i = 0; i < noisy.length; i++) { noisy[i].makeSound(); } }} interface Soundmaker { void makeSound();} abstract class Animal { String name; Animal(String name) { this.name = name; } void eat() { System.out.println(name + " is eating"); }} class Dog extends Animal implements Soundmaker { Dog(String name) { super(name); } public void makeSound() { System.out.println(name + " says woof"); }} class Robot implements Soundmaker { public void makeSound() { System.out.println("Robot says beep"); }}The output is Rex is eating, then Rex says woof, then Robot says beep. Each tool does the job it is good at. Animal is abstract because animals are the same kind of thing and share the name field and the eat code. Soundmaker is an interface because making a sound crosses families: a Robot does it without being an animal.
Notice that Animal says nothing about sound. A goldfish is an animal that makes no sound, so a Goldfish class would extend Animal and leave Soundmaker out. The family and the capability are separate choices. Dog takes both in one header, extends Animal implements Soundmaker, which Java allows because only one of the two is a class.
Key ideaUse an abstract class for the kind of thing an object is, and interfaces for what it can do. A class extends one kind and can implement many capabilities.
Dog is an Animal (an abstract class) and can make a sound (the Soundmaker interface). Which first line of the class compiles?Cat is an animal that says meow, so it belongs in the Animal family and keeps the Soundmaker promise. A Doorbell is not an animal at all: it only keeps the promise, and prints Ding dong. Write both classes, then add a cat called Tom and a doorbell to the end of the array.When the design grows#
A design is never finished. Months after you write Printable, someone asks for a print preview. The two tools react to a new method in very different ways.
A new method with no body breaks every class that implements it
Add one line to the interface, void preview();, and every class that implements Printable has broken its promise, because none of them has a previewmethod yet. The comments show javac's messages:
interface Printable { void print(); void preview(); // the new line} // error: Invoice is not abstract and does not override abstract method preview() in Printableclass Invoice implements Printable { int total; Invoice(int total) { this.total = total; } public void print() { System.out.println("Invoice total: " + total); }} // error: Photo is not abstract and does not override abstract method preview() in Printableclass Photo implements Printable { String title; Photo(String title) { this.title = title; } public void print() { System.out.println("Photo: " + title); }}In your own program you would add preview to both classes and move on. A library cannot do that: thousands of programs it has never seen implement its interfaces, and all of them would stop compiling. An abstract class does not have this problem. Add a finished method to the base class and every subclass inherits it, with nothing to fix.
Default methods let an interface grow
This is the reason default methods exist. For Java 8, the Java team wanted to give the collection interfaces new methods, such as sort on List and forEach on every collection. Added as plain promises, they would have broken every list class anyone had ever written. As default methods they came with a body, so existing classes inherited them and kept compiling. You can do the same with preview:
public class Main { public static void main(String[] args) { Printable[] things = { new Invoice(250), new Photo("sunset") }; for (Printable p : things) { p.preview(); } }} interface Printable { void print(); default void preview() { System.out.print("Preview: "); print(); }} class Invoice implements Printable { int total; Invoice(int total) { this.total = total; } public void print() { System.out.println("Invoice total: " + total); }} class Photo implements Printable { String title; Photo(String title) { this.title = title; } public void print() { System.out.println("Photo: " + title); }}It prints Preview: Invoice total: 250 and Preview: Photo: sunset, and neither Invoice nor Photo changed. The default method is built from print(), a method the interface already promises. That is all a default method can build on: the interface's other methods, never fields that belong to one object.
A class may still write its own version of a default method. Photo would rather show a small thumbnail than the whole photo. Decide what the loop prints now.
public class Main { public static void main(String[] args) { Printable[] things = { new Invoice(250), new Photo("sunset") }; for (Printable p : things) { p.preview(); } }} interface Printable { void print(); default void preview() { System.out.print("Preview: "); print(); }} class Invoice implements Printable { int total; Invoice(int total) { this.total = total; } public void print() { System.out.println("Invoice total: " + total); }} class Photo implements Printable { String title; Photo(String title) { this.title = title; } public void print() { System.out.println("Photo: " + title); } public void preview() { System.out.println("Thumbnail of " + title); }}Photo now writes its own preview. What does the loop print?
It prints Preview: Invoice total: 250, then Thumbnail of sunset. Invoice has no preview of its own, so it uses the interface's default. Photo's own method replaces the default, the same way an overriding method replaced an inherited one in method overriding. A method written in the class always wins over a default from an interface.
Key ideaAdding a method with no body to an interface breaks every class that implements it. A default method adds behavior without breaking anything, built from the methods the interface already promises.
A short checklist you can use#
When you next have to choose, go down this list from the top. The first clear yes usually decides it.
- Do the classes need to share real fields, or methods that work on those fields? Then it must be an abstract class. Interfaces cannot carry per-object state.
- Does one thing need several independent capabilities at once? Then use interfaces, because a class can implement many but extend only one.
- Read the sentences “X is-a Y” and “X can-do Y”. Whichever sounds natural points at the tool: is-a leans toward an abstract class, can-do toward an interface.
- Are the classes otherwise unrelated, sharing only one action? That is a classic interface.
- Still torn? Prefer the interface. It never spends a class's one superclass, and if real shared state turns up later you can still add an abstract class that implements the interface, the way
AbstractListdoes forList.
Comparable (can be put in order), and the Collections Framework shows that List, Set and Map are interfaces with several classes behind each one.Try it
Answer each one in your head or on paper first, then open the answer.
Bird, Plane and Drone, which are unrelated but can all fly(). Abstract class or interface? Why?Animal and Soundmaker example, why was Animal an abstract class but Soundmaker an interface? Name the deciding factor for each.extends two abstract classes at once. Explain.Printable an abstract class. Later Invoice needs to extend a real Document base class as well. What goes wrong, and what is the fix?void preview(); with no body to an interface that hundreds of classes implement. What happens to those classes, and how could the library have added preview without breaking them?Practice
Work through these on your own. Each one builds on this page.
CardPayment and CashPayment are the same kind of thing and share an amount field and a finished summary() method. Separately, some payments can be refunded and some cannot. Decide which idea is an abstract class and which is an interface, and write one sentence justifying each with the is-a or can-do test.Square, that extends Shape and works out its own area. Check that the inherited describe method works for it without any change. In a comment, note which two things the abstract Shape gave Square for free.Animal and Soundmaker program and add a second capability interface, Trainable, with a method doTrick(). Make Dog implement both Soundmaker and Trainable while still extending Animal, and let Robot implement only Soundmaker. In one sentence, explain why Dog can take on two capabilities but only one superclass.Soundmaker a default method makeSoundTwice() that calls makeSound() two times. Check that Dog and Robot compile without any change. Then give Robot its own makeSoundTwice() that prints Robot says beep beep on one line, and predict which version runs for each object before you run it.Further reading#
- Abstract Methods and Classes on dev.java, which ends with the official comparison of abstract classes and interfaces
- The interfaces tutorial on dev.java, on defining interfaces and using them as types
- Default Methods in Oracle's Java Tutorials, on adding methods to an interface without breaking the classes that implement it
- Interfaces in the Java Language Specification, chapter 9, the exact rules for interface fields, methods and defaults
Spotted a mistake, or a part that did not make sense? Report it by email