
Java 14 Feature Spotlight: Records - chhum
https://www.infoq.com/articles/java-14-feature-spotlight/?
======
cletus
So some immediate thoughts:

1\. Why not just call these "structs"? Everyone knows what a struct is. Also
making "struct" a reserved keyword is less likely to cause issues than
"record";

2\. I get that these are trying to be simple but why can't I compose records
from other records? I guess I can have a record member of a record?

3\. I couldn't see if records can be passed entirely on the stack like
primitive values can. This would be huge. I seem to recall there was some
effort to add this to Java but I'm not sure whatever happened to that. It
probably added a ton of complexity.

~~~
kasperni
1\. Records are reference types (heap allocated), structs are not. C# have
structs and will also be getting records with 9.0

2\. Yes, you can make records of other records.

3\. Not yet, but Project Valhalla will be bringing inline types to Java
[https://wiki.openjdk.java.net/display/valhalla/Main](https://wiki.openjdk.java.net/display/valhalla/Main)

~~~
skywhopper
What do you mean, "structs are not"? As far as I can tell, structs do not
exist in Java?

~~~
masklinn
What they mean is that `struct` in GC'd heap oriented languages is generally
considered an alias for "a value type", that's what it is in C# or Swift.

Records are shortcut syntax (and some runtime improvements) for a specific
category of simple classes but they remain a heap-allocated reference type so
calling records "struct" would be extremely misleading and would preclude (and
/ or make even weirder) the eventual inclusion of user-defined value types.

------
dwohnitmok
Records are great. If sealed classes become a thing
([https://openjdk.java.net/jeps/360](https://openjdk.java.net/jeps/360)) I'll
be absolutely elated. And it would be just the cherry on top if the two could
be used together (along with a private constructor). Scala has this, i.e.
`sealed abstract case class`, Kotlin does not, i.e. `sealed data class` is not
valid.

I am thrilled that algebraic datatypes (effectively the combination of records
and sealed classes) are finally entering mainstream consciousness and that
statically typed languages all over the place are starting to adopt them.

~~~
jillesvangurp
Kotlin data classes cannot be extended and they can only extend sealed
classes. So they are effectively sealed already.

~~~
dwohnitmok
I perhaps should have been more explicit about the goal rather than the
method, since sealing records is only one part of this.

This is all in service of private constructors. You effectively cannot have
private constructors for data classes in Kotlin because the copy method
remains public and cannot be "turned off." `sealed abstract case class` in
Scala gets rid of that.

Private constructors for record types is a fantastic light-weight way of
incorporating runtime checks into compile-time types.

------
AzzieElbab
Cool. Can't wait until java people start going crazy about profunctor optics

~~~
augusto-moura
Guess everyone can dream

------
fctorial
At first I thought it was project valhalla merging into mainline java.
Apparently this is a java language feature. Java is starting to move away from
being the C of jvm. Java constructs are no longer direct mappings of jvm
constructs.

~~~
tristanstcyr
I think that this ship has sailed quite some time ago. For example, inner
classes which were introduced in Java 4 (if my memory serves right).

~~~
lokedhs
I'm pretty sure it was in Java 1.2. That's when Swing was implemented, and I
don't recall ever having used Swing without inner classes.

------
th0ma5
This sounds a lot like Clojure records and it may be some time before I can
mentally distinguish them just based on the name alone.

------
abraxas
Will arrays of records be contiguous in memory or are we still chasing
pointers just as with all other arrays of objects in Java?

~~~
chris_overseas
You'll still be chasing pointers with these. What you (and myself and many
others) are looking for are inline types[0][1] that are part of Project
Valhalla[2].

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

[1] [https://dzone.com/articles/valhalla-lw2-progress-inline-
type...](https://dzone.com/articles/valhalla-lw2-progress-inline-types)

[2]
[https://wiki.openjdk.java.net/display/valhalla/Main](https://wiki.openjdk.java.net/display/valhalla/Main)

~~~
abraxas
Thanks, you're right on. That's what I'm after and that's exactly what Java
needs to be useful for doing high performance computations in a sane fashion.

~~~
xxs
You have direct buffers but that's that... You can use multiple arrays of
primitives, inverting the problem 90%. None of the solutions is optimal, easy
to maintain, etc.

~~~
abraxas
Yes there is also the sun.misc.Unsafe class but of course that's skating on
thin ice.

------
dangerlibrary
These seem extremely similar to Scala's case classes. In my experience, case
classes are often the feature that sells people on moving over to Scala from
Java (the other being exhaustive pattern matching warnings from the compiler).

Fingers crossed Java never implements the `implicit` keyword...

~~~
McDev
And Kotlin data classes: [https://kotlinlang.org/docs/reference/data-
classes.html](https://kotlinlang.org/docs/reference/data-classes.html)

~~~
q3k
... all of these are likely inspired by the extremely powerful namesake of
Java Records: Haskell Records [1].

[1] - [http://learnyouahaskell.com/making-our-own-types-and-
typecla...](http://learnyouahaskell.com/making-our-own-types-and-
typeclasses#record-syntax)

~~~
tombert
Gonna take an unpopular opinion here, but I completely hate Haskell records.

I'm sure that there's some kind of purity to them, but the fact that a two
records can't share field names ends up making them incredibly difficult to
use for anything practical. Granted, using the makeFields function with Lenses
allows you to kind of avoid the clashing, but that in itself feels like
somewhat of a hack.

~~~
dwohnitmok
I don't know if I _hate_ Haskell records, but I certainly agree they feel like
an ugly hack.

They're essentially just syntactic sugar for a standard product type with
auto-generated functions for access and update. Unfortunately those functions
come with foot guns. Here's two:

1\. As you pointed out, clashing field names (since fields don't truly exist
but are rather functions)

2\. Partiality of the generated functions if you have multiple constructors
for a type, at least one of which is a record.

Lenses are great, but would benefit from first-class integration as the
"blessed" way of interacting with records (this hopefully would also reduce
their learning curve).

One of my top wishes for Haskell is proper row types a la Purescript.
Purescript records and polymorphic variants are amazing. I would absolutely
love if this [https://github.com/natefaubion/purescript-checked-
exceptions](https://github.com/natefaubion/purescript-checked-exceptions) was
possible in Haskell.

~~~
tombert
I would definitely support making Lenses an officially-integrated part of the
language, since _for the most part_ they do somewhat address my complaints. If
they could make the syntax for it feel a little less hackey I think I wouldn't
whine at all about records.

I'm not super familiar with Purescript, so I can't comment on the row types;
are they more-pleasant than Haskell's?

~~~
dwohnitmok
Yes! Syntactically Purescript essentially automatically gives you lightweight
lenses for records. E.g.

    
    
      myValue { field0 { subField0 { subSubField0 = 5 }}}
    

Semantically... oh boy, row types (i.e. extensible product types) and
polymorphic variants (i.e. extensible sum types) are fantastic! I'd go so far
as to say as long as you give me `newtype`, I would be in favor of row types
and polymorphic variants completely replacing `data` declarations.

    
    
      -- forall x here means that our record can have other fields
      resetName :: forall x. { name :: String | x } -> { name :: String | x }
      resetName itemWithName = itemWithName { name = "Default" }
    
      -- Notice how Person is just a type synonym!
      -- And there's no data constructor
      -- { name :: String, id :: Int } is a type, not just a constructor
      type Person = { name :: String, id :: Int }
    
      type Pet = { name :: String, owner :: Person }
    
      bob = { name : "Bob", id : 0 }
      
      doggy = { name : "Doggy", owner : bob }
    
      -- Look they both work!
      bobReset = resetName bob
    
      doggyReset = resetName doggy
    
      -- And no clashes, because records are a first class type declaration
      bob.name == "Bob"
      doggyReset.name == "Default"
      
    

Purescript unfortunately doesn't have built-in polymorphic variants (you can
derive them from row types with a bit of type-level hackery), so a bit of this
is pseudo-Purescript (mainly the case declaration, functionally you can get
the same thing, it just looks slightly different).

    
    
      newtype TextTooShort = ...
      newtype TextNotValidASCII = ...
    
      findFirst5CharWord :: forall e. String -> Either (TextTooShort + e) String
    
      asciiNumberRepresentation :: forall e. String -> Either (TextNotValidASCII + e) (Array Int)
    
      processString :: forall e. String -> Either (TextTooShort + TextNotValidASCII + e) (Array Int)
      -- No lifting or wrapping of intermediate errors required!
      -- All the types still unify!
      processString = findFirst5CharWord `andThen` asciiNumberRepresentation
    
      -- But you still get exhaustivity checking!
      displayString :: String -> String
      displayString input = case (processString input) of
        Left TextTooShort -> "You didn't give me enough text!"
        Left TextNotValidAscii -> "Whoops no ASCII rep exists!"
        Right arrayOfInts -> show arrayOfInts

------
jfengel
Doesn't adding a "record" keyword break a lot of existing code? It wasn't a
keyword as recently as JDK 8
([https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_k...](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html)).

~~~
chrisseaton
Can you give an example of some code it'd break?

At what points in a valid legal Java program could you have previously written
record, where it would now conflict with this keyword in the locations where
it is valid?

~~~
Macha
Unless the rules of keywords have changed:

Map<String, String> record = getSomeData();

This should no longer be valid if record is a keyword?

~~~
chrisseaton
But a record keyword isn't valid where you've written it there. So isn't it
still an identifier?

~~~
Macha
No, keywords are banned as identifiers everywhere, not just in ambiguous
contexts.

~~~
chrisseaton
I don't believe that's true.

    
    
        boolean isRecordStart() {
         if (token.kind == IDENTIFIER && token.name() == names.record &&
                (peekToken(TokenKind.IDENTIFIER, TokenKind.LPAREN) ||
                 peekToken(TokenKind.IDENTIFIER, TokenKind.LT))) {
              checkSourceLevel(Feature.RECORDS);
              return true;
        } else {
           return false;
       }
    

[https://github.com/openjdk/amber/blob/4f5927ba8f23c2e40ac467...](https://github.com/openjdk/amber/blob/4f5927ba8f23c2e40ac467809ef83b7395084355/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java#L4114-L4122)

------
hyperpallium
They're not really "tuples", but classes with auto-boilerplate. e.g. you can't
say:

    
    
      (x,y) = getXY();
    

instead

    
    
      record Coord(int x, int y) {}
      Coord coord = getXY();
      // access as coord.x, coord.y
    

It's not _terrible_ , but it's terribly javay.

~~~
kasperni
That is by design
([https://cr.openjdk.java.net/~briangoetz/amber/datum.html](https://cr.openjdk.java.net/~briangoetz/amber/datum.html))

Why not "just" do tuples?

Some readers will surely be thinking at this point: if we "just" had tuples,
we wouldn't need records. And while tuples might offer a lighter-weight means
to express some aggregates, the result is often inferior aggregates.

Classes and class members have meaningful names; tuples and tuple components
do not. A central aspect of Java's philosophy is that names matter; a Person
with properties firstName and lastName is clearer and safer than a tuple of
String and String. Classes support state validation through their
constructors; tuples do not. Some data aggregates (such as numeric ranges)
have invariants that, if enforced by the constructor, can thereafter be relied
upon; tuples do not offer this ability. Classes can have behavior that is
derived from their state; co-locating state and derived behavior makes it more
discoverable and easier to access.

For all these reasons, we don't want to abandon classes for modeling data; we
just want to make modeling data with classes simpler. The major pain of using
named classes for aggregates is the overhead of declaring them; if we can
reduce this sufficiently, the temptation to reach for more weakly typed
mechanisms is greatly reduced. (A good starting point for thinking about
records is that they are nominal tuples.)

~~~
hyperpallium
> The major pain of using named classes for aggregates is the overhead of
> declaring them

But the pain remains the same in client code. Because api's are written once,
but used many times, it is more important to make client code simple than api
code.

In mathematics and programming, tuple content is accessed by position, not by
name. e.g. The arguments in method/constructor invocation form a tuple - so
there is precedent, even within java itself. Note that callers (clients)
address arguments by position, and callees address them by name (api's).

There'a a very similar passage in the article to the one you quoted. Regarding
nominal vs structural there, a better example is that java's class system is
nominal.

"records" are nominal in that they have a class-like name, and also their
content is accessed by name. Tuples are structural (not named), and access by
position (not name). So I think records are not really "tuples".

~~~
hyperpallium
EDIT as you can see, I'm concerned about the client experience for multiple
return. Perhaps it's not so bad:

1\. when assigning to a variable, type inference is easy, because can't
subclass records, so can omit type ceremony. So, although _nominal_ , client
can treat as structural.

2\. with standard IDE completion/lookup, user need't know or enter the exact
accessor names. So, the assigned variable acts like a namespace.

This mightn't be perfect, because appropriate names for the client's usage can
differ from what is appropriate from the api's perspectice. (with tuples, user
can name to suit their purpose).

------
cies
> Records can be used in a wide variety of situations to model common use
> cases, such as multiple return [...]

I thought multiple return was more interesting in the context of multiple
possible types: like a type in case of success and one in case of failure
(Either in Haskell, Result in Kotlin and Rust).

This type of type is a sum type a.k.a. a tagged union, you could think of as
Enum types where each item can have a payload.

This comes in really handy and allows Kotlin not to (ab)use exceptions for
non-fatal errors.

I never had the problem with returning several objects at ones, I would simply
write a class that embeds them (and in J14 I can write a record).

~~~
masklinn
> I thought multiple return was more interesting in the context of multiple
> possible types: like a type in case of success and one in case of failure
> (Either in Haskell, Result in Kotlin and Rust).

That's not a "multiple return", that returns a single value which is either
one type or the other. Although some languages do implement it via MRV by
necessity (e.g. Common Lisp, Go, Erlang).

~~~
sullyj3
Technically it's not even one type or another, eg in Rust, Some(1) and None
are values of the same type, Option<i32>. What's different is the tag of the
enum, or the data constructor, or whatever you want to call it.

~~~
masklinn
That’s the reification of it in many langage but at its core what you really
want if option is T | (). And some langages do express it that way though
mostly for historical reasons of not having sum types and / or being
dynamically typed (and there have been rumblings in the past of making rust
enum variants into proper types).

Of course this is not the case for all enum types. But option / either /
result, definitely.

~~~
sullyj3
Sure, namely typescript.

------
chaostheory
Did I miss something or will this feature make it into more license friendly
versions of Java? Otherwise what's the point if you have to pay Oracle
extortion money?

------
GiorgioG
C# has been waiting on record types for a while.

[https://github.com/dotnet/csharplang/blob/master/proposals/r...](https://github.com/dotnet/csharplang/blob/master/proposals/records.md)

Come on guys!

------
vbezhenar
I have some issues with this feature.

1\. I don't really like this immutable approach, I want setters too.

2\. I don't need those autogenerated equals/etc implementations, it's just
code bloat. May be it's OK to introduce some syntax to automatically generate
them like `String toString() default;` but it should not be implicit.

3\. It uses non-standard getter syntax. Java developers used `getProp()`
forever, now it's `prop()`. Shall we rewrite old classes or tools? Shall we
live with two different styles in one codebase? Both approaches are bad.

This feature looks very out of place in Java. I hope that it'll be removed in
the next release and replaced with something more compatible with old code. I
waited forever to get short property syntax in Java and got unusuable feature
in the end. I don't want to migrate to Kotlin just because of properties.

~~~
_bxg1
> I don't really like this immutable approach, I want setters too.

> I don't need those autogenerated equals/etc implementations, it's just code
> bloat. May be it's OK to introduce some syntax to automatically generate
> them like `String toString() default;` but it should not be implicit.

It sounds like you just want... regular classes? I don't understand your
position here. Immutable data structures have many uses and are quite popular.
If that's not what you want, why use these at all?

> Java developers used `getProp()` forever, now it's `prop()`. Shall we
> rewrite old classes or tools? Shall we live with two different styles in one
> codebase?

C# is the same way. The getProp() convention isn't as strong because it's had
computed properties for longer, but one doesn't have to preclude the other.

~~~
Rebelgecko
>It sounds like you just want... regular classes? I don't understand your
position here. Immutable data structures have many uses and are quite popular.
If that's not what you want, why use these at all?

Records provide a lot of syntactic sugar that classes don't (which is the
whole reason to use them— there's nothing stopping you from creating a regular
class with all final fields).

edit: I'm assuming that the "finality" of fields in records is identical to
the shallow finality of a final field in an object

------
smabie
It’s crazy that Java is still trying to catch up to Scala, yet, Scala seems
very maligned (at least on HN, my coworkers love it).

This is contraversial, but Java does not provide any value anymore. The JVM on
the otherhand, is a remarkable piece of engineering, unrivaled by pretty much
anything else in the space. I still can’t get over that Gosling was okay with
the first Oak or Java or whatever release. But he’s a super smart guy so I
assume there was a reason besides corporate pressure?

I’ve seen people on HN disagree, but we, as a profession, really deserve the
most powerful tool for the job. Notation as a tool for thought, etc etc.

PS: The thesis is that complexity is bad (obviously), but power is good (a
priori). Arguments against complexity are fine, but arguments against
expressiveness and power are ridiculeous. Moreover, the arguments I’ve heard
against power are strikingly similar to those I’ve heard against freedom, or
drug legalization, or democracy. They always revolve around how other people
are stupid and can’t be trusted with power/freedom/expressiveness/etc.

~~~
vips7L
In my experience scalac, scala, and sbt are some of the biggest productivity
killers at my work place. Most devs loathe going into the parts of our code
base that are scala (thanks Play).

The tooling for scala also is not as good as the rest of the Java world.

~~~
Justsignedup
I worked with Scala fans, and with non-fans.

Fans love it because they code in VIM. Non-fans tend to have better tools and
thus find Scala's tools very very lacking.

~~~
dtech
That is not my experience at all. IntelliJ has excellent Scala support and is
very widely used.

Quite the opposite, one of the downsides to Scala is that non-IntelliJ editor
support is currently lacking.

~~~
vips7L
This is exactly what I was referring to. I don't enjoy intellij too much and
the vscode and sublime Java extensions don't support a mixed project.

------
llcoolv
I am quite disappointed - if Java is supposed to compete with Kotlin, they
should not just provide an inferior clone of Kotlin features, but push it a
step further. Here, it is not the case as those records are basically Kotlin
data classes, but not really well thought out.

Unfortunately since Oracle took over, Java as a language went in a very dull
direction.

~~~
pjmlp
Kotlin fans keep forgetting that without the Java platform, meaning the JVM,
standard library and Maven central, Kotlin would be a non-starter.

No guest language ever took over a platform.

~~~
joshlemer
It might be argued that Elixir is more popular than Erlang these days.

~~~
pjmlp
Yet without Erlang and the libraries written in Erlang (OTP), Elixir wouldn't
stand on its own.

