Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Java 14 Feature Spotlight: Records (infoq.com)
204 points by chhum on Feb 4, 2020 | hide | past | favorite | 148 comments


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.


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


C# records:

https://github.com/dotnet/roslyn/blob/features/records/docs/...

It's a struct or class but you only define the data members and the compiler auto-generates boiler plate like constructors, equality tests, etc.


So same as Kotlin Data Class? Records is a weird name, IMO.


"Record" is what it was called in Algol and Pascal days.


it's also what's still called by the functional crowd, I believe.


They are called records in F# which predates kotlin


Personally I have been wanting to use headerless objects for a long time (pre java7). The best available option is DirectBuffer, still.


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


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.


Mark Valhalla project has been in the minds of people caring about performance for about 16years now...

Struct was a suggested name.


Structs in other similar languages, for example, C#


Aren't classes already allocated on the heap? Why would C# need another type like that?


There low overhead data classes. It's takes a lot create a data class correctly. Including implementing equals, hash etc. Plus a good syntax for creating records.

And encourages immutability.


The point of both the C# feature and the Java feature is to reduce boilerplate. They are not for improving the performance of apps.


The comment is wrong. Records are syntactical sugar for either value or reference types (depending on the language). They simply automatically implement equality, constructors and so forth.


1. C popularized the `struct` keyword, but "record" is a more general, language-agnostic term for nominal tuple types in the literature.

2. Yes, it is widely accepted that a has-a relationship is the proper way to compose data. Using inheritance for composition is bad practice.

3. The article mentions inline classes, which is what the proposed value type feature is called these days. Presumably many or most record types will be inline if and when they're both implemented in some future Java version.


It's not structs in C, it's more like records in Haskell.

If you have two records {a: 1, b: 2}, those are equal because they are the same.

If you have two structs {a: 1, b: 2}, those are also equal but they are separate instances that consume more space. The comparison is recursive.

It's more like how string works in Java and most modern languages with constant pools - behaves like values but only takes constant space.


There's a detailed Brian Goetz writeup about much of this at:

https://cr.openjdk.java.net/~briangoetz/amber/datum.html


Disclaimer: it's been a long time since I coded in Java, and I have no knowledge of this new record feature, but coming from clojure I would guess the answers to be:

1. You can't change a record after the fact because if you do it's no longer reliable, it's been tampered with :)

2. Yes, existing records only. You can always create a new record pointing to the old or any other record.

3. Hope not, would make it unpractical to handle verbose facts.

I guess clojure really twisted my mind ;)


Ad 1: Maybe they like pascal, scheme etc. more than c?


"record" is used in the Haskell world, for example. Also ATS.


They are not that similar to a struct in C in that they are not just about being a data carrier. So that might be confusing if that is what you expect.

Future work is intending to solve the third point and add deconstructing the record for pattern matching.

“We can declare a simple x-y point abstraction as follows: record Point(int x, int y) { } which will declare a final class called Point, with immutable components for x and y and appropriate accessors, constructors, equals, hashCode, and toString implementations.”


Records are great. If sealed classes become a thing (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.



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


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.


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


Guess everyone can dream


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.


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).


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.


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.


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


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

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

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


I always thought array elements were contiguous in memory? Is it that the objects are contiguous, but the pointers are not? If so, this does seem pretty expensive and I wonder if there's any workarounds.


Well arrays if elementary types are contiguous, yes. Arrays of objects are arrays of references to instances. The references are contiguous, but they point to instances all over the place.


that makes perfect sense, thank you


Does it strike anyone as odd that something trivial in toy languages from the 70s is a separate project 30 years in and named for the the heaven of the gods in the world of Java?


On the other hand the toy languages from the 70s never caught on. In many workplaces Java is considered the high performance language and most devs would prefer Python/PhP at 1/100th the performance.


I don't know what point you are making, but C had structs in the 70s too and it hasn't been a technical hurdle to implement there either. It's literally a few instructions of asm.


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.


I switched to java from c in the mid 90's. I remember thinking how ridiculously wasteful it was to allocate an array on classes one at at time rather than as a block of memory. I think I was using berkeley DB at the time in c and it let you do that.


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.


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


That ship has sailed.


What do you mean?


I think there was an opening for a fast safe alternative to C++ in HPC, but I think Rust has taken that spot now. Control over allocation, alignment and placement is important in HPC and imo Java took too long to address these issues.


Records can be combined with inline types when they get there with project Valhalla.


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...



... 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...


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.


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 was possible in Haskell.


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?


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


Functional languages tend to have name resolution as a separate step before type checking. OO languages tend to intermingle name resolution and type checking.

For example in OO languages `obj.func(1, 2)` will typically require the compiler to know the type of obj before knowing which func method is being referred to here.

In Haskell however the equivalent expression is `func obj 1 2`. The compiler has already performed name resolution and knows exactly what func is. So that when doing type-checking, it is not only possible for typing information to propagate from object to method, but also from function to object. That is to say, it is possible that the compiler initially knows what type func has, but has no idea what type obj has; it will only do so after type inference using the type of func.

This makes type inference more general and more elegant. I suspect it also makes the implementation of name resolution easier. Unfortunately as you have pointed out, it's practically more of a nuisance.

Also note that this applies to more than just names of fields associated with a type; it even applies to type names associated with a type. For example if you have

    class G a where
      data N a :: *
The name N is directly usable without even mentioning G. This would require, say in C++, you to write `G::N` to refer to an inner class:

    class G {
    public:
      class N;
    };
The reasoning is of course the same.


For those of us who don't do haskell, can you give us an example of what does not work and then work around?


For an example of clashing:

  data Person = Person {
    id :: Int,
    name :: Text
  }

  data Account = Account {
    id :: Int, # Oh no! Name clash
    amount :: Int
  }
This is because `id` isn't really a "field," among other thing it's a generated function so that

  id :: Amount -> Int
  id (Amount actualId _) = actualId
This particular case is compounded by the fact that `id` is already the name of the identity function in Haskell's standard library (one reason why many projects that roll their own standard library rename `id` to `identity`).

For the partiality:

  data AllowedItems = Person { id :: Int, name :: Text} | JustANormalInteger Int

  name (JustANormalInteger 5) # Blows up at runtime
That is name has the type AllowedItems -> Text, even though JustANormalInteger 5 is also a member of AllowedItems

The workaround for clashing is to prefix your fields with your type name (luckily Haskell still has module namespaces so you don't need a globally unique name, just one unique for your module).

The workaround for partiality is to disallow a type with a record to be anything other than that record (e.g. disallow JustANormalInteger or require that Person {...} must first be an independent type before it can be used in AllowedItems).

The talk of lenses is a way to generalize the notion of field accessors and talk about the "path" generated by a series of field accessors in a first class way. E.g. you might imagine that in a standard OO language

  myClass.field0.subField0.subSubField0
could have a stand-alone concept of `field0.subField0.subsubField0` as a path through `myClass` that you could then use either to get a value (get(field0.subField0.subSubField0, myClass)) or set a value (set(field0.subField0.subSubField0, myClass, newValue)).

Moreover, if you can talk about the paths through a class in a first-class way, what's to say that this path must actually correspond to a true field in a class? It could just be anything "field-like." For example:

  lens = integer.lastDigit.leastSignificantBit
  set(345, lens, 0) // Yields 344
even though the individual digits of myInteger aren't actually generally truly fields in any OO language nor are the individual bits of a number.

Because you have now removed the need for true "fields," lenses are one strategy to get around the hackiness of fields in Haskell and in fact in many ways represent an advance in expressiveness over "true" fields (although they suffer from not really being integrated into the language in the way faux-fields are currently in Haskell).


Whoops... apparently I was reading bash while writing this comment. Comments in Haskell should be `--` not `#`.


> but the fact that a two records can't share field names

That is awkward sometimes yes. Record fields are functions, and you can't have two functions with the same name in the same namespace.

You can, however, use namespaces to import two separate modules with conflicting names. It does mean you have to define the two different records in two different modules.

If, you want different records to have common fields because they are conceptually related, you probably want to use type classes.


> If, you want different records to have common fields because they are conceptually related, you probably want to use type classes.

I'm not a fan of ad-hoc typeclasses. I know that various approaches in modern Haskell advocate for them, but I think that ad-hoc typeclasses are a smell for overuse of typeclasses when plain-old data types should suffice.

I am a very big proponent of demanding that typeclasses have associated laws. Otherwise you can descend into the same hierarchy mess that plagues statically-typed class-based languages. And sometimes you just want to have different fields be named the same thing without an underlying profound relationship between the two.


I'm going on a limb and saying the namesake goes back to pascal. If not farther.


The original COBOL had them, maybe it's not surprising, it's a pretty natural metaphor for a business language. Fortran, Lisp and Algol 60 didn't have them. So I guess we can thank the bean counters for the idea.


Kotlin's data classes are quite different from the other three (Scala's case classes, Java's records and Haskell records), and those three are inspired by ML's records, but the name "record" predates even ML.


The implicit keyword is extremely powerful when used appropriately and not something that should just be shunned completely.

My favorite usage is probably in a typesafe Query engine that several people[1] built at Foursquare that is a lot better than what you can find in the java world.

  Venue.where(_.closed eqs false)
       .orderAsc(_.popularity)
       .limit(10)
       .modify(_.closed setTo true)
       .updateMulti
It allows for all sorts of typesafe queries that will fail at compile time if things are malformed including stuff like failing to query on an index.

https://github.com/foursquare/rogue

1. Hi Jorge, Liszka & Neil!


I've never shown a use of "implicit" to an experienced developer unfamiliar with Scala and seen them get excited about the language.

The best case scenario is that they aren't instantly horrified / confused / repulsed.


Automatic capture of Manifest / type parameters might be the most immediately appealing thing to a Java person learning about Scala, I dunno. Maybe don't show them implicit conversion.


There are a lot of tools in the world that are confusing at first but are extremely powerful and useful once you understand their use.

implicit is one of them


I never said it wasn't powerful.


The one thing that I like about Scala is Spark which is a really good example of what something like implicits can enable


I would say there are tons of other features as well.

More often than not Java programming just consists of throwing gang signs on your keyboard for your Ide to create tons of boilerplate.

Well case classes can implement traits, might have a companion object and can (poorly) replace enums.

Scala sometimes seems to not need standard classes.

Other languages like go, rust or c can do just fine without classes at all.


Implicits are very powerful when used tastefully but are being replaced/deprecated in Scala 3 regardless.


Actually, implicits aren't going anywhere in Scala 3.

There will be new syntax, but otherwise the underlying semantics will remain the same; it's just the implicit keyword itself and the kitchen sink of functionality that it provides in Scala 2 will be split up into new (soft) keywords like "given foo(using: Bar) = ..." rather than "implicit def foo(implicit bar: Bar) = ...", along with "extension listOps of" replacing "implicit class ListOps ...".

The community isn't united over the proposed changes (mostly due to migration concerns it seems), but overall it looks like a nice improvement over the status quo, particularly around typeclass definitions.


I only see a superficial similarity. Case classes are much more powerful. Copy constructor, private constructor, pattern matching, overrideable apply/equals/hashcode. Granted, some of that stuff is esoteric, but it does make case classes a very different beast.



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...).


The keywords you refer to in your link are all "classic" keywords. The last one that was added was "assert" with Java 1.4, which broke a lot of code. And made people realize that adding new "classic" keywords was a bad idea.

Now, Java only adds contextual keywords. Which are basically a sequence of Java letters that are tokenized as a keyword in certain contexts but as an identifier in all other contexts. var, record, inline, module, ect. are examples hereof.

You can read more about it in this JEP Draft about "keyword management for the Java Language": https://openjdk.java.net/jeps/8223002


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?


Unless the rules of keywords have changed:

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

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


> Unless the rules of keywords have changed

Keywords are context dependent nowadays. In the context you've provided record would not be treated as a keyword.


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


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


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...


wouldn't this be true any time an English language keyword is added to a language?


Yes. It's not a very frequent occurrence. Here are all new keywords added to the language:

Java 2: strictfp

Java 4: assert

Java 5: enum

Java 9: module, requires

Java 13: yield

Java 15: record

Now my local Java dev env is only Java 8, but I'm not allowed declare

String enum = "foo";

which would have been valid prior to its addition as a keyword.


Although enums weren't introduced until Java 5, enum was always a reserved word in Java.

module, requires, yield, and record are not reserved words, and can still be used as names for members etc.


> enum was always a reserved word in Java

This isn't true. It isn't listed in the list of keywords in Section 3.9 of the 1996 Java specification.


My apologies, you are right. I was confusing it with const, which has always been a reserved word (despite never being used).


> enum was always a reserved word in Java

Nope, i had to patch a bunch of codes because enum was not a reserved keyword until it was introduced in Java 5.


It shouldn't be that big of a deal. Renaming is easy as pie in java codebases.


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.


That is by design (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.)


> 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".


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).


That's more lack of destructuring syntax than non-tuple-ness, no?


I think both.


EDIT can you think of a destructuring method that operates with access by name, rather than by position?

  (lo, hi) = getLim();
It seems to me it would have to be something like:

  Lim lim = getLim();
  lo = lim.minimum;
  hi = lim.maximum
Pwrhaps records could define an ordering of their fields, so as to enable destructuring by position?


> can you think of a destructuring method that operates with access by name, rather than by position?

Yeah, this in JS:

    obj = {a: 1, b: 22, c: 333}
    const {b, a} = obj
My original point was more that, even in languages that have both "proper" traditional tuple types and the parenthesised destructuring syntax, we're not obligated to use destructuring. i.e. writing the following is perfectly legitimate, if verbose:

    a_tup = function_returning_tuple()
    x = first(a_tup)
    y = second(a_tup)


I hadn't seen that. It is a bit shorter, because you needn't repeat the source object. My e.g. would be

  lim = getLim();
  const {minimum: lo, maximum: hi} = lim;


> 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).


”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”

That’s or (another) one in case of failure, and not multiple return values. It returns a single value.

”I never had the problem with returning several objects at ones, I would simply write a class that embeds them”

One man’s “simply” is another man’s “ugly boilerplate” or “unnecessary production of garbage objects”.

You could similarly ”simpler write a class that embeds either of them”, like this:

   class AorB {
     private final Boolean itsAnA;
     private final A a;
     private final B b;
     public AorB(A aa) {
       a = aa;
       b = null;
       itsAnA = true;
     }
     public AorB(B bee) {
       a = null;
       b = bee;
       itsAnA = false;
     }
     ...
  }
Ugly boilerplate? Yes. Not optimally efficient? Yes. Not as nice as the real thing? Yes. Workable? Yes. Good idea? Debatable. Java culture would use an exception if B is an error type.


I wonder what the "match" statement would look like on your example. This is exactly as you say though: ugly, inefficient, not-as-nice, yet works. And clearly debatable, especially as it goes against Java culture.

The pattern could be used commonly, and clean-up heaps of code, and remove exception's hard-to-wrap-yr-mind-around flow jumps/etc, that I do hope for both tagged unions and match switches on them to be introduced. Some are already demo features IIRC.


> 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).


I dont understand the "proper use" for MRVs then. Okay we get nicer types, record types, to do them; but they are otherwise perfectly doable and with little ugly boilerplate using classes.

The "either values", or sum types/ tagged unions I mentioned, are the thing that Java really lacks (and because of that the exception system got heavily abused IMHO).


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.


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.


Sure, namely typescript.


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?


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

https://github.com/dotnet/csharplang/blob/master/proposals/r...

Come on guys!


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.


> 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.


>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


> It sounds like you just want... regular classes

I wish Java provided syntax sugar similar to C#'s with {get; set;} and quick initialization with

  new C {
    field1 = X,
    field2 = Y,
    ...
  }
Java is unnecessarily verbose in this regard.

Records bring syntactic sugar to records only. And you're stuck with pages upon pages of unnecessary boilerplate code for classes.


So you don't want records, you want something else.

The rest of us who've use kotlin's data classes or similiar features in other languages really like the first two points (the third point I don't mind but I don't like it either).


> 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 was used in the olden days, nowadays developers prefer not to use pointless words in method names.

I really like those, and it also strengthen the fact that records are not java beans. If you want those you should write a regular class.


I'm trying to decide how I feel about the naming convention. On the one hand, I don't want there to be two conventions for no reason. On the other hand, I think I see a reason: `getProp()` is a method that might do _anything_, up to and including making some I/O call that might fail. With a record's `prop()` call, though, I can expect that it's just getting a value. And I sure wouldn't mind having a convention that `valueName()` is a lightweight getter, whereas the 'get' in `getValueName()` is a warning to be on guard for side effects.

To the first two, it sounds like maybe you just don't want what it's offering. The autogenerated equals, etc can only work sensibly if instances are immutable. And if instances are immutable, enforcing a value-level comparison so that you can have predictable, standardized semantics around one of the pricklier bits of how a datatype is defined makes a fair bit of sense.


Why do you want mutable objects?


Because they are more convenient and performant. Immutable objects have their place, of course.


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.


Many things that become mainstream was at one point a subject of research, but that doesn't mean that the mainstream is "trying to catch up" to research, because most of what is researched never gets good or appropriate enough to be mainstream. Comparing a language that in its manifest declared that it is "blue-collar" and would strive to only adopt features after they've been tried and tested for years elsewhere and then become familiar with a language that is a tool to try research questions and to judge them by similar criteria is very strange. Scala and Java have very different design philosophies, they are designed for different audiences, and they have different goals.

You also seem to be under the impression that more features always mean "better" and that more power always means "better." I am not sure what gave you that impression, but by most reasonable metrics neither of these is the case. Your comparison with democracy is also misused. I think you'd agree that if every person had the right to arrest anyone else or to judge and punish people, our situation would not necessarily be improved. Software, or at least the kind of software that Java is designed for, isn't written by a person but by a group of people, often a very large group of people, and over what could be decades. The goal of various restrictions is to protect the work. Finally, arguments for "expressivity" are just as ridiculous as arguments against it; these are largely matters of personal aesthetic preference.


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.


Unlike Perl, I find that Scala encourages writing code that is quite clear and maintainable. Of course the unwise might go berzerk with symbolic method names and overuse of postfix notation, but written sensibly, Scala's compactness and avoidance of boilerplate and tricky control flow (break, continue, early return, multiple ctors, etc.) can improve ergonomics and readability.


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.


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.


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.


>> They always revolve around how other people are stupid and can’t be trusted with power/freedom/expressiveness/etc.

It's not the stupid that can't be trusted with Scala's power and freedom but it is those who are trying to be too clever. At least when they are working in a team.

Of course this is not only the case with Scala, but not many languages offer you so many different ways to abuse your power as Scala does.


> It’s crazy that Java is still trying to catch up to Scala,

Or Haskell etc... Plenty of languages have product types.


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.


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.


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


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


You do know that the concept of records/data classes goes back long before Kotlin?

And what exactly are you missing?


When it comes to popular languages on the JVM, it doesn't go before Kotlin as Scala was never a popular language to begin with.

One thing I and a lot of people are missing is annotations, with them data classes in Kotlin are useful for JPA/REST/AMQP/etc DTOs. Another convenience would be the ability to add at least mapping methods like in Kotlin or implement interfaces (Serializable anyone?)

Java records are going to be yet another barely used feature.


Record are product types, with union types (sealed types in Java speak) you get algebraic data type, you can take a look to SML to understand why there will be used a lot.

Record have an automatic way to propagate annotations from the record component to the generated methods/fields and comes with native support of serialization (like enums).


Could you show an example of it? In none of the materials published so far there is anything even remotely similar to what you are suggesting.


http://cr.openjdk.java.net/~briangoetz/amber/datum.html

There you can read about algebraic data types in java, in Java 14 you get preview for records in Java 15 probably you'll get preview for sealed types - they were developed together for some time, only recently there was a split.


Where did you get the idea that records can't use annotations, have methods, or implement interfaces?:

"Beyond the restrictions above, records behave like normal classes: they can be declared top level or nested, they can be generic, they can implement interfaces, and they are instantiated via the new keyword. The record's body may declare static methods, static fields, static initializers, constructors, instance methods, and nested types. The record, and the individual components in a state description, may be annotated."

https://openjdk.java.net/jeps/359


Scala is still far more more popular than Kotlin. https://redmonk.com/sogrady/2019/07/18/language-rankings-6-1...

Scala 13th, Kotlin 20th.


I know Redmonk has reasonably believable language rankings, but a recent JVM ecosystem survey says otherwise - https://snyk.io/blog/kotlin-overtakes-scala-and-clojure-to-b...


I highly doubt they are going to be an unused feature, as Java is full of POJO's

I expect the current annotation-based libraries will start supporting record classes too.


It is full of ANNOTATED POJOs. Those records in their current form are useless for 95% of POJO use cases.


records can have annotations


Are you saying that Scala isn't popular, but Kotlin is? If so, what threshold are you using for "popular", that you think Kotlin exceeds but Scala doesn't?



Interesting survey, any ideas about what the audience of the survey was? (For trying to understand what kind of biases it might have in regards to non-Java languages)


I think being the recommended language for Android development makes it pretty popular.


Replying late, so you probably won't see this. Replying anyway, so I look less stupid in the future.

The survey SureshG put forward (in a post parallel to yours) shows Kotlin twice the size of Scala (which surprised me), and Java something like 15 times the size of Kotlin. So to say that Kotlin is popular, but Scala isn't, seems to me to be drawing the line in a fairly arbitrary place. It seems more reasonable to say that neither is popular, or that both are.

"But Android" you say. Well, that will make Kotlin pretty popular, but it hasn't yet. In the future, you'll be right. That's why I added this. In the future, my post is going to look pretty silly without this context.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: