
Records Come to Java - agluszak
https://blogs.oracle.com/javamagazine/records-come-to-java
======
djsumdog
Java finally gets what's been available in Scala (case classes), C# (structs
or maybe properties), Kotlin (data classes) and others for a very long time ..
so long we already have tools like Immutables and Lombok to get past this
really dumb limitation in Java.

I was really surprised to learn that the Java compiler/VM, when it sees the
patterns "somevar" and functions "getSomeVar" and "setSomeVar" doing purely
getter/setter stuff, removes the actual function calls and silently changes
them in the background to field lookups for speed.

If you attach a debugger or memory profiler, is changes them back to functions
and slows them down. I'm not sure if this is still true; saw an example of
this at a meetup back in the Java 8 days.

~~~
pron
> Java finally gets what's been available in ... and others for a very long
> time

You seem to think that the goal is to get as many features as possible, as
quickly as possible. Maybe some languages have that goal, but Java's design
philosophy -- from its inception [1] -- has been to be a conservative language
that only adopts features that have become familiar to programmers _and_ stood
the test of time in providing a good cost/benefit. Java hasn't always done
this, but that's the aspiration. Worked out pretty well, I think.

Given Java's design philosophy, the far more interesting question is which
features of those more adventurous languages Java does _not_ intend to adopt.

> Java compiler/VM, when it sees the patterns "somevar" and functions
> "getSomeVar" and "setSomeVar" doing purely getter/setter stuff, removes the
> actual function calls and silently changes them in the background to field
> lookups for speed. If you attach a debugger or memory profiler, is changes
> them back to functions and slows them down.

It's true, and the optimizing JIT compiler does much more than just that. It
performs deep speculative optimizations, like inlining virtual calls and
removing untaken branches, reverting them (using a process called
deoptimization) either for debugging or when it detects its assumptions are
false. What you're referring to is just an instance of inlining.

[1]: [https://www.win.tue.nl/~evink/education/avp/pdf/feel-of-
java...](https://www.win.tue.nl/~evink/education/avp/pdf/feel-of-java.pdf)

~~~
Cladode

       Worked out pretty well, I think.
    

Au contraire mon ami!

ML was better than most of its successors, including Java, which held
programming back. Milner's ML avoided some of Java's glaring mistakes (e.g.
exception specifications, lack of generics, non-uniform treatment of types,
lack of higher-order functions, lack of pattern matching) from the start.
Xavier Leroy showed how efficiently to compile ML in the 1990s.

The main thing ML lacked (w.r.t sequential computing), was a good integration
with OO and HKTs. That was solved by Scala.

~~~
pron
I meant that it's worked out pretty well for Java. ML -- one of my favorite
languages, alongside Java and a couple of others -- has also managed to avoid
being used by anyone, really. But look at Java now: it has generics and
higher-order functions, and it's getting a uniform treatment of types (Project
Valhalla), and pattern matching [1]. And so while ML introduced those features
in a research context, Java is the language that has made and will make the
world actually use them. Being ahead of your time is often just as much of a
mistake as being behind (as far as product design goes).

As to the question of "holding programming back," it is yet to be seen how
much of an impact these features have in practice. Same goes for what ML
"lacked." I personally like some features more and some less, as do you, but I
don't think anyone knows which programming language features really push
programming forward.

[1]: [https://cr.openjdk.java.net/~briangoetz/amber/pattern-
match....](https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html)

~~~
AnimalMuppet
"Holding programming back" means that it would have advanced in the absence of
Java. What language would have advanced it? Cladote seems to assume that ML
would have, which would have been actually used if Java hadn't taken over the
world. That seems unsupported by evidence. Overly optimistic, as well.

And if Cladote's answer, ML, would _not_ have taken over, then did Java hold
back anything? No. Was it better than, say, C++? For many uses, yes.

"We would have ruled the world if it wasn't for you meddling kids!" is wishful
thinking. If ML was so good, _it should have won anyway_. Instead, "has
managed to avoid being used by anyone" is a pretty fair assessment.

~~~
Cladode
If we collapse the two concepts of

\- best possible X

\- most popular X

are we loosing possibly interesting perspectives on X?

~~~
pron
I don't think AnimalMuppet is confusing most popular with best. But as you
don't have the data required to conclude which language is better by any
meaningful metric, anyway, then your claim is just wishful thinking. If ML had
won and Java lost, one could have just as easily said that ML was holding
programming back from some imagined benefit Java would have brought. I think
that it is you who are confusing "an interesting perspective" with a value
judgment. Yes, ML had pattern matching before Java, while Java had pervasive
dynamic linking and dynamic code loading. Those are simple facts and perhaps
"an interesting perspective," but how do you decide which is better?

~~~
Cladode
Nobody has credible data enabling reproducible comparison of programming
languages. The big lacuna of PL research ...

If popularity mattered ... PHP, JavaScript etc.

I find the evolution of all successful programming languages eventually to
evolve into an ML-like core fascinating.

~~~
pron
> Nobody has credible data enabling reproducible comparison of programming
> languages.

Right, so as "better" and "best" are unknown, we should stop using those
terms. I like Java and I like ML, but I don't know that one of them is better
than the other, and if so which.

> I find the evolution of all successful programming languages eventually to
> evolve into an ML-like core fascinating.

Some of the successful _typed_ languages, sure: Java, C++, C#, but the three
heavily influence one another, and at least Java's and C#'s designers were
aware of ML. So I don't think that, to the extent this is true, it's some
"natural" draw, but rather a direct or indirect influence. On the other hand,
you have Go, which is also quite successful (admittedly in a lower tier of
success), and it doesn't seem to be converging on something ML-like, though
(although it is influenced by another Milner idea), and neither is C.

------
mark_l_watson
That looks like a good addition because anything that makes code more concise
and easier to read is a win.

Off topic, but I have been feeling nostalgic for Java, even though I now
almost exclusively use Lisp languages and I am happy with that. In early Java
years, Sun‘s main Java web site had a link to my site and I wrote a number of
Java books. I was the number one search result for “Java consultant” for about
a decade; Java was good for my career.

~~~
cle
Forcing usage of constructors is hardly more readable IMO.

Which bug is easier to spot?

    
    
        cat = new Cat(1, 2, 4);
        cat = Cat.builder().eyes(1).legs(2).heads(4).build();
    

Additionally, from the examples in that post, records aren't valid JavaBeans
either, so they can't be used with the myriad of existing tools/libraries that
expect beans.

From my first impression, it looks like records don't really address many of
the reasons we use Lombok and its ilk, so many of us will just ignore this
feature. Or do like I've done in the past couple of years, and just ignore
Java altogether.

~~~
Rebelgecko
If you're using a decent IDE, that bug is just as easy to spot when using a
constructor. My IDE adds the var names in front of the values, so when I
create a new Cat(1,2,4) it actually looks like this:

    
    
            new Cat(eyes:1, heads:2, legs:4);

~~~
cle
If using an IDE is necessary to make the code readable, then Java should ship
with an IDE. Or just add it to the language.

I find myself often reading code outside of an IDE, such as in GitHub or other
hosted repos. Why should it be less readable in those cases? Because it's
marginally easier to write?

I also write a lot of Go code, and appreciate how "go vet" disallows unkeyed
struct literals, forcing you to write the field names when creating structs.
It is not an IDE feature, and I can freely browse GitHub or grep around in the
command line and the code is just as readable as in an IDE. It also means Go
works in a lot of editors and IDEs more easily, because those editors/IDEs
don't need to implement a bunch of extra features to make the code actually
readable.

~~~
seanmcdirmid
Verbosity is a huge problem in a language. Verbosity that makes unfamiliar
code easier for you to read can actually hurt the readability to a more
familiar reader trying to figure out where the bug is. I don’t need that
thisIsTheBloodyFooArgument: all over the place if I already know it is the
bloody foo argument. Eventually, the code becomes so repetitively noisy that I
have to rewrite it stripped down into a text file just to get a gist of the
real things I need to understand (like when comments get in the way and don’t
help.

But really, this is a problem we don’t need to have...tooling could make it so
we have the verbose repetitive version that make unfamiliar readers happy and
the more concise to the point version that makes familiar readers happy. But
for some reason, our profession is infatuated with text files that make this
best of both worlds impossible.

~~~
cle
> But for some reason, our profession is infatuated with text files that make
> this best of both worlds impossible.

Because people already have existing tools, lots of them. Text files are an
easy way to support all of them. What kind of tooling do you propose that
would immediately integrate with IntelliJ, Eclipse, NetBeans, emacs, vim,
VSCode, Atom, Sublime, GitHub browsing, grep, etc.?

~~~
seanmcdirmid
Yes, but then you have people like the comment I was replying to who say that
those tools must be inflexibly fixed when the language ships...and so those
languages that expect less from tooling (like Go) are somehow better than
those (like Java) that expect more. That the text should speak for itself well
enough without tooling.

------
bozoUser
This is akin to case classes of Scala such a welcoming change to the Java
world.

Apart from being a functional language one of main draws to Scala is the ease
with which one can get rid of the boilerplate code. So kudos to the Java team!

~~~
The_rationalist
But case classes are also algebraic data types so Java is not yet as great.

------
frankosaurus
I'm curious if this will make AutoValue obsolete:
[https://github.com/google/auto/blob/master/value/userguide/i...](https://github.com/google/auto/blob/master/value/userguide/index.md)

~~~
apta
It should.

------
thrower123
I am continually surprised that Java hasn't adopted C#'s property syntax.
Having to write backing fields and your own getters and setters takes me back.

~~~
peterashford
As someone who codes C# every day, I hate properties with a passion. Its my
least favourite feature of c#

~~~
thrower123
Why?

------
jnwatson
Python recently added the same thing in 3.7. They call them data classes.

[https://docs.python.org/3/library/dataclasses.html](https://docs.python.org/3/library/dataclasses.html)

~~~
dfox
Python has had namedtuples for a long time, which is similar idea implemented
less cleanly (in fact the implementation is horrible hack because of some rare
corner cases) and without dependence on Py3 annotation syntax

~~~
dehrmann
> less cleanly

Python has a number of these warts. Same with classes and type annotations.

------
dehrmann
I'm pretty excited for this. I've seen Java developers jump through hoops so
they wouldn't have to implement a new class when a Python developer would use
a NamedTuple and not think twice.

And yes, there are codegen options (Autovalue, Lombok, etc.), but the build
process is a little clunkier, and IDE support isn't great.

------
specialist
I guess I was hoping the same end result would have been reached by different
means.

I would like to see:

new non canonical java.lang.BaseObject, which implements nothing.

new interfaces for .toString(), .equals( that ), and .hashCode().

Canonical java.lang.Object implement these new interfaces.

Any code wanting struct, data, record style objects would subclass BaseObject.

The JVM would auto generate missing .toString(), .equals( that ), and
.hashCode() as needed. Like when you add a data class to a HashMap.

New syntax for properties, to eliminate need for setter/getter boilerplate.

New syntactic sugar for the static initializer constructor trick, to eliminate
constructor boilerplate. Basically make new MyDataObject() {{ a = 1; b = "abc"
}} look prettier.

\--

Best as I can tell, only the last two would require language changes.

------
davewritescode
The best thing about this is VM support for records. This means that all the
other JVM languages that implement similar features can potentially leverage
the underlying support for records to make better implementations themselves.

I for example, don’t immediately see any reason kotlin can’t use this to make
a more efficient implementation of data classes.

~~~
kllrnohj
What VM support? Records are a Java compiler feature. It just auto-generates
all the boilerplate at compile time, there doesn't seem to be any bytecode or
VM support for this feature?

It seems functionally identical at the VM/bytecode level to what Kotlin's data
classes already do.

~~~
davewritescode
My mistake, I thought this was more akin to value types proposed in this JEP

[https://openjdk.java.net/jeps/169](https://openjdk.java.net/jeps/169)

This is disappointing.

~~~
pron
Records are meant to play nicely with inline types, i.e. when inline types
land, you'll be able to declare inline records.

------
thomascgalvin
I feel like Java is making great strides lately, but that we're also getting
slightly lesser versions of capabilities available in other languages.

Records, for example, are directly inspired by Kotlin's data classes, and
function almost identically... except that Kotlin allows you to have mutable
or immutable fields in a data class, and automatically provides a `copy`
method (similar to Lombok's `wither`) that allows you to use a builder
pattern.

This difference can be very important. For example, I don't see how a Record
class could be used with most JPA-style ORMs, which immediately rules out
Records for many of the most common use cases.

~~~
pron
Records are actually inspired by product types, and will be central to the
pattern-matching feature that has started making its way into the language
(and, combined with the upcoming sealed types, would form Java's ADTs).
Allowing mutation doesn't make much sense, then.

~~~
mahkoh
>Allowing mutation doesn't make much sense, then.

Why?

~~~
pron
Because that's not what records are about. See
[https://openjdk.java.net/jeps/359](https://openjdk.java.net/jeps/359),
[https://cr.openjdk.java.net/~briangoetz/amber/datum.html](https://cr.openjdk.java.net/~briangoetz/amber/datum.html)
and [https://cr.openjdk.java.net/~briangoetz/amber/pattern-
match....](https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html)

For one, mutation doesn't play well with pattern matching.

~~~
mahkoh
Explain why mutation doesn't play well with pattern matching.

~~~
pron
A pattern requires matching against a piece of data possibly composed of
several related components that are assumed to be coherent with one another,
not some mutable object. How would you match Point(3, y) against a mutable
object, if by the time you grab the y, the x component may no longer be 3
(recall that Java is a multithreaded language, and x could be volatile)? By
analogy, think of how matching a regex pattern would work against a mutable
array of characters as opposed to an immutable string.

~~~
mahkoh
Different components being in sync is a property of objects in general. E.g.
Java's ConcurrentHashMap is mutable but its fields are in sync.

For pattern matching to see an in-sync view of the object, the object must
provide such a view. If the object allows concurrent modification (most don't)
then it must provide a custom view. Otherwise a generated default view should
be sufficient.

If you look at [1] you can see that Goetz does propose a function that
provides such a view for classes and not just records (c-f extractor.)

[1] [https://cr.openjdk.java.net/~briangoetz/amber/pattern-
semant...](https://cr.openjdk.java.net/~briangoetz/amber/pattern-
semantics.html)

~~~
pron
But that's exactly what I meant by mutation does not play well with pattern
matching: you need to work, sometimes work hard (in fact you need to somehow
hide the mutability) to make it work. I'm not sure what you meant exactly by
the _fields_ of ConcurrentHashMap being "in sync" (the contents of the hash
map certainly aren't and it does not provide a snapshot view), but their
coherence requires hard work. Unless you don't know how the particular
synchronization mechanism, if any, works, you cannot provide a view suitable
for pattern matching, so it cannot, in general, be automatically generated for
mutable objects, at least not in a way that works as well as you'd want it to.
For example, a naive extractor to a Pair class with two volatile field could
return two values that have never been the values of object's fields at any
point in time (i.e. you'd pattern-match `case Pair(x, y)` and get, say, x=2
and y=3 despite the pair never having been (2,3)). That "works" in the sense
that you have some result, but it's probably not what you want.

~~~
mahkoh
ConcurrentHashMap contains multiple volatile fields. These are kept in sync
(i.e. their invariants are preserved) by the ConcurrentHashMap implementation.
Concurrent programming being hard is a property of concurrent programming. It
being hard does not require pattern matching.

Most mutable objects are easy because they don't support concurrent
modifications. Therefore pattern matching would not make them harder to
implement. Pattern matching such an object while it is being concurrently
modified is simply a programmer error just like any other use of the object.

Not allowing pattern matching of mutable objects would be similar to not
allowing equals and hashCode methods on such objects and for similar reasons.

~~~
pron
I'm not talking about _not allowing_. I merely explained, because you asked,
why pattern matching does not play well with mutation, and it doesn't. equals
and hashCode also don't play well with mutation, and indeed they are not
automatically generated for mutable objects, but they are for records. As the
author of a class you can provide a reasonable implementation of them, as well
as of a deconstructor for pattern-matching, but that requires knowing more
about the particulars of the class.

There are other reasons for not allowing mutable records, but most --
including pattern matching -- can be summarized as "that's not what records
are about, which is being 'dumb' data aggregates". You can read a more
detailed discussion of the subject here:
[https://cr.openjdk.java.net/~briangoetz/amber/datum.html](https://cr.openjdk.java.net/~briangoetz/amber/datum.html)

~~~
mahkoh
>I merely explained, because you asked, why pattern matching does not play
well with mutation, and it doesn't.

What you explained so far is that it doesn't play well with concurrent
modification. And then I said that the "not playing well" comes from the
concurrent part, not from the modification part.

I'm less interested in the philosophy of records and more in the use cases
they support.

~~~
pron
> What you explained so far is that it doesn't play well with concurrent
> modification

Fair enough.

> I'm less interested in the philosophy of records and more in the use cases
> they support.

The use cases and motivation, as well as why mutation is wrong for records,
are explained in the links I provided. Here they are again:

* [https://openjdk.java.net/jeps/359](https://openjdk.java.net/jeps/359)

* [https://cr.openjdk.java.net/~briangoetz/amber/datum.html](https://cr.openjdk.java.net/~briangoetz/amber/datum.html)

~~~
smarks
Mutation without concurrency is still a problem for pattern matching. To
return to your earlier example, suppose you've matched Point(3, y) and you're
now in some code that assumes that the x-value of the Point is 3. Now, you
call some method that has some reference to the point you're working on, and
it mutates the x-value to 4. The assumption in that code after the match is
now broken.

------
dvt
The article doesn't really touch on how this plays with generics, if at all.
Anyone know if we can do:

    
    
        public record MktOrder(T currency, int amount)<T> { }
    

or

    
    
        public record MktOrder<T>(T currency, int amount) { }
    
    

Would be a bummer if it breaks the idiomatic (and canonical) way of working
with generics. In any case, it's a much needed and welcome feature.

~~~
smarks
Yes, records work with generics pretty much the same way they work with
classes. You can do

    
    
        public record MktOrder<T>(T currency, int amount) { }
        var m1 = new MktOrder<USD>(usd, amount);

------
bobthepanda
Can anyone explain to me what the benefit of this is over Lombok annotations?
I like the Lombok builder syntax which this seems to lack.

~~~
peeters
Besides just the convenience of it not being a dependency, this will allow
other language-level features like pattern matching and object deconstruction
in the future. Basically anything that requires the language to have a
tuple/named tuple type.

~~~
bobthepanda
> tuple/named tuple type

As someone not familiar with using those, can you ELI5 what those are and what
their benefits are?

~~~
peeters
A tuple is the generic term for a pair, triple, etc. It's a collection of data
of heterogeneous types, unlike an array which is homogeneous. When the members
are unnamed, the data is indexed by its position like an array.

E.g. a method could return an unnamed tuple ("foo", 7) which might have type
(String, int).

In some languages like ML, having tuples is such a fundamental part of the
language that things like functions always act on a single argument which is
actually just a tuple. So max(5, 5) is a function that takes a tuple of two
ints as its parameter.

When you have tuples as a first-class concept, you can start to do some
interesting shortcuts like:

    
    
        (b, a) = (a, b); // swap two variables with no intermediate
    

or

    
    
        String (first, last) = name;
    

This is called deconstruction, where you extract a tuple into variables
representing its members. This is common in a ton of languages (Typescript,
C#, ML, Scala, Python, etc). It also forms a cornerstone of pattern matching
expressions in some languages (Scala, Haskell) where you can create a
condition and simultaneously assign the data you care about to variables that
can then be used in that branch. Scala pseudocode:

    
    
       sum(list: ConsList<int>) {
         return list match {
             Empty(): 0;
             Node(first, rest): first + sum(rest);
         }
       }

------
the_arun
Interesting.. why just stop at constructor, can't we introduce fluent/builder
pattern as well?

~~~
kelnos
I suppose there's nothing stopping you from writing your own builder to
complement the record class, though of course part of the point here is to
avoid additional boilerplate.

------
juped
Yes, it's absolutely not innovation - but this is actually really cool in the
context of Java, which was never an innovator.

------
grizzles
A missed opportunity?

If the struct graph is all records or primitives, the compiler should include
a toJson(), fromJson() in the sealed class.

It could be done with classes too, but if it was records/prims only, then it
could be done without using reflection.

------
flerchin
So basically Lombok's @Data annotation with extra steps.

~~~
krzyk
Why more steps? You even have fewer keystrokes in case of record.

I'm looking forward to ditching lombok finally.

------
bassman9000
So Beans are back?

~~~
rooam-dev
Where they gone?

------
fourseventy
we are on java 14 already wtf?? I really don't like the new frantic release
cycle. Cassandra is still stuck on Java 8...

~~~
lostcolony
Everybody is still stuck on Java 8.

~~~
hinkley
I know of a project with substantial GC problems that's stuck on Java 7.

I just roll my eyes at this point when I hear about it.

------
nitrogen
From the first example:

    
    
        private final double price;
        private final LocalDateTime sentAt;
    

Can't help but notice that they won't be able to trade at exact decimal
amounts whose fractions aren't sums of powers of 1/2.

Also wouldn't Instant be better for a timestamp?

~~~
pintxo
Production code likely should use neither double for currency nor
LocalDateTime for a point in time.

But for the purpose of the article, which is about Records being a new type of
Java class, this is kind of irrelevant.

~~~
nitrogen
I think it's kind of a problem if examples of how to write Java are full of
bad practices.

~~~
pgwhalen
For what it's worth, I work at a place that would write a similar domain
object, and we would use both of those and not feel bad about it.

The FUD on money as a double is way overblown in my opinion - it only becomes
a bad idea once you have to do precise math with the it (which is admittedly a
lot of the time when you're dealing with money, but surprisingly rarely in
some domains).

------
rosybox
So Java is basically getting structs. Seems awesome.

------
morelisp
There's a lot to like about this, but I laughed at trying to cast nominal
typing as a design choice in typing per se rather than e.g. a tool to make the
VM's loader simpler. It gives away the lie just two paragraphs after
explaining it:

> This choice was partially driven by a key design idea in Java’s type system
> known as nominal typing, which is the idea that... each type has a name that
> should be meaningful to humans... the compiler still produced two different
> anonymous classes, $0 and $1...

Names extremely meaningful to humans!

