
Data Classes for Java - mfiguiere
http://cr.openjdk.java.net/~briangoetz/amber/datum.html
======
HumanDrivenDev
_... To write such a class responsibly, one has to write a lot of low-value,
repetitive code: constructors, accessors ..._

If you're writing a plain data class, why on earth would you write getters and
setters? Just make your fields public and be done with it.

I will be forever perplexed by the idea of getters and setters (and this
extends to C#s syntax sugar). I have no idea what problem they solve. If
you're at the level of plain data, then just make a class with public fields
and no methods. If you're trying to write a 'higher level' object - then don't
refer to the names of fields in your method names. The getter/setter/property
approach is just the worst of both worlds. They make explicit reference to an
objects internal fields, while at the same time obfuscating what happens when
you actually access them.

~~~
derefr
> obfuscating what happens when you actually access them

This is the point, AFAICT. In languages with both primitive "read/write field"
operations that can't be interceded upon, but also with interfaces/protocols,
"obfuscating" (encapsulating) a field access in getter/setter methods is how
you allow for alternative implementations of the interface/protocol.

Specifying your object's interface in terms of getters/setters allows for
objects that satisfy your interface but which might:

• read from another object (flyweight pattern)

• read from another object and return an altered value (decorator pattern)[1]

• read from an object in a remote VM over a distribution protocol (proxy
pattern)

Etc.

Then a client can build these things and feed them to your library, and you
won't know that they're any different than your "ordinary" data objects.

You don't see this in languages with "pure" message-passing OOP, like Ruby or
Smalltalk, because there's no (exposed) primitive for field access. All field
access—at the AST level—goes through an implicit getter/setter, and so all
field access can be redefined. But in a language like Java, where field access
has its own semantics divorced from function calls, you need getters/setters
to achieve a similar effect.

And yes, this can cause problems—e.g. making expected O(1) accesses into O(N)
accesses, or causing synchronous caller methods to block/yield. This is why
languages like Elixir, despite having dynamic-dispatch features, have chosen
to _discourage_ dynamic dispatch: it allows someone reading the code to be
sure, from the particular function being called, what its time-and-space-
complexity is. You know when you call MapSet.fetch that you're getting O(1)
behaviour, and when you call Enum.fetch that you're not—rather than just
accessing MapSets through the Enum API and having them "magically" be O(1)
instead of O(N).)

\---

[1] Which is a failure of the Open-Closed Principle. That doesn't stop people.

~~~
mikeash
I like Swift's approach to this. (I'm sure other languages do it too, that's
just the one I know that does this.)

If you write a property (what other languages might call a "field") then by
default it's a stored property, i.e. it's backed by a bit of memory in the
object. If you need to take actions on changes, you can implement a willSet or
didSet handler to do so. If you need to change how the value is stored and
retrieved altogether, you can change it to a computed property, which invokes
code to get and set rather than reading and writing to a chunk of memory. All
of this is completely transparent to the caller.

It's particularly interesting because it still acts like a mutable value. You
can still pass a reference to such a field into an inout parameter using &, or
call mutating methods on it. Behind the scenes, the compiler does a
read/modify/write dance rather than mutating the value in place.

~~~
geophile
I really, really dislike Swift's approach. get/set/willSet/didSet -- so much
complication to preserve the illusion that you are operating on a field when
you are actually doing no such thing. Why is this desirable? It reminds me of
a class of C++ footguns where some innocuous code is actually invoking member
functions due to operator overloading.

I think that Java got this one right. (I do not at all like the cargo cult
custom of getter/setter methods for fields; I'm referring to the language
only.)

~~~
millstone
Today's fields are tomorrow's computed properties. Fields are rigid and cannot
be changed without recompiling dependencies. Notice how few fields there are
in Java's standard library. Why have fields at all?

~~~
grok2
How many times have you run into this in your career (the need to convert
"today's fields to tomorrow's computer properties")?

~~~
shabbyrobe
Plenty of times! The situation where a library released to third parties
requires internal structural changes is not an uncommon one. What do you do?
Break every piece of third party code or satisfy the new structure and the old
interface simultaneously with a computed property? "Move fast and break stuff"
doesn't always have to include breaking stuff.

------
tantalor
AutoValue with Builders is nice:

    
    
      Animal dog = Animal.builder()
        .setName("dog")
        .setNumberOfLegs(4)
        .build();
    

[https://github.com/google/auto/blob/master/value/userguide/b...](https://github.com/google/auto/blob/master/value/userguide/builders.md)

~~~
guelo
Not that nice compared to kotlin:

    
    
        data class Animal(dog: String, numberOfLegs: Int)  
    
        val dog = Animal(
            name = "dog",
            numberOfLegs = 4
        )
    

The amount of boilerplate for AutoValue builders is so ridiculous that most
people use IDE plugins to generate it.

~~~
Naac

      data class Animal(dog: String, numberOfLegs: Int)  
    

and

    
    
        Animal dog = Animal.builder()
        .setName("dog")
        .setNumberOfLegs(4)
        .build();
    

are very different. In this case the builder pattern is more powerful as it
lets you pass around builders that haven't been "built" yet. Kind of like
currying.

~~~
dangets
True, but the constructor method is checked/validated at compile time vs. run
time with the builder pattern. Not quite the same as your example, but Kotlin
does have good syntax for copying data classes while only changing a single
field - this could be used similar to your currying usage.

[https://kotlinlang.org/docs/reference/data-
classes.html#copy...](https://kotlinlang.org/docs/reference/data-
classes.html#copying)

~~~
guelo
Here's that kotlin copy syntax

    
    
          val cat = dog.copy(name = "cat")

------
harryh
I, for one, am enjoying the ever so slow scalaization of java.

~~~
sushisource
If the JVM magically got rid of nulls and Scala cleaned up some of the
slightly wartier bits (implicits come to mind, although they are useful and no
clue how I'd fix em...) then Scala would be my favorite language around by
quite a bit.

Besides maybe Rust. Really different usecases though.

~~~
bunderbunder
I don't think many of Scala's wartier bits will go away without Java getting
its act together re: things like type erasure and a bifurcated type system,
because a lot of the odder things in Scala are basically workarounds for
deficiencies in the underlying platform. And I just don't see much political
momentum behind either of those.

~~~
willtim
Why would type erasure affect the front end language? I'm generally
interested.

~~~
lmm
One of the earliest use cases for implicits is that in Java most types are
erased but Arrays aren't, so you can't instantiate an Array in a generic
method without having some way to pass the concrete type through.

~~~
willtim
I do not see why this problem cannot be solved by a compiler rather than a
language feature. The type is statically known. It may require additional
metadata alongside class files for separate compilation, but I don't see this
as a big issue.

~~~
bunderbunder
Scala needs to interoperate with other Java languages, and there is no
platform-wide spec for supplying such metadata.

Even if there were, it couldn't be statically known at compile time in the
case where a library written in Scala is being called by code that was written
at a later date.

And even without _that_ consideration, types still cannot be statically known
in cases where you're being handed objects whose type is not determined until
run time. Which happens all the @$#% time in Scala, due to its use of
algebraic data types.

So, e.g., Java's run-time has no real way of expressing the type
`Option<List<String>>`. You can declare it and get some compile-time checking,
but that's all erased before the final bytecode is generated. There's no such
thing as `Option<List<String>>.class`. This really limits what you can do with
generic code. Scala added its own parallel type tag system to get around that.
It's weird as heck and only really usable from other Scala code, though.

~~~
willtim
> Scala needs to interoperate with other Java languages

But does it? Can I even use e.g. a Scala collection from Java? (let alone
Clojure, Kotlin, etc) Certainly it looks easy to import and use Java objects,
but that looks to be the extent of it.

> it couldn't be statically known at compile time in the case where a library
> written in Scala is being called by code that was written at a later date

When you build some new code that uses an existing library, any parametric
polymorphic type variables get instantiated at the use sites. Where I admit
this isn't true, is the adhoc polymorphic dynamic dispatch via subtyping, but
this wasn't given to me as an example. I don't see that ArrayLists and ADTs
should present a problem.

> types still cannot be statically known in cases where you're being handed
> objects whose type is not determined until run time

Again, I think you are referring to dynamic dispatch here? I agree this could
be a problem in open class hierarchies. But the ADT encoding in Scala is a
closed statically-known hierarchy, so I can't see why it has to cause
problems. Truly "generic" or parametric polymorphic code should not depend on
type tags or even be allowed to introspect the runtime type, doing so violates
parametricity, which ultimately reduces our ability to reason about the code.

~~~
bunderbunder
> Can I even use e.g. a Scala collection from Java? (let alone Clojure,
> Kotlin, etc)

Yup to all. Sometimes the code comes out looking a bit ugly (e.g, you _can_
access default parameters from Java, but it's done by calling methods with
kind of awful auto-generated names), but the only things you really can't get
at are things that relies on implicits, type tags, or traits with fields.

------
icedchai
Or just install Lombok.

~~~
bherrmann7
Lombok @Value for the win!

~~~
gravypod
@Data seems more like what they are proposing. It comes with toString, equals,
etc and has a bunch of cool features [0].

[0] -
[https://projectlombok.org/features/Data](https://projectlombok.org/features/Data)

------
tybit
As the article starts out with, Algebraic Anny, Boilerplate Billy etc wanting
different things, I wonder if rather than side stepping this issue with a
compromise if they could solve it with metaclasses as proposed for C++
[https://herbsutter.com/2017/07/26/metaclasses-thoughts-on-
ge...](https://herbsutter.com/2017/07/26/metaclasses-thoughts-on-
generative-c/)

I really like the idea of metaclasses and would love to see proposals for C#
and Java too. As the proposal mentions Java and C# have already gone down a
separate path with interfaces (and structs for C#) but would be cool to see if
it could be resolved anyway.

------
bognition
Honestly immutable data classes that are created via builders is a must have
in Java.

~~~
eropple
Is there something that a builder gets you that Kotlin-style data classes
doesn't?

~~~
cle
One thing I love about builders, besides the sibling replies, is that builders
force users to explicitly name the arguments. Passing args to a
constructor/method/whatever with implicit argument ordering can be error-prone
in certain situations (multiple adjacent args with the same type).

    
    
        c = Class(firstName, lastname)   // are these passed in the right order?
        c = Class(lastName=firstName, firstName=lastName) // obviously incorrect
    

Especially when you're using other magic like auto-generating constructors
based on fields (as with Lombok)...then rearranging two String fields can
subtly break things with no compiler errors!

~~~
lmm
Named parameters for functions (including constructors) are a good idea -
better to build them in as a general case rather than making people read a lot
more code to have them in one specific case. Look at what Scala does.

------
pjmlp
Interesting to see everyone discussing Java vs C# properties, when C#
properties are actually based on how Eiffel and Delphi do them. :)

------
nikolay
Why underscores in "__data"? Is this just a temporary thing?

~~~
evacchi
It's decoy to avoid bikeshedding. The value class proposal used __value

~~~
billrobertson42
When you say decoy, do you mean it's intentionally gross to keep people from
going off on syntax debates?

------
niuzeta
So essentially Lombok?

------
kodablah
> Can a data class extend an ordinary class? Can a data class extend another
> data class? Can a non-data class extend a data class?

No, no, and no. There are interfaces with default impls these days, don't
allow base class state.

And figure out how to make interfaces "sealed".

------
virmundi
Can’t we just use AOP to generate the boilerplate code if we are ok with
Kotlin style data types? Toss an annotation on the class like @Data. After
that everything should get generated at compile time.

------
yeupyeupyeup
Classic Brian Goetz bashing Java serialization.

------
deepsun
"data class" is already in Kotlin flavor.

Yes, I refuse to call Kotlin a different programming language like Scala,
because it's just syntactic sugar over good-ol-Java, while all tools,
approaches, stackoverflow are the same. But it has the data classes,
implemented just like the article described.

~~~
ben-schaaf
Have you actually used kotlin before? It most certainly has features that are
much more than just syntactic sugar.

~~~
deepsun
Yes, I use it every day and absolutely love it. Especially the part that it's
still syntactic sugar over Java.

~~~
ben-schaaf
It does a lot more than just syntactic sugar. It expands the type system with
nullable types, smart casts and covariant/contravariant generics, and it has
actual closures. I agree that most of the features kotlin provides are
syntactic sugar over Java, but kotlin is as much a separate language to Java
as Scala is.

------
itronitron
This seems unnecessary if the functional / lambda approach is fully adopted as
the data can be understood by looking at the functions that operate on the
data, so there would be less of a need for simplifying access to the data
values when passing them into functions. As I have developed more data-
intensive computational methods in Java I no longer implement data
encapsulating classes but rather just use collections containing the basic
data types.

