Need help with java interop and type inference


#1

I’m trying to create a new Log4J2 GelfLayout builder. In java I’d do GelfLayout.newBuilder() and call it a day. In kotlin though, I get greeted with the error:

image

Unfortunately the type is the Builder itself so it goes into a recursive requirement:

image

image

image

The method signature for .newBuilder() is:

@PluginBuilderFactory
    public static <B extends Builder<B>> B newBuilder() {
        return new Builder<B>().asBuilder();
    }

Any suggestions on how this can be solved?


#2

The GelfLayout.Builder class is declared as Builder<B extends Builder<B>>. I think it is impossible to name a type that satisfy that bound on the type parameter without somehow circumventing the type system. In Java this is possible with raw types (raw types in Java are the parameterless legacy versions of generic classes. They mainly exist for compatibility with legacy code). In Java, the type parameter of GelfLayout.newBuilder() would usually be inferred as the raw Builder type, which is the only possible option. In Kotlin, raw types do not exist, so there really is no way to use that .

I see the following options:

  • Use the GelfLayout constructor rather than the builder. Disadvantage: It is deprecated.
  • Write a wrapper in Java, which is more Kotlin friendly than what is available. The simplest option would be to write a function that just delegates to newBuilder() but returns the Builder as a raw type directly. You can call that from Kotlin, which will expose the return type as GelfLayout.Builder<*>.
public static GelfLayout.Builder newRawBuilder() {
    return GelfLayout.newBuilder();
}

#3

Thanks for the response. I figured as much.

As a purely academic exercise, I wanted to understand your first assertion though:

The GelfLayout.Builder class is declared as Builder<B extends Builder< B>>. I think it is impossible to name a type that satisfy that bound on the type parameter without somehow circumventing the type system.

In general, won’t a Child extends Builder<Child> satisfy the type system when Builder is defined as Builder<B extends Builder<B>?


#4

I think you might be right. That opens up another option

// This class only exists to be passed as a type argument. It will never be instantiated.
private class Child: GelfLayout.Builder<Child>()

// Return type MUST be explicit. Inferring it to Child will give you a ClassCastException
fun newGelfLayoutBuilder() : GelfLayout.Builder<*> = GelfLayout.newBuilder<Child>()

You should now be able to use newGelfLayoutBuilder() whenever you would normally call GelfLayout.newBuilder().


#5

I wanted to try it out before responding.

Sweet! That’s actually a good idea and it seems to work.