Interfaces define a contract specifying required methods without providing implementation details. Unlike concrete classes, they establish a set of behavioral expectations that implementing classes must fulfill.
Implementing Comparable
To enable natural ordering within custom objects, a class must implement the Comparable interface. This generic interface mandates overriding the compareTo method. The syntax places the extends clause before implements.
class ProjectLead extends Staff implements Comparable<ProjectLead> {
private double budget;
@Override
public int compareTo(ProjectLead other) {
return Double.compare(this.budget, other.budget);
}
}
Before lambda epxressions, explicit comparators were often attached as static fields for specialized sorting scenarios:
public static Comparator<ProjectLead> budgetComparator = new Comparator<ProjectLead>() {
@Override
public int compare(ProjectLead p1, ProjectLead p2) {
return p1.compareTo(p2);
}
};
Array sorting would invoke this comparator:
Arrays.sort(projectLeads, ProjectLead.budgetComparator);
Interfaces Versus Abstract Classes
Abstract classes share state and behavior among related subclasses but restrict hierarchy depth due to Java's single inheritance model. Once a class extends a parent like OrganizationMember, it cannot extend another base class. Interfaces circumvent this limitation because a single class may implement numerous interfaces simultaneously, enabling flexible composition of behaviors across disparate hierarchies.
Lambda Expressions
Introduced in Java 8, lambda expressions provide a concise mechanism for representing instances of functional interfaces—interfaces containing exactly one abstract method. This eliminates verbose anonymous inner class declarations.
Syntax patterns include:
(params) -> expression(params) -> { statements; return value; }
Functional Interfaces
Any interface with a single unimplemented method qualifies as a target for lambda assignment. The following example demonstrates a custom arithmetic operation:
public class FunctionDemo {
public static void main(String[] args) {
PowerCalculator calculator = (base, exponent) -> {
int result = 1;
for (int i = 0; i < exponent; i++) {
result *= base;
}
return result;
};
System.out.println(calculator.calculate(5, 3)); // Outputs 125
}
@FunctionalInterface
interface PowerCalculator {
int calculate(int base, int exponent);
}
}
Streamlined Sorting
Lambda expressions drastically simplify comparator definitions. The prevoius array sorting example can be condensed into a single expression, removing the need for dedicated comparator classes entirely:
Arrays.sort(projectLeads, (p1, p2) -> Double.compare(p1.getBudget(), p2.getBudget()));
Collection Traversal
Built-in collection methods leverage functional programming patterns. The forEach consumer action accepts a lambda to execute operations on every element sequentially:
List<String> modules = Arrays.asList("Networking", "Security", "Database");
modules.forEach(moduleName -> System.out.println(moduleName));
Method References
When a lambda merely delegates to an existing method, method reference syntax offers further brevity. The traversal example above can be optimized by referencing the standard output method directly:
modules.forEach(System.out::println);
This approach reduces boilerplate while maintaining identical execution behavior.