Level 2 · 20 min
Abstraction
Abstraction is the process of hiding complex implementation details and showing only the necessary features. It lets you focus on what an object does rather than how it does it.
Interface vs Abstract Class
Interface: pure contract, no state, multiple implementation. Use when defining a capability that unrelated classes can have (Serializable, Comparable, Runnable). Abstract Class: partial implementation, can have state and concrete methods. Use when sharing implementation across closely related classes. Java 8+ default methods blur the line — use abstract class when you need constructors or non-public state. Bloch's Item 64 (Effective Java) crystallizes the rule: "If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types." Declaring LinkedHashSet<Son> sonSet = new LinkedHashSet<>() instead of Set<Son> sonSet = new LinkedHashSet<>() ties callers to the concrete type unnecessarily — swapping to HashSet later requires touching every declaration. Item 20 further refines the interface/abstract-class tradeoff: "Interfaces are generally superior" for defining types, but abstract classes can evolve more easily — adding a non-abstract method to an abstract class is safe, while adding a default method to an interface risks breaking existing implementations that already have a method with the same signature.
Design by Contract
Design by Contract (DbC) formalizes method contracts with preconditions (what the caller must guarantee), postconditions (what the implementer guarantees after the call), and invariants (what is always true). Abstraction establishes a contract — callers depend on the contract, not the implementation. Java has Javadoc @throws for documenting precondition violations.
Levels of Abstraction
Good code operates at a single level of abstraction per method/class. A method that processes an HTTP request should not also format SQL queries — mixing levels of abstraction is a design smell. When you read a method and find both high-level business logic ('place the order') and low-level implementation details ('execute prepared statement'), the method is poorly abstracted.
Code example
// Interface: capability contract
interface Sortable<T> {
int compareTo(T other); // contract: 0=equal, negative=less, positive=greater
}
// Abstract class: shared implementation for related classes
abstract class AbstractRepository<T, ID> {
protected final DataSource dataSource;
AbstractRepository(DataSource ds) { this.dataSource = ds; }
abstract String tableName();
void delete(ID id) {
// shared implementation using tableName() hook
String sql = "DELETE FROM " + tableName() + " WHERE id = ?";
// execute...
}
abstract Optional<T> findById(ID id); // subclass provides mapping
}
class UserRepository extends AbstractRepository<User, UUID> {
String tableName() { return "users"; }
Optional<User> findById(UUID id) { /* User-specific mapping */ }
}