To fully appreciate the benefits of private methods in interfaces, it’s crucial to first understand the role of default methods, released in Java 8 (2014) with JSR 335.
Now you can have private “helper” methods in your interfaces so that repeating logic can be tucked away and re-used in private (non-exposed) methods only useful for internal use in the interface.
Before Java 11:
Before Java 9, all methods in an interface were either public or default. Common functionality needed by multiple default methods had to be duplicated or exposed unnecessarily.
public interface BeforeDemo { default void sayHello(){ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); System.out.printf("%s: Hello there.\n", now.format(formatter)); } default void sayGoodbye(){ // Lots of code repetition here 😢 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); System.out.printf("%s: Goodbye.\n", now.format(formatter)); } }
Or…
public interface BeforeDemo { default void sayHello(){ printWithTimestamp("Hello there."); } default void sayGoodbye(){ printWithTimestamp("Goodbye."); } // Common code is exposed to the public despite being for internal use only 😢 default void printWithTimestamp(String msg){ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); System.out.printf("%s: %s\n", now.format(formatter), msg); } }
Both solutions are not ideal; we don’t want duplicate code, and we certainly don’t want to expose methods that are only there for internal use to avoid code repetition.
After Java 11:
We can avoid duplicate code and having to expose new public methods purely for tucking away common logic by using private
methods, designed for internal use!
public interface AfterDemo { default void sayHello(){ printWithTimestamp("Hello there."); } default void sayGoodbye(){ printWithTimestamp("Goodbye."); } // Common code is kept private for internal use only 🤩 private void printWithTimestamp(String msg){ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); System.out.printf("%s: %s\n", now.format(formatter), msg); } }
BeforeDemo
and AfterDemo
, and re-create the code shown in the examples above, illustrating the benefits of private methods.Calculator
-interface, have normal public abstract methods such as:add(a: double, b: double): double
subtract(a: double, b: double): double
multiply(a: double, b: double): double
divide(a: double, b: double): double
CalculatorImpl
-class and use them in the main
-method of a CalcApp
-class.Calculator
-interface, by adding 2 new methods:square(a: double): double
cube(a: double): double
default
implementation for these 2 methods (you should be able to re-run CalcApp
without issue).Calculator
-interface by getting rid of repeating logic, placing common logic in a reusable exponentiate
-method, but make sure it isn’t exposed (i.e. for internal use only), thereby keeping usage of your interface intact.square
– and cube
-methods in the CalcApp
-class without overriding them in the implementing class.public interface Calculator { double add(double a, double b); double subtract(double a, double b); double multiply(double a, double b); double divide(double a, double b); default double square(double a){ return exponentiate(a, 2); } default double cube(double a){ return exponentiate(a, 3); } private double exponentiate(double base, int exponent){ double total = 1; for(int i = 0; i < exponent; i++){ total *= base; } return total; } }
public class CalculatorImpl implements Calculator { @Override public double add(double a, double b) { return a + b; } @Override public double subtract(double a, double b) { return a - b; } @Override public double multiply(double a, double b) { return a * b; } @Override public double divide(double a, double b) { return a / b; } }
public class CalcApp { public static void main(String[] args) { Calculator calculator = new CalculatorImpl(); double x = 2; double y = 3; System.out.println(calculator.add(x, y)); System.out.println(calculator.subtract(x, y)); System.out.println(calculator.multiply(x, y)); System.out.println(calculator.divide(x, y)); System.out.println(calculator.square(x)); System.out.println(calculator.cube(x)); } }