Blog

Using Generics for Constructors

By  
Sven Ruppert
·
On Mar 27, 2018 7:00:00 AM
·

Intro

This is the first article in a series on Core Java. The goal will be to look at functional and reactive aspects. Everything is based on the JDK itself, so that the examples can be tried out directly. All articles of this series are marked with the tag Core Java. At the end of each article you will find the link to the git repository with the examples.

Typed Constructors

In Java, it is planned to do abstractions by means of interfaces. This is taught in the very first lectures on the subject of Java. But sometimes it can lead to unclean source code. In my projects, I always check that internal implementation classes implement two or more interfaces of an API. This is not so bad in itself, only at the other side it leads to the following constructs.

Let us assume for the following discussion that we have two interfaces (ServiceA and ServiceB). (see listing given below)

public interface Service_A {
  String doWork_A();
}

public interface Service_B {
  String doWork_B();
}    

In the implementation of the framework, there is now a class ServiceImplInternal, which implements both these interfaces.

public class ServiceImplInternal implements Service_A, Service_B {
  @Override
  public String doWork_A() {
    return "A";
  }

  @Override
  public String doWork_B() {
    return "B";
  }
}    

This is not so significant within the framework. Internally, only this implementation is used at the given point. Only outside one should not cast explicitly on this implementation, since it is not ensured that this implementation will continue to exist like this in the longer run.

Therefore, there is no interface, which inherits from both the interfaces and hence merges them. There should now be a method, which generates a data holder based on the method return values of methods of both the interfaces.

public static class DataHolder_A {
    private String a; //doWork_A()
    private String b; //doWork_B()
    
    public DataHolder_A(final String a, final String b) {
      this.a = a;
      this.b = b;
    }
  } 

This then leads to the following transformations, such as

final List<Service_A> service_A_list = getServices(); //reduced to one interface

final List<DataHolder_A> list = service_A_list.stream()
        .map(a -> {
          final Service_B b = (Service_B) a;
          return new DataHolder_A(a.doWork_A(), b.doWork_B());
        })
        .collect(Collectors.toList());

This is not particularly elegant, but what can one do now? The first step is to transform the list, which one gets from the framework. This can then be implemented as a map() stage during the stream processing.

    final List<DataHolder_A> collect = service_A_list
        .stream()
        .map(e -> (Service_A & Service_B) e)
        .map(e -> new DataHolder_A(e.doWork_A(), e.doWork_B()))
        .collect(Collectors.toList());

One can make use of the fact here that one can cast on a random number of interfaces in one step. e -> (Service_A & Service_B) e But why carry out a cast? Let us have a look at the DataHolder_A. The aim here is to hold the results of both the methods. It is known about the methods that one can define the type T in the method itself. If we apply this to our transformation, we get a method with the following signature.

  private <T extends Service_A & Service_B> List<T> transform(List<Service_A> services) {
    return services.stream()
        .map(e -> (Service_A & Service_B) e)
        .map(value -> (T) value)
        .collect(Collectors.toList());
}

Important at this point is <T extends Service_A & Service_B> List<T>.

Now this is only a small step. If one now, in this case, understands a constructor like a method, one can also defined a T here. The advantage is that this need not be done at the class level.

Let us change our data holder as follows.

public class DataHolder_B {

    private String a;
    private String b;

    public <T extends Service_A & Service_B> DataHolder_B(T input) {
      this.a = input.doWork_A();
      this.b = input.doWork_B();
    }
  }

One can now formulate the transformation quite easily.

    final List<Service_A> service_A_list = getServices(); //reduced to one interface  
    final List<DataHolder_B> collect = service_A_list
        .stream()
        .map(e -> (Service_A & Service_B) e)
        .map(DataHolder_B::new)
        .collect(Collectors.toList());

If one formulates this now a little less formally, one gets the following example. This also ensures that we never use an invalid combination or that implementation classes become visible outside of the framework. In case the implementation within the framework changes in the composition of the interfaces, then this becomes visible directly during compilation.

public class Main {
  interface Service_A { String doWork_A(); }
  interface Service_B { String doWork_B(); }
  interface Service_C { String doWork_C(); }
  interface Service_D { String doWork_D(); }
  
  public static class Impl_A implements Service_A , Service_B{
    public String doWork_A() { return "A"; }
    public String doWork_B() { return "B"; }
  }
  public static class Impl_B implements Service_C , Service_D{
    public String doWork_C() { return "C";}
    public String doWork_D() { return "D";}
  }

  public static class DataHolder{
    String a;
    String b;

    public DataHolder(final String a, final String b) {
      this.a = a;
      this.b = b;
    }

    public <T extends Service_A & Service_B> DataHolder(T value) {
      this.a = value.doWork_A();
      this.b = value.doWork_B();
    }
    public <T extends Service_C & Service_D> DataHolder(T value) {
      this.a = value.doWork_C();
      this.b = value.doWork_D();
    }
  }

  public static void main(String[] args) {
    new DataHolder("A","B");
    new DataHolder(new Impl_A());
    new DataHolder(new Impl_B());
  } 
}

But sometimes one comes across cases, in which there are several implementations for an interface. One then sometimes comes in the following situation.

public interface Service_A {
    String doWork_A();
  }

  public static class Service_A_Impl_A implements Service_A {
    @Override
    public String doWork_A() {
      return null;
    }
  }

  public static class Service_A_Impl_B implements Service_A {
    @Override
    public String doWork_A() {
      return null;
    }
  }

  public interface Service_B {
    String doWork_B();
  }

  public static class Service_B_Impl_A implements Service_B {
    @Override
    public String doWork_B() {
      return null;
    }
  }

  public static class Service_B_Impl_B implements Service_B {
    @Override
    public String doWork_B() {
      return null;
    }
  } 

Now it unfortunately happens that the combinations of the results are correct functionally only when the A implementation or B implementation respectively has been taken from the two. (yes there is really something like this ;-) ) That is, there are valid and invalid combinations of the implementations.

  //the only valid combinations
  // Service_A_Impl_A && Service_B_Impl_A
  // Service_A_Impl_B && Service_B_Impl_B

  // not allowed
  // Service_A_Impl_A && Service_B_Impl_B
  // Service_A_Impl_B && Service_B_Impl_A

This, in turn, leads to constructions as listed below.

  //not nice
  public static class DataHolder_AB {
    private String a;
    private String b;

    //not secure
    public DataHolder_AB(final Service_A service_a, final Service_B service_b) {
      a = service_a.doWork_A();
      b = service_b.doWork_B();
    }

    //not secure
    public DataHolder_AB(String a, String b) {
      this.a = a;
      this.b = b;
    }

    //not nice
    public DataHolder_AB(final Service_A_Impl_A service_a, final Service_B_Impl_A service_b) {
      a = service_a.doWork_A();
      b = service_b.doWork_B();
    }

    //not nice
    public DataHolder_AB(final Service_A_Impl_B service_a, final Service_B_Impl_B service_b) {
      a = service_a.doWork_A();
      b = service_b.doWork_B();
    }
  }

If one now comes across this, it is not allowed to typify the class DataHolder itself. That is, no DataHolder<…> is allowed. One can then possibly here take help from typed constructors.

  //no generics on class level
  public static class DataHolder {

    //not secure
    //public <A extends Service_A, B extends Service_B> DataHolder(A serviceA, B serviceB) {}

    //ok
    public <A extends Service_A_Impl_A, B extends Service_B_Impl_A> DataHolder(A serviceA, B serviceB) {}
    public <A extends Service_A_Impl_B, B extends Service_B_Impl_B> DataHolder(A serviceA, B serviceB) {}

  }

In the usage, therefore, combinations are allowed only when they are specified here explicitly as constructor. The notation here is essentially shorter and clearer if one has to deal with several combinations.

Summary

If one has to fight with old and strongly developed software systems, one comes across some very strange things. Unfortunately, one cannot always improve the entire architecture, even if one considers this as very useful and as a good investment from the technical view point.

In such cases, one can then see to it with small improvements that some things come in the static semantic and the compiler helps one (functionally) to find errors.

The sources are given at https://github.com/Java-Publications/jaxenter.de-0028-CoreJava-TypedConstructor.

Happy coding! in case of queries or suggestions -> Twitter @SvenRuppert or mail: sven.ruppert@vaadin.com

Sven Ruppert
Sven Ruppert has been coding Java since 1996 and is working as Developer Advocate at Vaadin. He is regularly speaking at Conferences like JavaOne/Jfokus/Devoxx/JavaZone/JavaLand and many more and contributes to IT periodicals, as well as tech portals.
Other posts by Sven Ruppert