Factory methods & the language model

Remi Forax forax at univ-mlv.fr
Tue Sep 14 07:56:19 UTC 2021

I will take the scenic road to answer :)

There is currently an issue with the fact that we present primitive class as constructor in Java the language but is translated not to a constructor in the class file.
This introduce a false sense of compatibility, the code is identical if it's a classical class and a primitive class, but it's a trap because those are not binary compatible.

We start with the idea of "code like a class" but the current runtime model is different for a primitive class and a classical class, Q-type and factory at construction.
I think Java should reflect that difference in the code instead of trying to retrofit the constructor syntax to work with primitive class.

Apart from the "code like a class" mantra, it seems we do not want users to use a syntax corresponding to withfield directly, the withfield are implicitly generated by the compiler from the this.x = x.

In Amber, in a compact constructor, we have chosen another way, to avoid users to write this.x = x, they are generated automatically by the compiler.
I think we can use the same idea here.

In term of syntax, instead of having a constructor, we can have a method __new__ (syntax up to debate obviously)
and the compiler generates the withfield to initialize the primitive object before returning it

  public primitve class Point {
    private final int x;
    private final int y;

    public __new__(int x, int y) {
      if (x > 0 || y > 0) {
        throw ...
      // at the end the compiler generates the withfield to initialize the fields x and y with the value of the local variable x and y

The idea is that in the method __new__,
- is implicitly static (so "this" is not available)
- there should be a local variable defined inisde __new__ with the same name as each field
- you can have as many overloads you want, using __new__() instead of "this()" to call in between the overloads the same way record constructors work for you to delegate between constructors

For the reflection API, we can either go and introduce a new kind of member reflected by a class like j.l.r.NewFactoryMethod or with flag-like method on j.l.r.Method (isNewFactoryMethod()) saying that it's a special kind of static method. I prefer the later because most existing code will still work with that model.

To summarize, i think the syntax should reflect the fact that a primitive class is not initialized the same way a classical class is.

So if we want to be consistent with that idea, it means that for the JVM, those factory methods __new__ are only available to primitive classes.
So the VM should reject
 - a primitive class with a method <init>
 - a reference class with a method <new>

This is i believe your solution (1).


----- Original Message -----
> From: "daniel smith" <daniel.smith at oracle.com>
> To: "valhalla-spec-experts" <valhalla-spec-experts at openjdk.java.net>
> Sent: Jeudi 9 Septembre 2021 16:23:41
> Subject: Factory methods & the language model

> JEP 401 includes special JVM factory methods, spelled <new> (or, alternatively,
> <init> with a non-void return), which are needed as a standardized way to
> encode the Java language's primitive class constructors.
> We have a lot of flexibility in how much we restrict use of these methods. Too
> many restrictions seem arbitrary and incoherent from the JVM's point of view;
> but too few restrictions risk untested corner cases, unfortunate compatibility
> obligations, and difficulties mapping back to the Java language model.
> Expanding on that last one: for tools that operate with a Java language model,
> there are essentially three strategies for dealing with factory methods outside
> of the core primitive class construction use case:
> 1) Have the JVM reject them
> 2) Ignore them
> 3) Expand the model to include them
> Taking javac as an example, here's what that looks like:
> 1) If factory methods outside of primitive classes are illegal, javac can treat
> classes with such methods as malformed and report an error.
> 2) Or if javac sees a factory method in a non-primitive class, it can just leave
> it out when it maps the class file to a language-level class. (There's
> precedent for this in, e.g., the treatment of fields with the same name and
> different descriptors.)
> 3) Or we can allow javac to view factory methods in any class as constructors. A
> few complications:
>    - Constructors of non-final classes have both 'new Foo()' and 'super()' entry
>    points; factories only support the first. So we either need to validate that a
>    matching pair of <new> and <init> exist, or expand the language to model
>    factories independently from constructors.
>    - The language expects instance creation expressions to create fresh instances.
>    We need to either validate this behavior (does the factory look like
>    "new/dup/<init>"?) or relax the language semantics (perhaps this is in the grey
>    area of mixed binaries?)
>    - Factories can appear in abstract classes and interfaces. Again, are we willing
>    to change the language model to support these use cases? Perhaps to even allow
>    their declaration?
>    - If a factory method has a mismatched return type (declared in Foo, but returns
>    a Bar), are we willing to support a type system where the type of a factory
>    invocation is not the type of the class to which the factory belongs?
> There are probably limits to what we're willing to do with (3), which pushes at
> least some cases into the (1) or (2) buckets.
> So, my question: what should we expect from (3), now and in the foreseeable
> future? And for the cases that fall outside of it, should we fall back to (1),
> (2), or a mixture of both?

More information about the valhalla-spec-experts mailing list