Things I didn't know about Java: Generic Constructors
Tags: java, programming, generics
Published: 2022-07-25 11:13:39

Later this year I will work again on [Objectos Code](https://github.com/objectos/incubator/tree/ea5dfe015ad8457b60bb47ffdc7036d7e3aa82df/code). It is a library for generating Java source code. At time of writing, it is still unreleased and alpha-quality. Using a code _similar_ to the following _planned_ one:

```java
import static objectos.code.Java.*;

_var(id("foo"), _new(Foo.class, l(1), l("abc")));
```

Would generate the following Java statement:

```java
var foo = new Foo(1, "abc");
```

Whenever I work on it, I have to study the [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se18/html/index.html). In the previous example, I need to know what the actual formal definition of a `new` expression is. Its formal name is [Class Instance Creation Expression](https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.9), defined in section 15.9. A relevant production is the following:

```
UnqualifiedClassInstanceCreationExpression:
  new [TypeArguments] ClassOrInterfaceTypeToInstantiate ( [ArgumentList] ) [ClassBody]
```

As you can see, there is an optional list of type arguments immediately after the `new` keyword. I did not know about that. The following java class compiles:

```java
public class OptionalTypeArgumentsExample {
  public static void main(String[] args) {
    var t = new <String> Thread();

    t.start();
  }
}
```

The Eclipse JDT compiler issues a warning about an _unused_ type argument. On the other hand, `javac` (OpenJDK 18.0.1.1) compiles it without warnings:

```shell
$ javac -Xlint:all src/main/java/iter1/OptionalTypeArgumentsExample.java
[no warnings emitted]
```

So what is this? [JLS section 15.9](https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.9) states (emphasis mine):

> If the __constructor is generic__ (§8.8.4), the type arguments to the constructor may similarly either be inferred or passed explicitly.

Ah, so constructors can be generic. Well, I did not about that either. I think we should investigate that.

So, in this blog post I will give you a quick overview of generic constructors.

## Generic constructors are rarely used (in the JDK)

As I have never seen generic constructors before I wanted to know how "real-world" code uses them. So I wrote a [program](https://github.com/objectos/blog-examples/blob/master/p011-generic-constructors/src/main/java/shared/JdkSearch.java) that parses the Java files in the JDK source code. It uses the [JavaParser](https://github.com/javaparser/javaparser) open-source library. Since its README file mentions Java 15, I ran the program on tag [jdk-15+36](https://github.com/openjdk/jdk/tree/jdk-15+36/src) of the [JDK source code](https://github.com/openjdk/jdk/tree/jdk-15+36).

I found seven classes having generic constructors. They are all in the `java.management` module. Four classes are exported (and therefore have Javadocs):

- [javax.management.StandardEmitterMBean](https://docs.oracle.com/en/java/javase/18/docs/api/java.management/javax/management/StandardEmitterMBean.html)
- [javax.management.StandardMBean](https://docs.oracle.com/en/java/javase/18/docs/api/java.management/javax/management/StandardMBean.html)
- [javax.management.openmbean.OpenMBeanAttributeInfoSupport](https://docs.oracle.com/en/java/javase/18/docs/api/java.management/javax/management/openmbean/OpenMBeanAttributeInfoSupport.html)
- [javax.management.openmbean.OpenMBeanParameterInfoSupport](https://docs.oracle.com/en/java/javase/18/docs/api/java.management/javax/management/openmbean/OpenMBeanParameterInfoSupport.html)

While three of the classes are internal:

- [com.sun.jmx.mbeanserver.MXBeanSupport](https://github.com/openjdk/jdk/blob/jdk-15%2B36/src/java.management/share/classes/com/sun/jmx/mbeanserver/MXBeanSupport.java)
- [com.sun.jmx.mbeanserver.MBeanSupport](https://github.com/openjdk/jdk/blob/jdk-15%2B36/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java)
- [com.sun.jmx.mbeanserver.StandardMBeanSupport](https://github.com/openjdk/jdk/blob/jdk-15%2B36/src/java.management/share/classes/com/sun/jmx/mbeanserver/StandardMBeanSupport.java)

Therefore one can safely say generic constructors are rarely used. At least in the JDK source code.

## Still... it gives a glimpse on how to use them

Let's study the signature of one of those constructors.
For example, let's take this one from the [`OpenMBeanAttributeInfoSupport`](https://docs.oracle.com/en/java/javase/18/docs/api/java.management/javax/management/openmbean/OpenMBeanParameterInfoSupport.html#%3Cinit%3E(java.lang.String,java.lang.String,javax.management.openmbean.OpenType,T)) class. Its signature is:

```java
public <T> OpenMBeanParameterInfoSupport(
  String name, String description,
  OpenType<T> openType, T defaultValue)
  throws OpenDataException
```

The Javadocs for the type parameter `<T>` says:

> T - allows the compiler to check that the defaultValue, if non-null, has the correct Java type for the given openType.

So the type parameter in the constructor prevents mixing incompatible types. In other words, the following code to compiles:

```java
OpenType<Foo> openType = getOpenType();
Foo foo = getFoo();
var o = new OpenMBeanParameterInfoSupport(
  "A Name", "Some description", openType, foo);
```

As `OpenType<Foo>` is compatible with `Foo`. However, the following code fails to compile:

```java
OpenType<Foo> openType = getOpenType();
Bar bar = getBar();
var o = new OpenMBeanParameterInfoSupport(
  "A Name", "Some description", openType, bar);
// compilation error                      ^^^
```

As `OpenType<Foo>` is __not__ compatible with `Bar`.

Great, let's try to create an example using same idea. It should make things clearer.

## A simple example

Suppose we have a `Payload` class that represents arbitrary data to be sent over the wire. For example, It could be JSON data to be sent over HTTPS. To keep our example simple, we will model the data as a `String` value. Additionally, since our data is immutable, we will use a Java record:

```java
public record Payload(String data) {}
```

So, if we were to send a "Hello world!" message over the wire, we could invoke a hypothetical `send` method like so:

```java
send(new Payload("Hello world!"));
```

The actual JSON payload sent by our hypothetical service is not important for our example. But, for completeness sake, let's suppose the JSON data sent in our previous example was:

```
{
  "data": "Hello world!"
}
```

That's great. Next, let's add a little complexity to our data.

### Sending other data types

Suppose now we want to send data that is both structured and more complex than our previous "Hello world!" message. For example, we want to send a simplified log message represented by the following Java record:

```java
public record Log(long millis, Level level, String msg) {}
```

This data is structured in the sense that its JSON format is defined by the following converter:

```java
public class LogConverter {
  public String convert(Log log) {
    return """
    {
      "millis": %d,
      "level": "%s",
      "msg": "%s"
    }
    """.formatted(log.millis(), log.level(), log.msg());
  }
}
```

To send our log record we could just:

```java
var converter = new LogConverter();
var log = new Log(12345L, Level.INFO, "A message");
var data = converter.convert(log);
send(new Payload(data));
```

But we expect more data types each with its own structure. That is, each data type will bring its own converter. So, let's refactor our `Payload` record.

### Enter the generic constructor

Since each data type will have its own converter there is a chance to use a generic constructor like so:

```java
public record Payload(String data) {
  public <T> Payload(Function<T, String> converter, T item) {
    this(converter.apply(item));
  }
}
```

The converter is represented by a `Function` from a generic type `T` to a `String`. Our parameterized constructor ensures that the second argument's type is compatible with the converter.

So let's use our new constructor. The following test does just that:

```java
@Test
public void data() {
  var converter = new LogConverter();
  var log = new Log(12345L, Level.INFO, "A message");
  var p = new Payload(converter::convert, log);

  assertEquals(p.data(), """
  {
    "millis": 12345,
    "level": "INFO",
    "msg": "A message"
  }
  """);
}
```

Good, our test passes. Granted, there is very little difference to the previous example using the canonical constructor. But it does its job as an example of generic constructors.

## Invoking generic constructors

In our last example we invoked our generic constructor just like we do with a non-generic one. In other words, we did not provide explicit type arguments to our generic constructor. The actual type arguments were [inferred](https://docs.oracle.com/javase/specs/jls/se18/html/jls-18.html) by the compiler.

We can be explicit if we wanted. That is, we can provide a type argument list to the generic constructor.

### Providing type arguments with the `new` keyword

Taking again our last example, we can provide explicit type arguments. So the class instance creation becomes:

```java
var p = new <Log> Payload(converter::convert, log);
```

Notice the `<Log>` right after the `new` keyword. Providing explicit type arguments means that the following code does __not__ compile:

```java
var converter = new LogConverter();
var log = new Log(12345L, Level.INFO, "A message");
var p = new <Category> Payload(converter::convert, log);
// compilation error           ^^^                 ^^^
```

The compiler tries to match the actual arguments to a "virtual" constructor having the following signature:

```java
public Payload(Function<Category, String> converter, Category item);
```

As the types are not compatible, compilation fails.

### Providing type arguments with the `this` or `super` keyword

Apart from the class instance creation expression (i.e., `new` keyword), there are other ways to invoke constructors. In particular, constructors themselves can invoke other constructors:

- a constructor in the same class using `this`; and
- a constructor from the superclass using `super`.

But what happens if the invoked constructor is generic? Let's investigate.

Here's the production from [Section 8.8.7.1](https://docs.oracle.com/javase/specs/jls/se18/html/jls-8.html#jls-8.8.7.1) of the JLS:

```
ExplicitConstructorInvocation:
  [TypeArguments] this ( [ArgumentList] ) ;
  [TypeArguments] super ( [ArgumentList] ) ;
  ExpressionName . [TypeArguments] super ( [ArgumentList] ) ;
  Primary . [TypeArguments] super ( [ArgumentList] ) ;
```

As suspected, both `this` and `super` can be invoked with a type arguments list.

So let's try it with our `Payload` record. We can add a specialized constructor for a `Log` instance like so:

```java
public record Payload(String data) {
  public <T> Payload(Function<T, String> converter, T item) {
    this(converter.apply(item));
  }

  static final Function<Log, String> LOG_CONVERTER
      = LogConverter.INSTANCE::convert;

  public Payload(Log log) {
    <Log> this(LOG_CONVERTER, log);
  }
}
```

We added an invocation to the other constructor in the same class. It supplies a type argument to it:

```java
public Payload(Log log) {
  <Log> this(LOG_CONVERTER, log);
}
```

This means that the following code does __not__ compile:

```java
public Payload(Log log) {
  <LocalDate> this(LOG_CONVERTER, log);
  // error         ^^^            ^^^
}
```

As the compiler tries to match the actual arguments to a "virtual" constructor having the following signature:

```java
public Payload(Function<LocalDate, String> converter, LocalDate item);
```

As the types are not compatible, compilation fails.

### Caveat with `new` keyword and diamond form

[Section 15.9](https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.9) of the JLS has the following in bold:

> __It is a compile-time error if a class instance creation expression provides type arguments to a constructor but uses the diamond form for type arguments to the class.__

Let's investigate. Here's a small Java program:

```java
public class Caveat<T> {
  public <E> Caveat(T t, E e) {}

  public static void main(String[] args) {
    var t = LocalDate.now();
    var e = "abc";

    new <String> Caveat<>(t, e);
  }
}
```

The class `Caveat` is generic on `<T>`. It declares a single constructor which is generic on `<E>`. In the `main` method it tries to create a new instance of the `Caveat` class.

Let's compile it:

```
$ javac src/main/java/iter3/Caveat.java
src/main/java/iter3/Caveat.java:17: error: cannot infer type arguments for Caveat<T>
    new <String> Caveat<>(t, e);
                       ^
  reason: cannot use '<>' with explicit type parameters for constructor
  where T is a type-variable:
    T extends Object declared in class Caveat
1 error
```

Here's the explanation from the JLS:

> This rule is introduced because inference of a generic class's type arguments may influence the constraints on a generic constructor's type arguments.

To be honest, I was not able to understand it. In any case, to fix the compilation error we replace the diamond form:

```java
new <String> Caveat<LocalDate>(t, e);
```

With an explicit `<LocalDate>`. The code now compiles.

## Conclusion

In this blog post we discussed a few topics on generic constructors. A feature of the Java language I did not know until recently.

We have seen how it is rarely used in the JDK source code. Is it safe to extrapolate and say that it is rarely used in general? I personally believe it is. But don't take my word for it.

We then saw an example exercising a possible use-case.

Finally, we saw how to invoke generic constructors using:

- `new` keyword; and
- `this` keyword (which can be equally applied to the `super` keyword).

The source code of the examples in this post can be found in this [GitHub repository](https://github.com/objectos/blog-examples/tree/master/p011-generic-constructors).

_Originally published at the [Objectos Software Blog](https://www.objectos.com.br/blog/alternative-to-binary-files-in-your-java-tests.html) on July 18<sup>th</sup>, 2022._

Follow me on [twitter](https://twitter.com/marcioendo).