Private Methods in Java Interfaces
Evolution of Java Interfaces
In Java 1.0, an interface definition could contain only two kinds of members:
- final variable
- abstract methods
public interface Interface01 {
int finalInt = 9; // variable declaration
int abstractMethod(double n1, int n2); // method declaration
}
Inner classes introduced in Java 1.1. From Java 2 onward, we had the static nested classes and interfaces, and these could also be used inside an interface. Thus, since Java 2, we two extra kinds of members in an interface:
- inner (nested) interfaces
- inner (nested) classes
public interface Interface02 {
// Java 1 kinds of members
// inner interface definition
interface InnerInterface {
// member of an interface
}
// inner class definition
class InnerClass {
// members of a class
}
}
Java 5 introduced generics type parameters. Then now we can have generic classes, generic interfaces, and generic methods. So, from Java 5 onward, we could have another two members in an interface,
- inner (nested) enums
- inner (nested) annotations
and the abstract method now can use Generic type parameter
public interface Interface03<T> {
// Java 1 kinds of members
// Java 2 kinds of members
// method using type parameter
T abstractMethodUsingGenericType(T cls);
// nested enum definition
enum NestedEnum {
ENUM1,
ENUM2
}
// nested annotation definition
@interface NestedAnnotation {
String variable1();
}
}
Java 8 added default methods as members in the interface. This allowed the interface to be enhanced with new methods, providing a default implementation for the new method. Java 8 also allowed interfaces to include static methods. Therefore, from Java 8 onward, we could have another two members in an interface:
- default methods
- static methods
public interface Interface04<T> {
// Java 1 kinds of members
// Java 2 kinds of members
// Java 5 kinds of members
default void defaultMethod(int x, int y) {
// implementation of method
}
static void staticMethod(String[] args) {
// any static method can be included in interface
}
}
Check this article about default methods and static methods in Java interfaces.
Since we can implement behavior via default methods which is public in interface, there are possibilities that we have a repetitive, common code, which is duplicated in multiple default methods within the same interface. To avoid this, we should break the repetitive part of implementation of a method into another methods. But, in Java 8, it's also will becomes another default public method. Since these methods may not really be required to be available outside the interface (not public), they should ideally be private.
This can now be done with Java 9, which introduced private methods in an interface. These private methods will improve code re-usability inside interfaces. For example, if two default methods in some parts have repetitive codes, they should move this part into a private method.
public interface Interface05<T> {
// Java 1 kinds of members
// Java 2 kinds of members
// Java 5 kinds of members
// Java 8 kinds of members
private int privateMethod(T var) {
// private method implementation
return 0;
}
private static void privateStaticMethod(double n1, double n2) {
// private method implementation
}
}
Using private methods in interfaces have four rules :
- Private method cannot be abstract.
- Private method can be used only inside interface.
- Private static method can be used inside other static and non-static interface methods.
- Private non-static methods cannot be used inside private static methods.
Let's move to more concrete example below. Interface Number has private method isPrimeNumber() to check if a number is prime number or not. This interface also has a private static method print() to print a number following by space. You can see that every default method will rely to isPrimeNumber() private method to filter the number.
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
public interface Number {
default int sumPrimeNumbers(int... nums) {
return sum(n -> isPrimeNumber(n), nums);
}
default long countPrimeNumbers(int... nums) {
return count(n -> isPrimeNumber(n), nums);
}
default void printPrimeNumbers(int... nums) {
print(n -> isPrimeNumber(n), nums);
}
private boolean isPrimeNumber(int num) {
boolean isPrime = true;
for(int divisor = 2; divisor <= num / 2; divisor++) {
if (num % divisor == 0) {
isPrime = false;
break; // num is not a prime, no reason to continue checking
}
}
return isPrime;
}
private int sum(IntPredicate predicate, int... nums) {
return IntStream.of(nums).filter(predicate).sum();
}
private long count(IntPredicate predicate, int... nums) {
return IntStream.of(nums).filter(predicate).count();
}
private void print(IntPredicate predicate, int... nums) {
IntStream.of(nums).filter(predicate).forEach(x -> Number.print(x));
System.out.println();
}
private static void print(int n) {
System.out.print(n + " ");
}
}
public class MainNumber implements Number {
public static void main(String[] args) {
int[] nums = {1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,20};
Number number = new MainNumber();
long cntPrimeNumbers = number.countPrimeNumbers(nums);
System.out.println(cntPrimeNumbers);
number.printPrimeNumbers(nums);
int sumPrimeNumbers = number.sumPrimeNumbers(nums);
System.out.println(sumPrimeNumbers);
}
}
/*
Output:
------
9
1 2 3 5 7 11 13 17 19
78
*/
Summary
Overall, this is what Java interface since Java 9 have:
Since | members |
---|---|
Java 1 | (public abstract) method |
Java 2 | inner interfaces |
inner classes | |
Java 8 | (public) default method |
(public) static method | |
Java 9 | private method |
private static method |