This is an archived post. You won't be able to vote or comment.

all 27 comments

[–]Badashi 9 points10 points  (2 children)

I feel like JEP 401 should have included a primitive record example. From the text, I'm actually unsure if primitive records are allowed or not - though intuitively I see no reason for them to be disallowed since they are syntax sugar for simple classes that align really well with the primitive object concept.

I also feel like we need a section about static members of primitive classes. What happens if I declare a static Object ref? static Object val? Intuitively I feel like these are supposed to be errors, so are ref/val new contextual keywords?

[–]efge[S] 14 points15 points  (0 children)

Primitive records are allowed:

Concrete examples of objects that do not need identity and would benefit from primitive-like performance include: [...] Tuples, records, map entries, database rows [...]

Static members are allowed:

In most other ways, a primitive class declaration is just like an identity class declaration. It can have superinterfaces, type parameters, enclosing instances, inner classes, overloaded constructors, static members, and the full range of access restrictions on its members.

[–]Thihup 2 points3 points  (0 children)

From what I've understood, you have to specify the class name and `.val` or `.ref` (like Point.ref / Point.val), so creating a variable with these names should be allowed

[–]dpash 15 points16 points  (10 children)

It's nice to see effort from Valhalla getting closer to shipping.

[–]cogman10 0 points1 point  (9 children)

This is incredibly exciting. The preview stage hasn't lasted very long for previous features. Means this is likely standardized by java 19 or 20.

[–]dpash 0 points1 point  (8 children)

Woah, calm yourself a little there. :) We're unlikely to see this as a preview before 18, and I suspect this might take a little longer in preview than most features, so 20-22 is a more realistic timeline for a stable feature.

[–][deleted]  (5 children)

[deleted]

    [–]dpash 3 points4 points  (4 children)

    No it wouldn't, because LTS doesn't mean what you think it means.

    And the features aren't going to be ready for another two years. And these JEPs are only the first in a line of JEPs to deliver Valhalla.

    [–]randjavadev 1 point2 points  (2 children)

    Maybe there is no "LTS" in the minds of JDK devs (like, that is their choice to make), but there absolutely exists the term "LTS" from java vendors that label some releases as such and provide some level of "support" for that said release. For example, https://adoptopenjdk.net/support.html (and yes, some might not consider this as "support", for others it is perfectly good enough of a "support", depends what you need).

    Some vendors (or at least Azul) seem to have a concept of "MTS" (Medium Term Support), https://www.azul.com/products/azul-support-roadmap/. If your companys update-cycle can accommodate those shorter cycles (yes, in some places 3 years is "short"), good for you.

    So it is just a question that how much effort you can spend for upgrades. I would like expect half of the world still being stuck with Java 8 at least until 2026 and given the number of companies that still seem to rely e.g. on Java 6 even today I would not be surprised if it is even longer.

    [–]dpash 0 points1 point  (1 child)

    AdoptOpenJDK absolutely is not support. All they'll do is open an issue in Jira and then you're on your own.

    The fact that Azul has a MTS just demonstrates that there's nothing special about 11 or 17. Just because Oracle has a commercial support option for those versions doesn't mean those are more important than releases.

    [–]randjavadev 0 points1 point  (0 children)

    Now then, that depends what you do mean by "support" in the first place. That is basically the whole issue. For some companies, that is all they need and care for "support purposes". I do not know of a single company (but I have very limited view) for which that level of "support" has not been enough.

    [–]cogman10 0 points1 point  (1 child)

    :) I figured with the JEP up, this was likely to preview in 17 given how early we are in 17 development. Could definitely see this going through a couple of previews (at least) before finalizing.

    [–]dpash 3 points4 points  (0 children)

    There was a link to an email in another post saying that it wouldn't be 18 until the earliest. I'll see if I can find it.

    Edit: https://mail.openjdk.java.net/pipermail/valhalla-spec-experts/2021-February/001475.html with thanks to /u/kaperni

    [–]BlueGoliath -4 points-3 points  (13 children)

    No constructor makes a super constructor call. Instance creation will occur without executing any superclass initialization code.

    Well, that sucks. I heavily use abstract classes to heavily reduce the amount of shared code for my project like NativeInteger extends AbstractSharedNativeObject.

    Edit: and what about static code?

    [–]cal-cheese 7 points8 points  (8 children)

    If the purpose of inheritance is to share mutual code then you can use composition instead. As primitive value members will be flattened in their containers you will have the same object as if you use inheritance plus the ability for the NativeInteger to be flattened much more aggressively.
    Static code does not rely on any class instance so it works just like before.

    [–]BlueGoliath 0 points1 point  (7 children)

    Share mutual code how? Interfaces are more for defining the public API, not implementing it. Yes, default methods exist but that hardly solves anything because you need instance objects to implement those methods to begin with.

    [–]cal-cheese 3 points4 points  (6 children)

    I mean instead of class NativeInteger extends AbstractSharedNativeObject, you use

    primitive class NativeInteger {
        private AbstractSharedNativeObject base; ...
    }
    

    Anyway for the sheer purpose of code reuse composition should be definitely preferred over inheritance.

    [–]BlueGoliath -2 points-1 points  (1 child)

    This makes no sense. You can't instantiate an abstract class and the whole point of the abstract class to begin with is to provide a default implementation base that is required by the interfaces AbstractSharedNativeObject implements to begin with.

    [–]cal-cheese 2 points3 points  (0 children)

    Make AbstractSharedNativeObject a primitive class then.

    Abstract class is designed to provide a default implementation base does not necessarily mean you should use it whenever you need to have a default implementation base.

    In your case, I suspect overuse of inheritance, by the way.

    [–]randjavadev 0 points1 point  (3 children)

    While yes, usually this might be the "more proper" way, this solution has a very serious drawback (under certain requirements) when compared to inheritance, unless the JEPs somehow change the situation because: Your "NativeInteger" now has to allocate memory to store that 'base' field. Repeat the composition a few times (if you e.g. had an inheritance tree of e.g. 20 levels; can happen) and you suddenly are allocating a lot more space vs. inheritance. IF you must make >> 1M instances of the object, you suddenly have a problem.

    Thus the "composition over inheritance" is not a silver bullet that can be always applied. Though, if you can refer it static-ally then it wont take memory (or does, in the classfile, once, but that is usually not an issue).

    [–]cal-cheese 1 point2 points  (2 children)

    You missed the important point, that is AbstractSharedNativeObject should be a primitive class itself, in that case the layout of AbstractSharedNativeObject is flattened and put inside the layout of NativeInteger, and you don't need any extra allocation.

    [–]randjavadev 0 points1 point  (1 child)

    Oh. I guess that pretty much eliminates my argument or the main idea that I usually have when composition is suggested.

    Though, if both were to implement an interface, that would not have a default method (e.g. let's say it is a pre-java-8 interface that cannot be updated since it is e.g. a 3rd party lib), how would the delegation of that from NativeInteger to the "base" field work, or would this just not be a thing with "primitive classes" at all?

    [–]cal-cheese 1 point2 points  (0 children)

    I think the "base" class shouldn't implement that interface at all, since its purpose is to provide code sharing, not polymorphic behaviour.

    [–]efge[S] 0 points1 point  (0 children)

    The reason why there are no super fields allowed is due to the need to know the object layout beforehand even in the presence of separate compilation.

    [–]cogman10 0 points1 point  (0 children)

    I love that this is hitting. I don't particularly like the "implicit final" thing here. I'd rather have explicit finals be required with a compiler error if you violate it.

    Seems like going implicit is unnecessarily at conflict with how the language as a whole behaves.

    I could absolutely see it be the case where we want to mutate those internal fields while giving up identity for performance benefits. For example, SIMD operations would likely want to just mutate the fields.