OOP Advanced
Static Interface Methods
Utility Functions
You want List.of(1, 2, 3) to create a list. Static interface methods let
interfaces have utility functions without needing a separate utility class.
The method belongs to the interface itself, not to implementing classes.
Basic static method
Define a utility method on an interface.
// Basic Static Method Syntax
interface MathUtils {
// Static method in interface
static int square(int n) {
return n * n;
}
static int cube(int n) {
return n * n * n;
}
static boolean isEven(int n) {
return n % 2 == 0;
}
static boolean isPositive(int n) {
return n > 0;
}
// Static methods can call other static methods
static boolean isPositiveEven(int n) {
return isPositive(n) && isEven(n);
}
}
// Class implementing interface
class Calculator implements MathUtils {
// Does NOT inherit static methods!
public int add(int a, int b) {
return a + b;
}
}
public class StaticBasics {
public static void main(String[] args) {
System.out.println("=== Static Interface Methods ===\n");
// Call via interface name
System.out.println("MathUtils.square(5) = " + MathUtils.square(5));
System.out.println("MathUtils.cube(3) = " + MathUtils.cube(3));
System.out.println("MathUtils.isEven(4) = " + MathUtils.isEven(4));
System.out.println("MathUtils.isEven(7) = " + MathUtils.isEven(7));
System.out.println("MathUtils.isPositiveEven(6) = " + MathUtils.isPositiveEven(6));
System.out.println("\n=== Using with Calculator ===");
Calculator calc = new Calculator();
System.out.println("calc.add(2, 3) = " + calc.add(2, 3));
// Cannot call static via instance!
// calc.square(5); // COMPILE ERROR!
// Must use interface name
System.out.println("MathUtils.square(5) = " + MathUtils.square(5));
System.out.println("\n=== Static vs Instance ===");
System.out.println("""
Static methods in interfaces:
- Belong to the interface itself
- Called via InterfaceName.method()
- NOT inherited by implementing classes
- Cannot be overridden
- Good for utility functions
Default methods (for comparison):
- Belong to instances
- Called via object.method()
- ARE inherited by implementing classes
- CAN be overridden
""");
}
}
static methods belong to interface. Call via InterfaceName.method().
Factory methods
Static methods that create instances.
// Factory Methods in Interfaces
interface Shape {
String getName();
double getArea();
// Static factory methods
static Shape circle(double radius) {
return new Circle(radius);
}
static Shape rectangle(double width, double height) {
return new Rectangle(width, height);
}
static Shape square(double side) {
return new Rectangle(side, side); // Square is special rectangle
}
}
// Implementation classes can be package-private
class Circle implements Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public String getName() {
return "Circle(r=" + radius + ")";
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width, height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public String getName() {
return "Rectangle(" + width + "x" + height + ")";
}
@Override
public double getArea() {
return width * height;
}
}
// Another example: immutable collections pattern
interface ImmutableList<T> {
int size();
T get(int index);
// Factory methods like List.of()
static <T> ImmutableList<T> of() {
return new EmptyList<>();
}
static <T> ImmutableList<T> of(T item) {
return new SingleList<>(item);
}
@SafeVarargs
static <T> ImmutableList<T> of(T... items) {
return new ArrayBackedList<>(items.clone());
}
}
class EmptyList<T> implements ImmutableList<T> {
@Override public int size() { return 0; }
@Override public T get(int index) { throw new IndexOutOfBoundsException(); }
}
class SingleList<T> implements ImmutableList<T> {
private T item;
SingleList(T item) { this.item = item; }
@Override public int size() { return 1; }
@Override public T get(int index) {
if (index != 0) throw new IndexOutOfBoundsException();
return item;
}
}
class ArrayBackedList<T> implements ImmutableList<T> {
private Object[] items;
ArrayBackedList(Object[] items) { this.items = items; }
@Override public int size() { return items.length; }
@Override @SuppressWarnings("unchecked")
public T get(int index) { return (T) items[index]; }
}
public class FactoryMethods {
public static void main(String[] args) {
System.out.println("=== Shape Factory Methods ===\n");
// Create shapes via interface
double circleRadius = ;
Shape circle = Shape.circle(circleRadius);
Shape rect = Shape.rectangle(4, 6);
Shape square = Shape.square(3);
System.out.println(circle.getName() + " area: " + String.format("%.2f", circle.getArea()));
System.out.println(rect.getName() + " area: " + rect.getArea());
System.out.println(square.getName() + " area: " + square.getArea());
System.out.println("\n=== Benefits of Factory Methods ===");
System.out.println("""
1. Hide implementations - users don't know about Circle/Rectangle
2. Return appropriate subtype - square() returns Rectangle
3. Caching possible - can reuse instances
4. Validation - can check parameters
""");
System.out.println("=== Immutable List Factory ===\n");
// Like Java's List.of()
ImmutableList<String> empty = ImmutableList.of();
ImmutableList<String> single = ImmutableList.of("hello");
ImmutableList<String> multi = ImmutableList.of("a", "b", "c");
System.out.println("empty.size() = " + empty.size());
System.out.println("single.get(0) = " + single.get(0));
System.out.println("multi.size() = " + multi.size());
for (int i = 0; i < multi.size(); i++) {
System.out.println("multi.get(" + i + ") = " + multi.get(i));
}
System.out.println("\n=== Real Java Examples ===");
System.out.println("""
Java's static factory methods:
- List.of("a", "b", "c")
- Set.of(1, 2, 3)
- Map.of("key", "value")
- Optional.of(value)
- Optional.empty()
- Stream.of(items)
- Comparator.comparing(keyExtractor)
""");
}
}
// Factory Methods in Interfaces
interface Shape {
String getName();
double getArea();
// Static factory methods
static Shape circle(double radius) {
return new Circle(radius);
}
static Shape rectangle(double width, double height) {
return new Rectangle(width, height);
}
static Shape square(double side) {
return new Rectangle(side, side); // Square is special rectangle
}
}
// Implementation classes can be package-private
class Circle implements Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public String getName() {
return "Circle(r=" + radius + ")";
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width, height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public String getName() {
return "Rectangle(" + width + "x" + height + ")";
}
@Override
public double getArea() {
return width * height;
}
}
// Another example: immutable collections pattern
interface ImmutableList<T> {
int size();
T get(int index);
// Factory methods like List.of()
static <T> ImmutableList<T> of() {
return new EmptyList<>();
}
static <T> ImmutableList<T> of(T item) {
return new SingleList<>(item);
}
@SafeVarargs
static <T> ImmutableList<T> of(T... items) {
return new ArrayBackedList<>(items.clone());
}
}
class EmptyList<T> implements ImmutableList<T> {
@Override public int size() { return 0; }
@Override public T get(int index) { throw new IndexOutOfBoundsException(); }
}
class SingleList<T> implements ImmutableList<T> {
private T item;
SingleList(T item) { this.item = item; }
@Override public int size() { return 1; }
@Override public T get(int index) {
if (index != 0) throw new IndexOutOfBoundsException();
return item;
}
}
class ArrayBackedList<T> implements ImmutableList<T> {
private Object[] items;
ArrayBackedList(Object[] items) { this.items = items; }
@Override public int size() { return items.length; }
@Override @SuppressWarnings("unchecked")
public T get(int index) { return (T) items[index]; }
}
public class FactoryMethods {
public static void main(String[] args) {
System.out.println("=== Shape Factory Methods ===\n");
// Create shapes via interface
double circleRadius = ;
Shape circle = Shape.circle(circleRadius);
Shape rect = Shape.rectangle(4, 6);
Shape square = Shape.square(3);
System.out.println(circle.getName() + " area: " + String.format("%.2f", circle.getArea()));
System.out.println(rect.getName() + " area: " + rect.getArea());
System.out.println(square.getName() + " area: " + square.getArea());
System.out.println("\n=== Benefits of Factory Methods ===");
System.out.println("""
1. Hide implementations - users don't know about Circle/Rectangle
2. Return appropriate subtype - square() returns Rectangle
3. Caching possible - can reuse instances
4. Validation - can check parameters
""");
System.out.println("=== Immutable List Factory ===\n");
// Like Java's List.of()
ImmutableList<String> empty = ImmutableList.of();
ImmutableList<String> single = ImmutableList.of("hello");
ImmutableList<String> multi = ImmutableList.of("a", "b", "c");
System.out.println("empty.size() = " + empty.size());
System.out.println("single.get(0) = " + single.get(0));
System.out.println("multi.size() = " + multi.size());
for (int i = 0; i < multi.size(); i++) {
System.out.println("multi.get(" + i + ") = " + multi.get(i));
}
System.out.println("\n=== Real Java Examples ===");
System.out.println("""
Java's static factory methods:
- List.of("a", "b", "c")
- Set.of(1, 2, 3)
- Map.of("key", "value")
- Optional.of(value)
- Optional.empty()
- Stream.of(items)
- Comparator.comparing(keyExtractor)
""");
}
}
// Factory Methods in Interfaces
interface Shape {
String getName();
double getArea();
// Static factory methods
static Shape circle(double radius) {
return new Circle(radius);
}
static Shape rectangle(double width, double height) {
return new Rectangle(width, height);
}
static Shape square(double side) {
return new Rectangle(side, side); // Square is special rectangle
}
}
// Implementation classes can be package-private
class Circle implements Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public String getName() {
return "Circle(r=" + radius + ")";
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width, height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public String getName() {
return "Rectangle(" + width + "x" + height + ")";
}
@Override
public double getArea() {
return width * height;
}
}
// Another example: immutable collections pattern
interface ImmutableList<T> {
int size();
T get(int index);
// Factory methods like List.of()
static <T> ImmutableList<T> of() {
return new EmptyList<>();
}
static <T> ImmutableList<T> of(T item) {
return new SingleList<>(item);
}
@SafeVarargs
static <T> ImmutableList<T> of(T... items) {
return new ArrayBackedList<>(items.clone());
}
}
class EmptyList<T> implements ImmutableList<T> {
@Override public int size() { return 0; }
@Override public T get(int index) { throw new IndexOutOfBoundsException(); }
}
class SingleList<T> implements ImmutableList<T> {
private T item;
SingleList(T item) { this.item = item; }
@Override public int size() { return 1; }
@Override public T get(int index) {
if (index != 0) throw new IndexOutOfBoundsException();
return item;
}
}
class ArrayBackedList<T> implements ImmutableList<T> {
private Object[] items;
ArrayBackedList(Object[] items) { this.items = items; }
@Override public int size() { return items.length; }
@Override @SuppressWarnings("unchecked")
public T get(int index) { return (T) items[index]; }
}
public class FactoryMethods {
public static void main(String[] args) {
System.out.println("=== Shape Factory Methods ===\n");
// Create shapes via interface
double circleRadius = ;
Shape circle = Shape.circle(circleRadius);
Shape rect = Shape.rectangle(4, 6);
Shape square = Shape.square(3);
System.out.println(circle.getName() + " area: " + String.format("%.2f", circle.getArea()));
System.out.println(rect.getName() + " area: " + rect.getArea());
System.out.println(square.getName() + " area: " + square.getArea());
System.out.println("\n=== Benefits of Factory Methods ===");
System.out.println("""
1. Hide implementations - users don't know about Circle/Rectangle
2. Return appropriate subtype - square() returns Rectangle
3. Caching possible - can reuse instances
4. Validation - can check parameters
""");
System.out.println("=== Immutable List Factory ===\n");
// Like Java's List.of()
ImmutableList<String> empty = ImmutableList.of();
ImmutableList<String> single = ImmutableList.of("hello");
ImmutableList<String> multi = ImmutableList.of("a", "b", "c");
System.out.println("empty.size() = " + empty.size());
System.out.println("single.get(0) = " + single.get(0));
System.out.println("multi.size() = " + multi.size());
for (int i = 0; i < multi.size(); i++) {
System.out.println("multi.get(" + i + ") = " + multi.get(i));
}
System.out.println("\n=== Real Java Examples ===");
System.out.println("""
Java's static factory methods:
- List.of("a", "b", "c")
- Set.of(1, 2, 3)
- Map.of("key", "value")
- Optional.of(value)
- Optional.empty()
- Stream.of(items)
- Comparator.comparing(keyExtractor)
""");
}
}
List.of(), Map.of() are static factory methods on interfaces.
Utility methods
Helper functions related to the interface's purpose.
// Utility Methods in Interfaces
interface StringUtils {
// Null-safe operations
static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
static boolean isBlank(String s) {
return s == null || s.isBlank();
}
static String nullToEmpty(String s) {
return s == null ? "" : s;
}
// String transformations
static String reverse(String s) {
if (isEmpty(s)) return s;
return new StringBuilder(s).reverse().toString();
}
static String capitalize(String s) {
if (isEmpty(s)) return s;
return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
}
static String repeat(String s, int times) {
if (isEmpty(s) || times <= 0) return "";
return s.repeat(times);
}
// Validation
static boolean isAlpha(String s) {
if (isEmpty(s)) return false;
return s.chars().allMatch(Character::isLetter);
}
static boolean isNumeric(String s) {
if (isEmpty(s)) return false;
return s.chars().allMatch(Character::isDigit);
}
}
interface ArrayUtils {
// Array checks
static boolean isEmpty(int[] arr) {
return arr == null || arr.length == 0;
}
static boolean contains(int[] arr, int value) {
if (isEmpty(arr)) return false;
for (int item : arr) {
if (item == value) return true;
}
return false;
}
// Statistics
static int sum(int[] arr) {
if (isEmpty(arr)) return 0;
int total = 0;
for (int item : arr) total += item;
return total;
}
static double average(int[] arr) {
if (isEmpty(arr)) return 0.0;
return (double) sum(arr) / arr.length;
}
static int max(int[] arr) {
if (isEmpty(arr)) throw new IllegalArgumentException("Array is empty");
int result = arr[0];
for (int item : arr) {
if (item > result) result = item;
}
return result;
}
static int min(int[] arr) {
if (isEmpty(arr)) throw new IllegalArgumentException("Array is empty");
int result = arr[0];
for (int item : arr) {
if (item < result) result = item;
}
return result;
}
// Transformations
static int[] reverse(int[] arr) {
if (isEmpty(arr)) return arr;
int[] result = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result[i] = arr[arr.length - 1 - i];
}
return result;
}
}
public class UtilityMethods {
public static void main(String[] args) {
System.out.println("=== StringUtils ===\n");
// Null safety
System.out.println("isEmpty(null): " + StringUtils.isEmpty(null));
System.out.println("isEmpty(\"\"): " + StringUtils.isEmpty(""));
System.out.println("isEmpty(\"hello\"): " + StringUtils.isEmpty("hello"));
System.out.println("isBlank(\" \"): " + StringUtils.isBlank(" "));
// Transformations
System.out.println("\nreverse(\"hello\"): " + StringUtils.reverse("hello"));
System.out.println("capitalize(\"jOHN\"): " + StringUtils.capitalize("jOHN"));
System.out.println("repeat(\"ab\", 3): " + StringUtils.repeat("ab", 3));
// Validation
System.out.println("\nisAlpha(\"Hello\"): " + StringUtils.isAlpha("Hello"));
System.out.println("isAlpha(\"Hello123\"): " + StringUtils.isAlpha("Hello123"));
System.out.println("isNumeric(\"12345\"): " + StringUtils.isNumeric("12345"));
System.out.println("\n=== ArrayUtils ===\n");
int[] numbers = {5, 2, 8, 1, 9, 3};
int[] empty = {};
// Checks
System.out.println("isEmpty(numbers): " + ArrayUtils.isEmpty(numbers));
System.out.println("isEmpty(empty): " + ArrayUtils.isEmpty(empty));
System.out.println("contains(numbers, 8): " + ArrayUtils.contains(numbers, 8));
System.out.println("contains(numbers, 7): " + ArrayUtils.contains(numbers, 7));
// Statistics
System.out.println("\nsum(numbers): " + ArrayUtils.sum(numbers));
System.out.println("average(numbers): " + ArrayUtils.average(numbers));
System.out.println("max(numbers): " + ArrayUtils.max(numbers));
System.out.println("min(numbers): " + ArrayUtils.min(numbers));
// Transformations
System.out.print("\nreverse(numbers): ");
for (int n : ArrayUtils.reverse(numbers)) {
System.out.print(n + " ");
}
System.out.println();
System.out.println("\n=== Why Interfaces for Utilities? ===");
System.out.println("""
Before Java 8:
- Utility classes with private constructor
- All static methods
- Couldn't use interface
With Java 8:
- Interfaces can have static methods
- Cleaner organization
- Can combine with default/abstract methods
Benefits:
- No need for private constructor hack
- Cleaner than abstract class
- Groups related utilities
""");
}
}
Group related utilities on the interface instead of separate helper class.
Static with default
Combine static and default methods in one interface.
// Combining Static and Default Methods
interface Validator<T> {
// Abstract - each validator implements differently
boolean isValid(T value);
String getErrorMessage();
// Default - common behavior
default ValidationResult validate(T value) {
if (isValid(value)) {
return ValidationResult.success();
}
return ValidationResult.failure(getErrorMessage());
}
// Static factory methods for common validators
static Validator<String> notEmpty() {
return new Validator<>() {
@Override
public boolean isValid(String value) {
return value != null && !value.isEmpty();
}
@Override
public String getErrorMessage() {
return "Value cannot be empty";
}
};
}
static Validator<String> minLength(int min) {
return new Validator<>() {
@Override
public boolean isValid(String value) {
return value != null && value.length() >= min;
}
@Override
public String getErrorMessage() {
return "Value must be at least " + min + " characters";
}
};
}
static Validator<Integer> range(int min, int max) {
return new Validator<>() {
@Override
public boolean isValid(Integer value) {
return value != null && value >= min && value <= max;
}
@Override
public String getErrorMessage() {
return "Value must be between " + min + " and " + max;
}
};
}
// Static combinator methods
static <T> Validator<T> and(Validator<T> v1, Validator<T> v2) {
return new Validator<>() {
@Override
public boolean isValid(T value) {
return v1.isValid(value) && v2.isValid(value);
}
@Override
public String getErrorMessage() {
return v1.getErrorMessage() + " AND " + v2.getErrorMessage();
}
};
}
}
// Simple result class
class ValidationResult {
private final boolean valid;
private final String message;
private ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
static ValidationResult success() {
return new ValidationResult(true, "OK");
}
static ValidationResult failure(String message) {
return new ValidationResult(false, message);
}
@Override
public String toString() {
return valid ? "✓ Valid" : "✗ Invalid: " + message;
}
}
// Custom validator
class EmailValidator implements Validator<String> {
@Override
public boolean isValid(String value) {
return value != null && value.contains("@") && value.contains(".");
}
@Override
public String getErrorMessage() {
return "Invalid email format";
}
}
public class StaticWithDefault {
public static void main(String[] args) {
System.out.println("=== Validator Interface ===\n");
// Using static factory methods
Validator<String> notEmpty = Validator.notEmpty();
int minChars = ;
Validator<String> minLengthRule = Validator.minLength(minChars);
Validator<Integer> ageRange = Validator.range(18, 65);
System.out.println("--- notEmpty validator ---");
System.out.println("\"hello\": " + notEmpty.validate("hello"));
System.out.println("\"\": " + notEmpty.validate(""));
System.out.println("null: " + notEmpty.validate(null));
System.out.println("\n--- minLength(" + minChars + ") validator ---");
System.out.println("\"hello\": " + minLengthRule.validate("hello"));
System.out.println("\"hi\": " + minLengthRule.validate("hi"));
System.out.println("\n--- range(18, 65) validator ---");
System.out.println("25: " + ageRange.validate(25));
System.out.println("15: " + ageRange.validate(15));
System.out.println("70: " + ageRange.validate(70));
// Using combinator
System.out.println("\n--- Combined validator (notEmpty AND minLength) ---");
Validator<String> combined = Validator.and(notEmpty, minLengthRule);
System.out.println("\"hello world\": " + combined.validate("hello world"));
System.out.println("\"hi\": " + combined.validate("hi"));
System.out.println("\"\": " + combined.validate(""));
// Custom validator uses default method
System.out.println("\n--- Custom EmailValidator ---");
Validator<String> email = new EmailValidator();
System.out.println("\"test@example.com\": " + email.validate("test@example.com"));
System.out.println("\"invalid-email\": " + email.validate("invalid-email"));
System.out.println("\n=== Design Summary ===");
System.out.println("""
Static methods:
- Validator.notEmpty() - factory for common validator
- Validator.minLength(n) - configurable factory
- Validator.range(min, max) - another factory
- Validator.and(v1, v2) - combinator
Default method:
- validate(value) - uses isValid() and getErrorMessage()
Abstract methods:
- isValid() - must implement
- getErrorMessage() - must implement
This pattern:
- Factories create pre-built validators
- Custom validators implement interface
- All get validate() for free
""");
}
}
// Combining Static and Default Methods
interface Validator<T> {
// Abstract - each validator implements differently
boolean isValid(T value);
String getErrorMessage();
// Default - common behavior
default ValidationResult validate(T value) {
if (isValid(value)) {
return ValidationResult.success();
}
return ValidationResult.failure(getErrorMessage());
}
// Static factory methods for common validators
static Validator<String> notEmpty() {
return new Validator<>() {
@Override
public boolean isValid(String value) {
return value != null && !value.isEmpty();
}
@Override
public String getErrorMessage() {
return "Value cannot be empty";
}
};
}
static Validator<String> minLength(int min) {
return new Validator<>() {
@Override
public boolean isValid(String value) {
return value != null && value.length() >= min;
}
@Override
public String getErrorMessage() {
return "Value must be at least " + min + " characters";
}
};
}
static Validator<Integer> range(int min, int max) {
return new Validator<>() {
@Override
public boolean isValid(Integer value) {
return value != null && value >= min && value <= max;
}
@Override
public String getErrorMessage() {
return "Value must be between " + min + " and " + max;
}
};
}
// Static combinator methods
static <T> Validator<T> and(Validator<T> v1, Validator<T> v2) {
return new Validator<>() {
@Override
public boolean isValid(T value) {
return v1.isValid(value) && v2.isValid(value);
}
@Override
public String getErrorMessage() {
return v1.getErrorMessage() + " AND " + v2.getErrorMessage();
}
};
}
}
// Simple result class
class ValidationResult {
private final boolean valid;
private final String message;
private ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
static ValidationResult success() {
return new ValidationResult(true, "OK");
}
static ValidationResult failure(String message) {
return new ValidationResult(false, message);
}
@Override
public String toString() {
return valid ? "✓ Valid" : "✗ Invalid: " + message;
}
}
// Custom validator
class EmailValidator implements Validator<String> {
@Override
public boolean isValid(String value) {
return value != null && value.contains("@") && value.contains(".");
}
@Override
public String getErrorMessage() {
return "Invalid email format";
}
}
public class StaticWithDefault {
public static void main(String[] args) {
System.out.println("=== Validator Interface ===\n");
// Using static factory methods
Validator<String> notEmpty = Validator.notEmpty();
int minChars = ;
Validator<String> minLengthRule = Validator.minLength(minChars);
Validator<Integer> ageRange = Validator.range(18, 65);
System.out.println("--- notEmpty validator ---");
System.out.println("\"hello\": " + notEmpty.validate("hello"));
System.out.println("\"\": " + notEmpty.validate(""));
System.out.println("null: " + notEmpty.validate(null));
System.out.println("\n--- minLength(" + minChars + ") validator ---");
System.out.println("\"hello\": " + minLengthRule.validate("hello"));
System.out.println("\"hi\": " + minLengthRule.validate("hi"));
System.out.println("\n--- range(18, 65) validator ---");
System.out.println("25: " + ageRange.validate(25));
System.out.println("15: " + ageRange.validate(15));
System.out.println("70: " + ageRange.validate(70));
// Using combinator
System.out.println("\n--- Combined validator (notEmpty AND minLength) ---");
Validator<String> combined = Validator.and(notEmpty, minLengthRule);
System.out.println("\"hello world\": " + combined.validate("hello world"));
System.out.println("\"hi\": " + combined.validate("hi"));
System.out.println("\"\": " + combined.validate(""));
// Custom validator uses default method
System.out.println("\n--- Custom EmailValidator ---");
Validator<String> email = new EmailValidator();
System.out.println("\"test@example.com\": " + email.validate("test@example.com"));
System.out.println("\"invalid-email\": " + email.validate("invalid-email"));
System.out.println("\n=== Design Summary ===");
System.out.println("""
Static methods:
- Validator.notEmpty() - factory for common validator
- Validator.minLength(n) - configurable factory
- Validator.range(min, max) - another factory
- Validator.and(v1, v2) - combinator
Default method:
- validate(value) - uses isValid() and getErrorMessage()
Abstract methods:
- isValid() - must implement
- getErrorMessage() - must implement
This pattern:
- Factories create pre-built validators
- Custom validators implement interface
- All get validate() for free
""");
}
}
// Combining Static and Default Methods
interface Validator<T> {
// Abstract - each validator implements differently
boolean isValid(T value);
String getErrorMessage();
// Default - common behavior
default ValidationResult validate(T value) {
if (isValid(value)) {
return ValidationResult.success();
}
return ValidationResult.failure(getErrorMessage());
}
// Static factory methods for common validators
static Validator<String> notEmpty() {
return new Validator<>() {
@Override
public boolean isValid(String value) {
return value != null && !value.isEmpty();
}
@Override
public String getErrorMessage() {
return "Value cannot be empty";
}
};
}
static Validator<String> minLength(int min) {
return new Validator<>() {
@Override
public boolean isValid(String value) {
return value != null && value.length() >= min;
}
@Override
public String getErrorMessage() {
return "Value must be at least " + min + " characters";
}
};
}
static Validator<Integer> range(int min, int max) {
return new Validator<>() {
@Override
public boolean isValid(Integer value) {
return value != null && value >= min && value <= max;
}
@Override
public String getErrorMessage() {
return "Value must be between " + min + " and " + max;
}
};
}
// Static combinator methods
static <T> Validator<T> and(Validator<T> v1, Validator<T> v2) {
return new Validator<>() {
@Override
public boolean isValid(T value) {
return v1.isValid(value) && v2.isValid(value);
}
@Override
public String getErrorMessage() {
return v1.getErrorMessage() + " AND " + v2.getErrorMessage();
}
};
}
}
// Simple result class
class ValidationResult {
private final boolean valid;
private final String message;
private ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
static ValidationResult success() {
return new ValidationResult(true, "OK");
}
static ValidationResult failure(String message) {
return new ValidationResult(false, message);
}
@Override
public String toString() {
return valid ? "✓ Valid" : "✗ Invalid: " + message;
}
}
// Custom validator
class EmailValidator implements Validator<String> {
@Override
public boolean isValid(String value) {
return value != null && value.contains("@") && value.contains(".");
}
@Override
public String getErrorMessage() {
return "Invalid email format";
}
}
public class StaticWithDefault {
public static void main(String[] args) {
System.out.println("=== Validator Interface ===\n");
// Using static factory methods
Validator<String> notEmpty = Validator.notEmpty();
int minChars = ;
Validator<String> minLengthRule = Validator.minLength(minChars);
Validator<Integer> ageRange = Validator.range(18, 65);
System.out.println("--- notEmpty validator ---");
System.out.println("\"hello\": " + notEmpty.validate("hello"));
System.out.println("\"\": " + notEmpty.validate(""));
System.out.println("null: " + notEmpty.validate(null));
System.out.println("\n--- minLength(" + minChars + ") validator ---");
System.out.println("\"hello\": " + minLengthRule.validate("hello"));
System.out.println("\"hi\": " + minLengthRule.validate("hi"));
System.out.println("\n--- range(18, 65) validator ---");
System.out.println("25: " + ageRange.validate(25));
System.out.println("15: " + ageRange.validate(15));
System.out.println("70: " + ageRange.validate(70));
// Using combinator
System.out.println("\n--- Combined validator (notEmpty AND minLength) ---");
Validator<String> combined = Validator.and(notEmpty, minLengthRule);
System.out.println("\"hello world\": " + combined.validate("hello world"));
System.out.println("\"hi\": " + combined.validate("hi"));
System.out.println("\"\": " + combined.validate(""));
// Custom validator uses default method
System.out.println("\n--- Custom EmailValidator ---");
Validator<String> email = new EmailValidator();
System.out.println("\"test@example.com\": " + email.validate("test@example.com"));
System.out.println("\"invalid-email\": " + email.validate("invalid-email"));
System.out.println("\n=== Design Summary ===");
System.out.println("""
Static methods:
- Validator.notEmpty() - factory for common validator
- Validator.minLength(n) - configurable factory
- Validator.range(min, max) - another factory
- Validator.and(v1, v2) - combinator
Default method:
- validate(value) - uses isValid() and getErrorMessage()
Abstract methods:
- isValid() - must implement
- getErrorMessage() - must implement
This pattern:
- Factories create pre-built validators
- Custom validators implement interface
- All get validate() for free
""");
}
}
Static for utilities, default for shared implementation, abstract for required behavior.
Not inherited
Static methods don't become part of implementing classes.
// Static Methods Are NOT Inherited
interface Counter {
// Static method
static int defaultStart() {
return 0;
}
// Default method
default void increment() {
System.out.println("Incrementing...");
}
// Abstract method
int getCount();
}
class SimpleCounter implements Counter {
private int count = Counter.defaultStart();
@Override
public int getCount() {
return count;
}
// Note: we DON'T have defaultStart() method!
// Static methods are NOT inherited
}
// Let's prove it
class InheritanceDemo {
// This would work with default method
static void testDefault(Counter counter) {
counter.increment(); // Works! Default is inherited
}
// Cannot call static via instance
static void showStaticDifference() {
SimpleCounter sc = new SimpleCounter();
// WORKS - default method via instance
sc.increment(); // inherited!
// DOES NOT WORK - static via instance
// sc.defaultStart(); // COMPILE ERROR!
// Must use interface name
int start = Counter.defaultStart();
System.out.println("Start value: " + start);
}
}
// What if class has same-named static method?
interface Vehicle {
static String getType() {
return "Generic Vehicle";
}
}
class Car implements Vehicle {
// This is NOT overriding!
static String getType() {
return "Car";
}
// It's a completely separate method
// Car.getType() and Vehicle.getType() are different
}
// Contrast with default methods
interface Animal {
default String speak() {
return "Some sound";
}
}
class Dog implements Animal {
@Override // This IS overriding
public String speak() {
return "Woof!";
}
}
public class NotInherited {
public static void main(String[] args) {
System.out.println("=== Static vs Default Inheritance ===\n");
// Default method IS inherited
System.out.println("--- Default Method (inherited) ---");
Dog dog = new Dog();
System.out.println("dog.speak() = " + dog.speak()); // Overridden
Animal genericAnimal = new Animal() {
// Uses default
};
System.out.println("genericAnimal.speak() = " + genericAnimal.speak()); // Default
// Static method NOT inherited
System.out.println("\n--- Static Method (NOT inherited) ---");
System.out.println("Vehicle.getType() = " + Vehicle.getType());
System.out.println("Car.getType() = " + Car.getType()); // Different method!
// They are completely separate
System.out.println("\n--- Proving Separation ---");
Vehicle v = new Car();
// v.getType(); // COMPILE ERROR - no instance method
System.out.println("Vehicle.getType() on Car instance: " + Vehicle.getType());
System.out.println("Car.getType() directly: " + Car.getType());
System.out.println("\n--- Counter Example ---");
SimpleCounter counter = new SimpleCounter();
counter.increment(); // Default - inherited
// counter.defaultStart(); // Static - NOT available!
System.out.println("Counter.defaultStart() = " + Counter.defaultStart());
System.out.println("counter.getCount() = " + counter.getCount());
System.out.println("\n=== Summary ===");
System.out.println("""
+-------------------+------------+--------------+
| Feature | Default | Static |
+-------------------+------------+--------------+
| Belongs to | Instance | Interface |
| Inherited | YES | NO |
| Can override | YES | NO |
| Call via instance | YES | NO |
| Call via interface| NO | YES |
+-------------------+------------+--------------+
Key insight:
- Default methods behave like instance methods
- Static methods belong ONLY to the interface
- Same-named static in class is completely separate
""");
}
}
Call via interface name only. Implementing class doesn't get the method.
Exercise: Practical.java
Build a complete interface with static factories and utilities