We’ve learned about three kinds of abstraction:
Variables abstract values.
When we define a variable we are defining the idea of how a whole set of values can be used.
int speed; // The speed something is moving.
String name; // Something's name.
At different times the variable will hold different values but in an abstract sense it will always represent the same thing.
Methods abstract computations.
public int distance(Point a, Point b) {
return Math.hypot(a.x - b.x, a.y - b.y);
}
Each call to a given method with different arguments does something different.
But the differences have been abstracted away so we can talk about “what the method does”.
A class defines instance variables, constructors, and methods.
It defines an abstraction over all objects with that particular shape (defined by the instance variables) and behavior (methods).
public class Student {
private String name;
private int grade;
private ArrayList<Course> courses;
public double gpa() { ... }
public int creditsEarned() { ... }
// etc.
}
Every instance of a class has its own state but all instances have the same set of methods, abstractly defining what we can do with those instances.
A well-designed class’s instance variables are almost always private.
So from the abstract view we only care about a class’s methods.
Why tie ourselves down?
Abstracting classes
Suppose we want to represent shapes we can manipulate and draw on the screen. We might want these methods that define what we can do with a shape.
public void draw(Graphics g)
public double area()
public Point centroid()
public void moveTo(Point p)
But there’s not really a way to define most of those methods for a generic shape, so it doesn’t really make sense as a class.
Interfaces exist to allow us to abstract over a set of classes that have similar behavior without regard to their structure.
public interface Shape {
public void draw(Graphics g);
public double area();
public Point centroid();
public void moveTo(Point p);
}
Use the keyword interface rather than class.
Instead of a a method body in {}s, after the method signature put a ;.
To use an interface we need a class that implements that interface.
public class Circle implements Shape { ... }
public class Triangle implements Shape { ... }
public class Rectangle implements Shape { ... }
public class Circle implements Shape {
private Point center;
private int r;
public Circle(int x, int y, int r) {
this.center = new Point(x, y);
this.r = r;
}
public void draw(Graphics g) {
g.drawOval(center.x - r, center.y - r, 2 * r, 2 * r);
}
public double area() { return Math.PI * r * r; }
public Point centroid() { return center; }
public void moveTo(Point p) { center = p; }
}
| Classes | Interfaces |
|---|---|
| Can have instance variables | Cannot have instance variables |
| Have constructors | Don’t have constructors |
| Specify structure and behavior | Specify only behavior |
Polymorphism. From the Greek “poly” meaning “many” and “morph” meaning “form”, so “many forms”.
This is the power move of object orientation.
for (Shape s : shapes) {
s.draw(g);
}
The code that runs each time draw is called is determined by the actual class that each Shape is an instance of.
Even more than classes, interfaces purely define a type.
Anywhere you can use a type (declaring a variable, an array type, a method or constructor parameter, or as the type parameter in a generic type) you can use an interface.
private Shape s = new Circle(0, 0, 100);
private Shape[] shapes = new Shape[10];
s can hold an instance of any class that implements Shape.
shapes is an array that can hold ten instances of any classes that implement Shape.
private int differenceInArea(Shape a, Shape b) {
return a.area() - b.area();
}
This method accepts two arguments that can be instances of any class that implements Shape. (Not necessarily the same class).
The code can call the area() method on them because it’s a method in the interface.
private List<Shape> shapes = new ArrayList<>();
Two for the price of one! List is an interface that captures the notion of a list of things of which ArrayList is a concrete implementation.
And the type parameter Shape is also an interface.
So the value of shapes will be an instance of a class that implements List and all the items in the list will be instances of classes that implement Shape.
Originally interfaces were just a collection of methods and constants (static final variables).
These days interfaces can have default methods which contain actual code.
But default methods can only be written in terms of other methods in the interface.
public interface Shape {
public void draw(Graphics g);
public double area();
public Point centroid();
public void moveTo(Point p);
public default double distanceTo(Shape other) {
return centroid().distance(other.centroid());
}
}
The distanceTo method can compute the distance between the centers of mass of any two shapes since all shapes have a centroid method.
Beyond just the information in the method signatures, the point of an interface is to specify a contract that code that uses the interface can rely on and that implementations of the interface must satisfy.
Think about:
Preconditions
Postconditions
java.lang.RunnableUsed to represent a task that can be run requiring no arguments.
Only one method: public void run()
java.lang.Comparable<T>Implemented by classes that have a natural ordering such as String.
Only one method: int compareTo(T o).
Used by Collections.sort(List<T>) and Arrays.sort(Object[])
Has an interesting contract. “This interface imposes a total ordering on the objects of each class that implements it.”
java.util.ListThe interface implemented by java.util.ArrayList and other classes.
Captures the idea of a sequential, ordered list of elements. Can contain duplicate elements.
Some Lists are immutable.
List.of() returns an immutable list of its arguments. We don’t even know what class it is.
Interfaces can extend other interfaces and add methods.
And a single interface can extends multiple other interfaces, creating an interface with the union of all the other intefaces’ methods plus any added in the new interface.
public interface PriorityTask
extends Runnable, Comparable<PriorityTask> {
public double getPriority();
}
Any class that implements this interface will be a Runnable and also Comparable with other PriorityTasks.
Which means any such class will need to provide run and compareTo methods as well as getPriority.