Blog-Archiv

Sonntag, 28. März 2021

Java Interface Default Implementations

Since Java 8 we can write implementations into interfaces. As several interfaces can be implemented by a class, a class can now inherit actual code (not just responsibilities) from multiple different sources. Such inherited default implementations are overridable, and multiple-inheritance problems will detected and rejected by the Java 8 compiler.

Example

The interface Identified demands the presence of an identifier like a database primary key.
The interface Named demands the presence of a human readable name.
Objects of the example class Person should be both identified and named.

    public static void main(String[] args) {
        Person person = new Person(1L, "John Doe");
        System.out.println("Person id="+person.getId()+", name="+person.getName());
    }

This should output:

Person id=1, name=John Doe

Before Java 8

It was not possible to provide a default implementation of an interface responsibility, all implementations had to be in the class:

public interface Identified
{
    Object getId();
}

public interface Named
{
    String getName();
}

public class Person implements Identified, Named
{
    private final Object identity;
    private final String name;
   
    public Person(Object identity, String name)   {
        this.identity = identity;
        this.name = name;
    }
   
    @Override
    public Object getId() {
       return identity;
    }

    @Override
    public String getName() {
       return name;
    }
}

Since Java 8

In case the inherited interface responsibility happens to be optional, we can now provide a default implementation for any interface method:

public interface Named
{
    default String getName() {
        return "(undefined name)";
    }
}

public interface Identified
{
    default Object getId() {
        return null;
    }
}

As both interfaces provide defaults, the inheriting class is not required to implement anything:

public class Person implements Identified, Named
{
}

This compiles!
Output will be:

Person id=null, name=(undefined name)

Overriding Defaults

Most important here: default implementations can be overridden (other than static ones). In the Person example this actually makes sense. Following source shows an implementation that redirects getId() to the according constructor parameter:

public class Person implements Identified, Named
{
    private final Object identity;
   
    public Person(Object identity)   {
        this.identity = identity;
    }
   
    @Override
    public Object getId() {
       return identity;
    }
}

Conflict Solution

The "diamond problem" occurs in case two inherited interfaces provide a default method of same signature:

public interface Identified
{
    default boolean isDeleted() {
        return false;
    }
    
    ....
}

public interface Named
{
    default boolean isDeleted() {
        return true;
    
    ....
}

The isDeleted() method has been introduced on both interfaces, with different default implementations. Which one would be used at runtime by Person?

We don't need to think about this. Following error message is printed by the Java compiler when it comes to class Person:

Duplicate default methods named isDeleted with the parameters () and () are inherited from the types Named and Identified.

Following override would fix it:

public class Person implements Identified, Named
{
    ....

    @Override
    public boolean isDeleted() {
        return Identified.super.isDeleted();
    }

}

The class decides for one of the defaults, in this case for the implementation in interface Identified.

Conclusion

Interface default implementations could easily take over the role that static utility classes have been playing until now (StringUtils, IOUtils, ...). Theses classes provided "pure" method implementations that rely on parameters only, without using their enclosing class body in any way. The problem with these utils is that they are not overridable. Interface default methods are overridable and would solve this problem. Method signature clashes would be found by the compiler.

Keep in mind that interfaces still can not hold any state, that means default implementations need to be "pure", everything they need has to be passed to them as parameter. But an interface now is the perfect place for "pure" functions.

Mind further that interfaces methods are always public, implicitly. You can not reduce their visibility to protected or private in an override.




Sonntag, 14. März 2021

Java Interface as Function Pointer

Java interfaces can mark component boundaries, but their core capability is separation. Thus they can also isolate functions, which, in OO terms, are methods of an object. That means, through interfaces we can separate a method from all other methods of its object context and pass it around like any other object, without losing strong typing.

This Blog is about how you can simulate C function pointers in Java (before and after version 1.8).

Why Would We Need That?

Imagine a class that exposes setter-methods, but you want to hide these setters from a related class the object is given to as parameter. In no case the related class should be able to call one of the setters, you want to expose just a getter.
This is called information hiding, an important tool to reduce complexity.

Example

Following example is about having Person objects in memory that should be checked for uniqueness using their getId() method.

public class Person
{
    private final String id;
    private String name;
    
    public Person(String id) {
        this.id = id;
    }
    
    public String getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Mind that the id field is immutable to guarantee that it never changes in the lifetime of a Person object.

Here is the UniqueRegistration class that checks for uniqueness:

public class UniqueRegistration
{
    private final Set<String> registry = new HashSet<>();
    
    public void register(Person person)    {
        final String key = person.getId();
        
        if (key == null)
            throw new IllegalArgumentException("No id available: "+person);
        
        if (registry.contains(key))
            throw new IllegalArgumentException("Already registered: "+person);
        
        registry.add(key);
    }
}

Unfortunately this registry has full access to all methods of Person, it could call setName() or getName() or any other public method.

Example application:

        final Person john = new Person("John");
        final Person jill = new Person("Jill");
        
        final UniqueRegistration uniqueRegistration = new UniqueRegistration();
        uniqueRegistration.register(john);
        uniqueRegistration.register(jill);

Now it is about hiding person.setName() and getName() from uniqueRegistration.register(), just exposing the getId "function pointer".

Interfaces as Object Aspects

We create an abstraction of Person that focuses on its identity and nothing else:

public interface Identifiable
{
    String getId();
}

Let Person implement that interface:

public class Person implements Identifiable
{
    // ... implementation same as above
}

And rewrite UniqueRegistration to use the Identifiable interface instead of Person:

public class UniqueRegistration
{
    // ...
    
    public void register(Identifiable identifiable)    {
        final String key = identifiable.getId();
        
        // ... implementation same as above
    }
}

That's it! We separated the getId() method from the Person class, but we did not tear the two apart. Still the method will be connected to its object and read the correct id field at runtime. Nevertheless UniqueRegistration will not see anything else than getId.

Functions since Java 1.8

In Java 1.8, function pointers have been integrated into the language. Here is an implementation of UniqueRegistration that uses a built-in functional interface instead of Identifiable:

import java.util.function.Supplier;

public class UniqueRegistration
{
    // ...
    
    public void register(Supplier<String> identifiable)    {
        final String key = identifiable.get();
        
        // ... implementation same as above
    }
}

The Supplier interface is a simple functional interface contained in the Java runtime library since 1.8. Its one-and-only function is get(), without parameters:

package java.util.function;

@FunctionalInterface
public interface Supplier<T>
{
    T get();
}

The @FunctionalInterface annotation is there to ensure that just one method (the "function") is inside the interface. The compiler would report an error when you put this annotation onto an interface with more than one method, whereby default and static methods do not count.

The example application now accesses the function through a Java syntax extension added in 1.8 (context::function) like here:

        final Person john = new Person("John");
        final Person jill = new Person("Jill");
        
        final UniqueRegistration uniqueRegistration = new UniqueRegistration();
        uniqueRegistration.register(john::getId);
        uniqueRegistration.register(jill::getId);

Mind that Person doesn't need to implement Identifiable now any more. We can remove that interface.

Built-in Functional Interfaces

Basic built-in functional interfaces (with their single functions) are:

  • T Supplier.get()
    T = return-type

  • void Consumer.accept(T parameter)
    T = parameter-type

  • R Function.apply(T parameter)
    R = return-type, T = parameter-type

  • boolean Predicate.test(T parameter)
    always returns boolean, T = parameter-type

These four functional interfaces represent archetypical method signatures, and they are generically typed to be reusable. The Java runtime library contains many more variants of these four. In case none satifies your needs, simply write your own @FunctionalInterface.

Conclusion

Java interfaces are a powerful technique, well usable in separation concerns. Since Java 1.8, interfaces have become even better than abstract classes, because they also allow implementations.

Anyway we shouldn't hide any class behind an interface. Introducing interfaces just to be able to write class-oriented mock tests goes too far in my opinion. Such tests see any class as component, but in reality it isn't. There are not many classes that can exist without related classes, and only such a graph of classes should be regarded as component.




Dienstag, 9. März 2021

Java Interfaces as Component Boundaries

This article is about the original Java interface before version 1.8. It is about the role of interfaces as well-documented software-component boundaries. More precisely, it is about the compiler's behavior when it meets interfaces.

Since Java version 1.8, interfaces can contain implementations, both static and instance-bound ones, which was not possible before. Thus they are more like abstract classes now, but featuring multiple inheritance.

Abstract

In case you ever tried to compile a Java application from command line, you may have noticed that the Java compiler would not compile every class that your Main class depends on.

cd src/main/java
javac interfaces/compilation/Main.java

It would find any constructor call and compile the referenced class, recursively. But it would not search for classes that are behind interfaces, i.e. it would not compile classes implementing an interface that your Main class uses if there is no constructor call to these classes.

That means the compiler regards interfaces to be boundaries towards implementations that may be known at runtime only. Such would actually work (when accurately deployed), without losing strict type checks, because interfaces provide types.

Example

Try to compile following classes using the command line above (you can use any Java compiler, also above 1.8).

src/main/java
interfaces
compilation
Main.java
Drum.java
BassDrum.java
SnareDrum.java

Here is an interface:

package interfaces.compilation;

public interface Drum
{
    void sound();
}

Here is the application that uses that interface, but loads its implementations not through constructor calls but through reflective calls (not detectable by the compiler):

package interfaces.compilation;

public class Main
{
    public static void main(String[] args) throws Exception {
        final Drum bassDrum = (Drum) Class.forName("interfaces.compilation.BassDrum")
                .getDeclaredConstructor()
                .newInstance();
        bassDrum.sound();
        
        final Drum snareDrum = (Drum) Class.forName("interfaces.compilation.SnareDrum")
                .getDeclaredConstructor()
                .newInstance();
        snareDrum.sound();
    }
}

Here are two different implementations for the interface, in the very same package:

package interfaces.compilation;

public class BassDrum implements Drum
{
    public void sound()    {
        System.out.println("dumb");
    }
}

package interfaces.compilation;

public class SnareDrum implements Drum
{
    public void sound()    {
        System.out.println("jack");
    }
}

When all classes have been compiled and deployed, output of this application should be:

java -cp . interfaces.compilation.Main
dumb
jack


Having compiled using the command-line above, the resulting .class files should be in same directory as the .java sources. You will observe that just Main.java and Drum.java have been compiled:

src/main/java
interfaces
compilation
Main.class
Drum.class
Main.java
Drum.java
BassDrum.java
SnareDrum.java

That means the application would not work when launched:

java -cp . interfaces.compilation.Main
Exception in thread "main" java.lang.ClassNotFoundException: interfaces.compilation.BassDrum
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:315)
	at interfaces.compilation.Main.main(Main.java:6)

Now try to replace the dynamic class loading by concrete constructor calls:

package interfaces.compilation;

public class Main
{
    public static void main(String[] args) {
        final Drum bassDrum = new BassDrum();
        bassDrum.sound();
        
        final Drum snareDrum = new SnareDrum();
        snareDrum.sound();
    }
}

Here is what you get:

src/main/java
interfaces
compilation
Main.class
Drum.class
BassDrum.class
SnareDrum.class
Main.java
Drum.java
BassDrum.java
SnareDrum.java

Now the compiler detected any dependency inside the Main.java source and compiled it. Launching the application now yields:

java -cp . interfaces.compilation.Main
dumb
jack

Component Boundaries

So why would we need dynamically loaded "components"?

Since Java 9 we need to ask: Why would we need "service modules"?

Software needs to be highly configurable. That means we want it to behave accordingly to the environment where it runs. We could choose some configuration library and implement lots of if-conditions and feature-toggles in our source code. Or we could define responsibilities via interfaces and rely on the right interface-implementations being deployed and thus present in CLASSPATH at runtime.

That's the way most Java applications work nowadays. Dependency injection (DI) has been invented to do that. Good DI containers require interfaces to represent component boundaries. Component-oriented software also demands a new responsibility: deployment. These people build together an application fitting to the customer.

Service Loading Example

Here is an example of dynamic service loading as it is possible since Java 1.6.

Mind that this still works in Java above or equal 9, but services are now defined in module-info.java files instead of META-INF/services/<name.of.interface> text files containing the fully qualified class-names of implementations.
src/main/java
interfaces
compilation
Main.java
Drum.java
BassDrum.java
SnareDrum.java
src/main/resources
META-INF
interfaces.compilation.Drum

The service definition file in META-INF directory must be named exactly like the fully-qualified class-name of the service interface, in this case interfaces.compilation.Drum. It contains the fully-qualified class-names of all implementations, separated by newlines:

fri.interfaces.compilation.BassDrum
fri.interfaces.compilation.SnareDrum

Here is the application using service implementations through the JDK's ServiceLoader utility class:

package interfaces.compilation;

import java.util.Iterator;
import java.util.ServiceLoader;

public class Main
{
    public static void main(String[] args) {
        final ServiceLoader<Drum> serviceLoader = ServiceLoader.load(Drum.class);
        final Iterator<Drum> iterator = serviceLoader.iterator();
        while (iterator.hasNext())    {
            final Drum drum = iterator.next();
            drum.sound();
        }
    }
}

Output of this application:

java -cp . interfaces.compilation.Main
dumb
jack

Conclusion

Java interfaces are more than just contracts between software components. Besides the new Java 8 features, the original Java interfaces could also provide "function pointers", i.e. class-methods that can be passed around as parameters, working even without the new @FunctionalInterface annotation. This will be subject of my next Blog.