[HN Gopher] JEP draft: Prepare to make final mean final
___________________________________________________________________
JEP draft: Prepare to make final mean final
Author : mfiguiere
Score : 199 points
Date : 2025-03-31 19:35 UTC (1 days ago)
(HTM) web link (openjdk.org)
(TXT) w3m dump (openjdk.org)
| Almondsetat wrote:
| final ly
| stickfigure wrote:
| Great! Now can we make `final` the default for all fields,
| variables, and parameters?
|
| (yes yes, I know, that would break syntax... but please come up
| with something to discourage mutability)
| magicalhippo wrote:
| Const-ness in C++ is something I miss in other languages. Being
| immediate able to see that this function or method couldn't
| mutate the object made it so much easier to reason about the
| code.
|
| Yeah I know there's ways around it, but then the author known
| what they told the other party to expect.
| josephg wrote:
| Yeah I find it a bit startling going from rust (where const
| is the default) to basically any other language. Sometimes I
| look at typescript function definitions and I'm like -
| uuuuhhh does this function mutate that parameter? Does it
| keep a reference to it? If the object I'm passing is mutated
| after I call this function, will something break? It's
| impossible to tell from a function signature, even with all
| of typescript's type safety. That gives me the willies. - and
| for good reason, it's tripped me up lots of times.
|
| Even the JS standard library struggles with this. You just
| have to remember that .sort() modifies the array in place
| (and returns it), but .slice() does a shallow clone of the
| array. (Not a deep clone - that would be different again!)
| mont_tag wrote:
| > Even the JS standard library struggles with this. You
| just have to remember that .sort() modifies the array in
| place
|
| ISTM that there are almost always some kinds of mutable
| data structures present in non-trivial programs. And
| outside of the program, you have databases to directories
| of files that get mutated by users or other parts of a
| program. I think this is just a fact of life.
| magicalhippo wrote:
| The point isn't that there's mutability, it's to be able
| to easily identify where it is and where it is not.
|
| Languages which lack the tools to do this are just harder
| to reason about, at least for me.
|
| In the JS example, as a non-JS coder, I would expect that
| a sort function that returns nothing/void would sort in-
| place, while a sort function that returns an array would
| return a sorted copy. I would _not_ expect it to sort in-
| place and return it.
|
| However in C++ I could easily see from the function
| definition that it might be doing that, because a sort
| that returns a copy would take a const reference for the
| input array. So if I came across a sort function which
| took a non-const reference as input I'd be able to at
| least suspect it's doing it in-place.
| recursive wrote:
| > Even the JS standard library struggles with this. ...
|
| I don't think this represents struggling. There needs to be
| _some_ way to sort in place. Sometimes you need to sort a
| big array and don 't want to allocate.
|
| And there should be _some_ way to clone the array without
| mutating. That 's slice. So you how do you sort a clone?
| .slice().sort()
|
| I think by far the biggest problem with ES .sort() is that
| number arrays don't sort numerically by default.
| steveklabnik wrote:
| > There needs to be some way to sort in place. Sometimes
| you need to sort a big array and don't want to allocate
|
| Your parent isn't saying there shouldn't be mutation,
| just that the mutation should be obvious.
|
| In Rust, the type signature for the in-place sort is
| pub fn sort(&mut self) where T: Ord,
|
| That `&mut self` lets you know that it's going to mutate.
| josephg wrote:
| Exactly. And for function parameters, rust also (usually)
| makes it obvious when you're passing by value, by
| reference or by mutable reference:
| foo(x); // Moves or copies foo(&x); // immutable
| reference foo(&mut x); // mutable reference
|
| I don't need to look up the signature of foo to
| understand what happens to my variable. It's obvious at a
| glance.
| recursive wrote:
| Fair point. I guess this is the cost of dynamic
| languages.
| titzer wrote:
| If possible, 1) design completely immutable data structures
| that can be broadly shared and don't need to be copied. If
| you need mutability, just embrace the fact that someone is
| going to abuse mutability and 2) try to create abstractions
| that can suffer abuse. If you're coming from a language that
| doesn't have const, you learn to build things that are
| hard(er) to screw up.
|
| While bad code can exist in any language, I get worried about
| too much const in code, because it means they failed at both
| 1) and 2) and instead there are usually seriously tricky
| protocols that must be observed to make the thing work. I
| often ran into code where people were sprinkling const all
| over the code to lock things down but they fundamentally did
| not understand the design and made it nearly impossible to
| evolve, unless you used casts to get rid of const, which
| defeats the whole purpose.
|
| I'm not saying const doesn't have value, but it's weapon #3,
| not weapon #1.
| magicalhippo wrote:
| > sprinkling const all over the code to lock things down
|
| That's like using a hammer on a screw, clearly not the
| right way.
|
| Thankfully I've never worked on such codebases.
| duskwuff wrote:
| What man that sees the ever-whirling wheel Of Change, the
| which all mortal things doth sway, But that thereby doth
| find, and plainly feel, How Mutability in them doth play
| Her cruel sports to many men's decay?
|
| (Edmund Spenser, 1596)
| xxs wrote:
| >but please come up with something to discourage mutability)
|
| Records?
| throwaway92422 wrote:
| Records in Java have final fields.
| nirvdrum wrote:
| I'd be happy with a `val` keyword. I thought that was a
| seriously missed opportunity when they introduced `var`.
| jjmarr wrote:
| From my perspective as a C++ developer, every attempt to use
| `const` for compiler optimization appears to be stymied by the
| existence of `const_cast`, because modifying a `const` value is
| only undefined behaviour if the underlying object is `const`.
| Glad to see that Java is willing to break the language to improve
| it.
| mb7733 wrote:
| > modifying a `const` value is only undefined behaviour if the
| underlying object is `const`.
|
| I found this sentence confusing. You mean that modifying a
| value that has been const_cast is undefined behaviour only if
| the original variable was const right? Or something else?
| oconnor663 wrote:
| (Rereading your comment, it sounds like you might already
| know all of this. Apologies.)
|
| If I understand correctly, here's a C++ example that has
| undefined behavior: int foo() {
| const int x = 42; int *p = (int *)&x;
| *p += 1; return x; }
|
| foo() is UB, because it modifies the const object x, using a
| pointer cast to "cast away const". (Unfortunately, UBSan
| doesn't catch this, and I'm not aware of any sanitizer that
| does.) It's tempting to say that the pointer cast is "at
| fault" for the UB, but consider this very similar example
| that's _not_ UB: int bar() {
| int x = 42; const int *const_p = &x;
| int *p = (int *)&const_p; *p += 1;
| return x; }
|
| bar() has exactly the same pointer cast as foo(), however in
| this case the original x object is _not_ const. That makes
| "casting away const" legal in this case. So the problem we're
| left with, is that knowing all the types isn't enough for us
| to tell whether this cast is going to cause UB. We have to
| know where the pointer _originally_ came from, which might be
| in another function or another file.
| mb7733 wrote:
| Right, gotcha, I just found it confusing to say "modifying
| a `const` value" but if course you meant modifying the
| underlying value [through a cast]. All good.
| PhilipRoman wrote:
| The implementation of "const" in C/C++ is really annoying, in
| the end all you get is a bit of semantic documentation and a
| bunch of compiler errors or casts whenever some library doesn't
| use it. You can often trick the compiler into stronger
| optimizations by making a local variable marked as "const" (in
| which case it is a "true const") and copying the value to/from
| it.
| magicalhippo wrote:
| So this is about warning about using deep reflection or such to
| modify fields marked as final.
|
| Not a Java dev, so I thought it might be related to classes
| marked final somehow. But this seems like a reasonable proposal,
| at least in spirit.
| bironran wrote:
| A cursory glance at "setAccessible" usage reveals popular
| libraries such as serializers like gson and jaxb, class
| manipulation and generation like cglib, aspectj and even
| jdk.internal.reflect, testing frameworks and libraries including
| junit, mockito and other mocking libraries, lombok, groovy,
| spring, and the list goes on and on.
|
| My bet is that this will be yet another "checked exception" or
| "module system", where many applications now need to add "--add-
| opens". If you'll use ANY of many of the more popular frameworks
| or libraries you'll end up giving this assurance away, which will
| make library developers not able to rely on it and we're back to
| square one.
| PathOfEclipse wrote:
| setAccessible is also used to be able to access private fields,
| and not just to be able to write to final fields. Most
| libraries shouldn't need to set final fields, and I say this as
| someone who was very against when they deprecated
| java.lang.misc.Unsafe. I've only had to set a final field once
| in my career and it was related to some obscure MySql/JDBC
| driver bug/workaround. This particular deprecation seems very
| sensible to me.
| eastbound wrote:
| So how should GSON initialize an object?
|
| The theory is, go through the constructor. However, some
| objects are designed to go through several steps before
| reaching the desired state.
|
| If GSON must deserialize {..., state:"CONFIRMED"}, it needs
| to call new Transaction(account1, account2, amount), then
| .setState(STARTED) then .setState(PENDING) then
| .setState(PAID) then .setState(CONFIRMED) ? That's the theory
| of the constructor and mutation methods guarding the state,
| so that it is physically impossible to reach a wrong state.
|
| There is a convention that deserialization is an exception to
| this theory: It should be able to restore the object as-is,
| after for example a transfer over the wire. So it was
| conventionally enabled to set final variables of the object,
| but only at initialization and only for its own good. It was
| assumed that, even though GSON could reach a state that was
| unachievable through normal means, it was, after all, the
| role of the programmer to add the right annotations to avoid
| this.
|
| So how do we do it now?
| vips7L wrote:
| Why would you use GSON for objects that go through steps of
| state? Why would you mark fields like State as final when
| it is actually mutable? This just sounds like poorly
| designed code.
|
| Maybe I don't know of your use case, but GSON/Jackson/Json
| type classes are strictly data that should only represent
| the data coming over the wire. If you need to further
| manipulate that data it sounds like the classes have too
| much responsibility.
| bdangubic wrote:
| all state is immutable :) a change creates new state -
| which is immutable
| vips7L wrote:
| :) no its not.
| bdangubic wrote:
| if you change the state, it is not same state, it is a
| new state
| steveklabnik wrote:
| > So how do we do it now?
|
| The JEP says:
|
| > the developers of serialization libraries should
| serialize and deserialize objects using the
| sun.reflect.ReflectionFactory class, which is supported for
| this purpose. Its deserialization methods can mutate final
| fields even if called from code in modules that are not
| enabled for final field mutation.
|
| I don't know enough about the details here to say if that's
| sufficient, but I imagine that it at least should be, or if
| it's not, it will be improved to the point where it can be.
| cesarb wrote:
| > The JEP says: [...]
|
| The JEP also says:
|
| > The sun.reflect.ReflectionFactory class only supports
| deserialization of objects whose classes implement
| java.io.Serializable.
|
| In my experience, most classes being deserialized by
| libraries like GSON do _not_ implement Serializable.
| Implementing Serializable is mostly done by classes which
| want to be serialized and deserialized through Java 's
| native serialization format (which is used by nothing
| outside Java, unlike cross-platform formats like JSON or
| CBOR).
| twic wrote:
| It strikes me that we could have a way to reflectively
| create an object from values for all its fields in a single
| step - similar to what record constructor does, but for any
| class (could even be Class::getCanonicalConstructor,
| returning a java.lang.reflect.Constructor). It would be
| equivalent to creating an uninitialised instance and then
| setting its fields one by one, but the partly-initialised
| object would never be visible. This should probably be
| restricted, because it bypasses any invariants the
| constructor enforces, but as you say, ultimately
| serialisation libraries do need to do that.
| recursivecaveat wrote:
| I don't know if Java serialization supports this kind of
| thing, but if you have object A has a pointer to object B
| and vice-versa, there's no order to deserialize them
| without passing through a partially-initialized state
| that preserves the object identity relationship. I
| suppose you can't construct this kind of loopy references
| graph with final fields without reflection in the first
| place, so it's kindof chicken and egg. For the very
| common case of DAG-shaped data or formats that don't
| support references I think the one-shot internal
| constructor works though.
| n_plus_1_acc wrote:
| Just do it like Rust
| merb wrote:
| Yeah like the module system. Looks good on paper, is probably
| hard to deal with. There are still tons of popular libraries
| that have no module-info. Java does evolve, but the direction
| it does is so weird. And than the tooling is strange and it's
| worse that there are basically two build tools, both with their
| upsides and downsides but they still feel more complicated than
| tools for other languages like cargo, go (if you consider
| that), msbuild (the modern csproj stuff/slnx)
| pron wrote:
| We've addressed that in the JEP. Serialization libraries have a
| special way to circumvent this (until we get Serialization
| 2.0), and mocking libraries may, indeed, need to set the flag,
| but they're rarely used in production, so if they don't enjoy
| some new optimisation -- no big deal.
|
| BTW, this JEP does not apply to setAccessible generally, as
| that's been restricted since JDK 16, but only to the particular
| (and more rare) use of setAccessible to mutate instance final
| fields. As the JEP says, static final fields, records' internal
| instance fields, and final instance fields of hidden classes
| cannot be mutated with that approach currently, so it's never
| been something that's expected to work in all cases.
| LadyCailin wrote:
| Would be nice to have a single "--test-mode" flag that is
| only meant to be set when running tests, and allows for all
| this leniency, (add opens, etc) in a single flag.
| pron wrote:
| We should separate the problem from the solution. The
| problem is that running tests may require relatively many
| integrity-busting flags. That is true.
|
| There are, however, better solutions than a global test-
| mode flag that, invariably, will be used by some in
| production out of laziness, leaving no auditable record of
| what integrity constraints need to be violated and why.
| When a new team lead is appointed some years later they
| will have a hard time trying to reduce the entropy.
|
| The better solutions will arrive in due course, but until
| then, build tools can automatically add most of the
| necessary flags. They should be encouraged to do that.
| LadyCailin wrote:
| So make the flag remove some other feature, which is
| critical to production, like the ability to run main() or
| something.
|
| On the other hand, I don't think the solution to someone
| holding a shotgun to their foot and threatening to pull
| the trigger is to make everyone wear armored shoes.
| They're already a lost cause, and there are a billion
| other ways they can shoot their foot off, if they are so
| inclined.
|
| I agree with the principal of making it hard to screw
| things up assuming good faith efforts (making it hard to
| fall in the pit of despair), so overall I like the JEP.
| pron wrote:
| > On the other hand, I don't think the solution to
| someone holding a shotgun to their foot and threatening
| to pull the trigger is to make everyone wear armored
| shoes.
|
| I don't think so, either, it's just that I think there
| are better solutions than a test-mode flag _at the level
| of the `java` launcher_. If the mechanism that runs the
| tests can automatically configure the appropriate
| capabilities without requiring the user running the tests
| to do manual configuration then the problem is solved for
| those who just want to easily run tests just as well as a
| test-mode configuration.
|
| The idea of a test-mode flag has been floated before and
| considered; we're not ruling it out, but if such a mode
| is ever added, I can't tell you now what it would mean
| exactly. In any event, it's better to carefully study the
| nature of the problem and its origins before suggesting a
| particular solution. As Brian Goetz likes to say, today's
| solutions may well become tomorrow's problems.
|
| > They're already a lost cause, and there are a billion
| other ways they can shoot their foot off, if they are so
| inclined.
|
| True, but our experience shows that it's not a good idea
| to make the bad choice the easiest one, or people may
| pick it out of laziness. Let those who want to shoot
| themselves in the foot work for it. If nothing else, it
| increases the chance that they learn what their (not-
| entirely-trivial) configuration means, and maybe they'll
| realise they don't want it after all.
|
| Someone might point out that there are still ways to do
| the wrong thing out of laziness by blindingly copying a
| configuration from StackOverflow etc., but we're not done
| yet.
| PaulHoule wrote:
| My impression is that this will be painful for the code I work
| on because the libraries you mention depend on being able to
| modify private and/or final fields.
| xxs wrote:
| private fields are no issue
| keybored wrote:
| I don't see a way to opt-in to a hard error if this happens
| somewhere in the guts of the code (third-party probably).
| steveklabnik wrote:
| > --illegal-final-final-mutation=deny will result in Field::set
| throwing an IllegalAccessException for every illegal final
| field mutation.
|
| Seems like this way?
| aardvark179 wrote:
| So happy to see this, thank you Ron and Alan.
| Traubenfuchs wrote:
| I would like to see some realistic example scenarios where this
| could actually lead to a speedup and how much it actually speeds
| up.
| kelnos wrote:
| The article mentions one: const folding. I don't think we need
| a benchmark to suggest that could mean a performance
| improvement in some cases.
|
| Regardless, to me this isn't about performance, this is about
| "integrity" (to use the same term as in the JEP): consumers of
| my library should not be mucking about in private
| implementation details, and then inevitably complaining about
| problems to me when something breaks. If you need a feature
| that I don't expose, ask for it (or better yet, submit a
| patch).
|
| Sure, I've used reflection to modify library internals before,
| but I recognize that whenever I do that I'm inviting a
| maintenance headache into my world. But some people just think
| things should always work, even when they are breaking them.
| elric wrote:
| I've written my fair share of nasty reflexive code for testing or
| for abusing libraries, but I don't think I've ever overwritten
| final fields in this way. Private fields, sure. But not final.
|
| Sounds like a good evolution to me.
| cogman10 wrote:
| Hmm, not a bad approach.
|
| I think the one thing that'd be nice is if I could somehow tell
| the JVM from a class that this class is open for final mutation
| rather than needing special flags passed into the JVM or special
| manifests in the Jar. It's often pretty clear to me, as a dev,
| what I when I need something to have final mutation (generally
| only with serialization objects).
|
| For example, @FinalMutatableByReflection
| class Foo { final String bar; }
|
| That'd allow me to transition code by just adding an annotation
| where it needs to be while also getting the benefit that final is
| really final everywhere else in code that isn't working with
| serialization.
| nightpool wrote:
| They actually have a very similar proposal in the draft
| already: The sun.reflect.ReflectionFactory
| class only supports deserialization of objects whose classes
| implement java.io.Serializable
|
| That is, you'll still be able to mutate final fields using the
| ReflectionFactory class, as long as that class inherits from
| Serializable
| no_wizard wrote:
| I know this isn't the most relevant but to see the sun
| namespace still exists gives me a small amount of joy
| xxs wrote:
| Back in the '90s I used to consider sun.xxx the cooler
| version of Java.
| owlstuffing wrote:
| The issue is that many essential libraries and tools rely on
| setting internal final fields. I assume that's why the options
| around this have remained open-ended.
|
| The problem with these various "integrity by default" options
| is that, in most cases, granting access to one effectively
| grants access to all. For instance, JNI, agent libraries, and
| JPMS options can each be used to bypass restrictions, making
| the separation between them largely illusory. Integrity, as
| framed here, is ultimately binary.
|
| The unfortunate reality of the "integrity by default" crusade
| is that applications relying on libraries and tools that modify
| internals will continue to do so. The JDK hasn't filled any
| gaps--it has only made an already delicate situation worse.
| twic wrote:
| Not in my experience. I run my applications with default
| integrity, and when i hit a problem, i find out what caused
| it, and fix it. Often it's just a matter of upgrading a
| library to a newer version which doesn't do naughty
| reflective things, or changing some config to stop it doing
| so, or changing our own code. We had a serialisation library
| which did deep reflection to be slightly more efficient at
| serialising BigDecimal; there was a system property to turn
| that off, so i set it. We had code doing deep reflection into
| JMX to get the current PID; i changed it to use ProcessHandle
| instead. My colleague wrote a little test data library which
| does some horrific things; i might delete it and adopt
| Instacio instead.
|
| I think there's only one case where i ended up relaxing
| integrity, and i'm hoping that's temporary - it will take
| more time to fix than i was willing to spend.
| pron wrote:
| First, it's not a "crusade" but the steps necessary to
| deliver the features Java's users demand. Second, the
| prevalence of the use of JDK internals has dropped
| drastically, and demonstrably so. For example, many programs
| broke before internals were encapsulated during the upgrade
| from 8 to 9; 99% of the causes were libraries relying on
| internals, which had changed. Access to internals was closed
| off in JDK 16, although, as you say, it can be selectively
| allowed. And yet, between JDK 17 and JDK 23, changes of
| similar magnitude to the JDK internals caused nearly no
| upgrade problems. Upgrading the JDK now is smoother and
| easier than it's been in the last two decades. Why? Because
| there's been a large reduction in libraries' access to
| internals.
|
| I think Java's handling of this transition compares very
| favourably to how other languages have handled similar
| transitions from some old model to a new one (or evolution in
| general) in terms of balancing the needs of both old and new
| projects.
| cogman10 wrote:
| I can attest to how easy it's become to update jdks for our
| org.
|
| 8->11 was really a pain in the neck. 11->17 had some pain,
| but mostly was nothing serious.
|
| 17->21 has been painless.
|
| And I have some projects running on 24 already with no
| problems.
|
| The feature delivery has been great and we are getting
| pretty close to not needing to do anything but update the
| jdk to move forward.
|
| Now, if only I could get devs to stop using lombok....
| sgammon wrote:
| > Now, if only I could get devs to stop using lombok
|
| One of the only remaining reasons I discard libraries
| without a further thought
| pandemic_region wrote:
| Why the Lombok hate? It's just an innocent preprocessor
| allowing a bit of syntactic sugar right?
| pests wrote:
| It feels weird to me that functions exist without being
| in the source code.
| pandemic_region wrote:
| I never looked at it that way, but I get now where you're
| coming from. To me, the Lombok annotations are more like
| @Transactional, providing some useful boilerplate that I
| don't need to care about, it just works.
| pests wrote:
| I agree its extremely useful. I am just used to
| languages/frameworks that at least autogenerate the code.
| Maybe some inheritance to give you a place to make
| changes that wont be wiped.
| dingi wrote:
| It's not an innocent preprocessor. It's a hack which
| breaks on almost every JDK upgrade.
| mschuster91 wrote:
| Now, given that a looot of projects use Lombok, it might
| be worth a try to actually _standardize_ (or at least
| upstream) the feature given there seems to be an obvious
| demand of people to not having to pollute their codebase
| with tons of pointless getter and setter boilerplate.
| cogman10 wrote:
| AutoValue, Immutables, MapStruct. All libraries that do
| what Lombok does without using JVM internals.
|
| Records also literally do a huge portion of what Lombok
| does.
|
| There are a lot of fairly popular alternatives to Lombok
| out there that don't need a constant eye on maintenance.
| Lombok is probably the most popular but that's really
| mostly because it's got the first mover advantage.
| JanecekPetr wrote:
| - It doesn't work with ErrorProne
| (https://github.com/google/error-
| prone/issues?q=sort%3Aupdate...) - you need a special
| plugin in your IDE for the generated classes to be
| visible and/or the code to be parsable - it does not work
| with many other tools, usually there is a bridge to fix
| that, e.g. with MapStruct:
| https://mapstruct.org/faq/#Can-I-use-MapStruct-together-
| with...
|
| Other tools and libraries generally do not interact in
| such an errorprone manner.
|
| That said, when you know how it works, what it needs, and
| you know how to iron all of those tiny wrinkles, it works
| fine and saves you some code and/or sanity. It's not the
| devil, it's a powerful tool with some downsides.
| pandemic_region wrote:
| I did not know about ErrorProne thanks, I can see this is
| useful indeed and then Lombok would be the odd one out
| breaking that tool. About the special IDE plugin,
| IntelliJ has native support but yeah it needs a bit of
| attention that's true. Overall the minor extra hassle is
| still worth it in my world. We're not using all of the
| annotations, but (in order of utility)
| @AllArgsConstructor, @Data, @Getter, @Setter and the
| occasional @Synchronized.
| dwattttt wrote:
| http://thecodelesscode.com/case/97
|
| There's one of these for just about every occasion
| gf000 wrote:
| The biggest gripe is that it is _not_ a preprocessor.
| Java has standard interfaces to properly preprocess code,
| and they have a very thoughtful limitation of _no code
| modification_ , it is strictly additive.
|
| There are very cool libraries making use of it, e.g.
| mapstruct.
|
| Now, lombok in its current approach can't make use of it,
| as it explicitly wants to modify the given class with its
| annotations. This is forbidden, so they literally hack
| into the java compiler classes themselves, modifying the
| AST in-flight. As you can imagine, this might break at a
| new Java update any time.
| cogman10 wrote:
| > As you can imagine, this might break at a new Java
| update any time.
|
| It's not a might. If you look at the lombok changelog
| they have a release for nearly every new jdk version
| because they break constantly.
|
| When a project can't move up jvm versions it's very
| frequently been because of lombok.
|
| And if you look at the commit log, it's all a single dev
| running the show. He's been doing it for years which
| means the stability of your project is pretty dependant
| on this one guy keeping things up to date and finding
| work arounds to get at the jvm internals.
| pests wrote:
| Been a long time since I used Java. I checked out lombok
| since you mentioned it. Does it really just internally
| create all those methods without there being actual
| source code? It seems nice but really spooky-action-at-a-
| distance feeling. Sounds like a nightmare.
| elchananHaas wrote:
| Yes, from a language design it's ugly and the
| implementation is convoluted. From a user perspective
| it's awesome and enables better interfaces.
| pests wrote:
| I don't even agree with their demo video. It shows the
| "hard" way of autogenerating settings/getters, toString,
| hash, etc. The end result was like an additional ~80
| boilerplate lines. I have no problem keeping those lines
| around.. opposed to adding the lombak.jar and changing my
| build config. I do understand the user perspective of it
| "just working" and of course the getters/setters will
| grow as you add fields... it just seems like such a low
| amount of code to keep around it shouldnt be too much
| hassle.
| vbezhenar wrote:
| It's not 80 lines. It's many thousands of lines in any
| real project. Lines that you should keep synchronised
| with "source of truth". People love Lombok for a reason.
| And most developers don't really care about
| implementation details, they believe that Lombok is "big
| enough" to work in the foreseeable future. It worked for
| them for years, it's supported well in Idea.
|
| I, personally, avoid Lombok, specifically because I care
| about implementation details. Because if I would have
| wanted better Java, I'd go with Kotlin rather than this
| hacky way of using another Java-like language. But other
| people hold different opinions.
| cogman10 wrote:
| There are alternatives that don't rely on jvm internals
| to accomplish their goals. They do like 90% of what
| lombok does. Immutables and AutoValue are two examples
| I've used that work fine.
|
| The 10% lombok can do because it's peaking at internals
| isn't valuable. I don't, for example, need an annotation
| to create a static logger. That's dumb.
| ivan_gammel wrote:
| For me personally those thousands of lines are often
| garbage. For example, constructors and fields generated
| by Lombok do not validate inputs, yet failing early
| instead of deferring the problem to client code reading
| the value is the easiest way to fight bugs. Sometimes one
| business method setting several values instead of
| multiple getters is better or the only possible way to
| preserve integrity of the state. Sometimes a field
| reflecting internal state will be annotated to produce
| getter and thus breaking the encapsulation. With all of
| that Lombok encourages poor code quality by enabling
| developers being lazy.
| xxs wrote:
| >It's many thousands of lines in any real project.
|
| I dont use lombok and the latter has been actively
| removed. If you have access to records, lombok is just
| bad. Even if you dont have - public final fields are
| sufficient in most case + c-tor and validation there.
| Just dont use getters & setters.
| lucumo wrote:
| The problem isn't the first-time generating the code. The
| problem is when objects gain fields and people forget to
| add them to hashCode and equals (or worse, hashCode OR
| equals). It's the kind of thing you won't notice until
| months later when you have intermittent hard to debug
| glitches in your system.
|
| Records have reduced the advantages of Lombok by a
| boatload. But there still are some things that can't be
| records.
| xxs wrote:
| >add them to hashCode and equals (or worse, hashCode OR
| equals)
|
| That's a fundamental misunderstanding of hashCode, and
| lombok makes no exception. Not all fields need to be used
| to calculate hashCode, it makes the overall performance
| worse in most cases. Equals is of course different,
| however if you have an identity (i.e. database primary
| key), only the identity should be used.
| cogman10 wrote:
| Hurray! I thought I was the only one that understood
| this. There are two of us!
|
| I've seen so many performance issues with hashcode
| because devs will put all fields into it. Even though
| there's an id column or even fields that imply other
| fields. Hashing 1000 char strings when there's a UUID or
| int field that guarantees identity is silly.
|
| I think it's because devs have an preference for
| symmetry. I see the same thing happen when they
| preferably add setters for all fields even though they
| aren't necessary.
| xxs wrote:
| >There are two of us!
|
| Realistically, I have trained quite a few folks on my
| own.
|
| >I think it's because devs have an preference for
| symmetry.
|
| Another option is that's the default for all IDEs auto
| gen, so few clicks/taps and it's done.
| ivan_gammel wrote:
| In Idea you have to explicitly select the fields which
| should be included. So it's always choice of a developer
| to misuse it.
|
| My favorite question on interviews is explaining all
| methods of class Object, including the contract and best
| practices for equals/hashCode. Failure to answer this
| question automatically disqualifies applicants to mid-
| level and senior positions.
| xxs wrote:
| >all methods of class Object finalize()
| is actually is a very hard mode; I'd not expect any extra
| senior to be able to explain it properly (incl. the
| semantics of JMM, the fact half created objects can be
| finalized; the resurrection ability). Deprecated now, so
| perhaps no need? wait/notify/notifyAll - easier,
| still require some practice, also not that useful any
| longer; but still I'd expect to know not to use a naked
| notify and how to properly use a loop around wait
| clone() - it'd be a hard nut for many, and I have seen
| more than enough implementations that straight out use
| new XXX(); not very difficult but not intuitive
| hashCode/equals -> hashCode being by default a random
| number generator is sort of cool; yes they are the
| backbone of all collection framework; also the value of
| the not overridden hashCode() is available through
| System.identifyHashCode() getClass() - if included
| anonymous classes, it might puzzle some toString()
| - finally something easy
|
| ---
|
| flip note: the standard templates for intellij could use
| some work when it comes to the quality of hashCode;
| ivan_gammel wrote:
| > finalize() is actually is a very hard mode;
|
| > Deprecated now, so perhaps no need?
|
| Yes. Worth mentioning existence and "do not touch it",
| but no need to go deep. Same with clone. The point of
| this exercise is to demonstrate that you can use the core
| library without shooting yourself in the foot. As for
| wait/notify/notifyAll, I'd expect the correct usage
| patterns from mid-level.
| xxs wrote:
| >"just working" and of course the getters/setters will
| grow as you add fields...
|
| The issue is having plainly useless getters and setter
| (just use public fields); along with the fact that
| mutable hashCode/equals
| saghm wrote:
| I mean, we're talking about how the best way to
| externally modify things that are ostensibly supposed to
| be internal and immutable, right? I feel like pretty much
| any way of doing it is going to be spooky action at a
| distance by definition. Modifying private internals from
| the outside isn't something you can do with mundane-
| action-at-close-range or whatever the alternative would
| otherwise be called.
| throwaway92422 wrote:
| I like Lombok. The best annotation is @Builder. It makes
| working with records much more convenient.
| JanecekPetr wrote:
| https://github.com/Randgalt/record-builder
|
| The upsides are: - it generates code and does not do
| anything funky with internals, - it has a lot of knobs if
| you need something a little different.
|
| The downside is that it does not provide you with the
| other Lombok annotations. In practice that has been OK!
| cogman10 wrote:
| Besides this I'd also point to Immutables and AutoValue
| which both can make builders without touching jvm
| internals.
| xxs wrote:
| >Now, if only I could get devs to stop using lombok....
|
| that's actually not too hard. It has happened to get them
| all converted to avid opponents of lombok.
| sgammon wrote:
| It's 2025 and Guava supports JPMS now
| Phelinofist wrote:
| Android doesn't :(
| sgammon wrote:
| One could easily generate the configurations via annotations,
| and I imagine Micronaut will do just that
| nightpool wrote:
| [Speculative optimizations] may not suffice in this case as
| future planned optimizations may wish to rely not only on
| immutability within the lifetime of the process, but also on the
| immutability of fields from one run of the application to the
| next.
|
| Can someone elaborate a little more on what this means? I'm very
| surprised to hear that this was considered a blocker important
| enough to add all of this complicated machinery (and breaking
| several deserialization libraries...), when I've never even heard
| of such an optimization and can't imagine what sort of form it
| would take
| Misdicorl wrote:
| I suppose serializing the JVM state itself to avoid the cold
| start problem might take advantage of this?
| nightpool wrote:
| Why would that prevent the JVM from using the same
| speculative optimization JIT with deoptimization hooks
| approach?
| Misdicorl wrote:
| It wouldn't, but it might preclude using (future)
| optimizations that forgo those de-optimization hooks?
| pron wrote:
| There's ongoing work, as part of Project Leyden, to cache
| certain computations -- either performed by the user code or
| the JVM -- from one run of the program to the next, including
| the caching of JIT-compiled machine code. The early parts of
| this work were discussed here:
| https://www.morling.dev/blog/jep-483-aot-class-loading-linki...
| xxs wrote:
| This is rather old[0] but still relevant.
|
| _Surprise #2! A popular open-source framework writes to final
| fields after object construction via generated bytecodes!...
| This optimization of the final field is *the* main optimization
| performed on final fields._
|
| [0]
| https://web.archive.org/web/20121016082428/http://www.azulsy...
| TOGoS wrote:
| > Application developers can avoid both current warnings and
| future restrictions by selectively enabling the ability to mutate
| final fields where essential.
|
| /me raises hand
|
| Maybe if you want to mutate a field, don't mark it `final`?
|
| I know, I know, people like to pretend things are one way and
| then hand their objects over to some horrid framework that breaks
| all the rules, because apparently giant web of mutable spaghetti
| is _just fine, not an anti-pattern at all_ if you let some third-
| party bull$#!7 ORM /dependency-injection-framework-for-people-
| who-don't-like-constructors do it.
| neonsunset wrote:
| .NET went through a similar change, blocking `static readonly`
| fields from being accessible via private reflection.
| Unfortunately, a lot of serializers and all sorts of meta-
| programming libraries depend on (mutable) private reflection of
| instance fields still so for now they are not blocked and JIT
| cannot treat them as truly immutable, turning into JIT constants
| the way it does so for static readonly fields. Although I guess
| you can always make a struct and place it into a static readonly,
| where each field could be such JIT constant (within certain
| limits).
| GauntletWizard wrote:
| When I had the brief displeasure of working on HDFS at Facebook,
| we took a series of customer meetings to figure out how to get
| our oldest customers to upgrade their clusters. I was in a
| meeting with the photos team about what their requirements were
| and what was blocking them from upgrading, and they were very
| frank - they asked if the upgrade preserved the internal struct
| types associated with blocks on the disc servers. They didn't
| actually use hdfs as a file system, they allocated 1 GB files
| with zero replication, then used deep reflection to find the
| extent that comprised them on the discful storage servers, then
| built their own archival backup file system on top of that. I was
| horrified. The some of the older hats on the team were less
| surprised, having had some inkling of what was going on, even
| though they clearly didn't understand the details. Others
| considered it tantamount to sacrilege.
|
| I think about this a lot. What they had built was probably
| actually the best distributed file system within Facebook. It was
| similarly structured to unraid, and had good availability,
| durability, and space saving properties, but the approach to
| engineering was just so wrong headed in my opinion that I
| couldn't stomach it. Talking about it with other Java programmers
| within facebook, nobody seemed to mind. Final was just a hint
| after all.
| adrianmonk wrote:
| That reminds me of a quote from some Perl documentation[1]:
|
| > _Perl does not enforce private and public parts of its
| modules as you may have been used to in other languages like
| C++, Ada, or Modula-17. Perl doesn 't have an infatuation with
| enforced privacy. It would prefer that you stayed out of its
| living room because you weren't invited, not because it has a
| shotgun._
|
| It's not exactly the same situation, but the point is, at the
| end of the day, you need to be able to rely on the people
| involved being willing to act reasonable. If you can't, then
| you're going to have problems.
|
| ---
|
| [1] https://perldoc.perl.org/perlmodlib
| dapperdrake wrote:
| This approach is surprisingly (or unsurprisingly) one of the
| most robust.
| exabrial wrote:
| I'm 100% onboard with this. My thought was how are they going to
| make Serialization work, but looks like they thought of that.
|
| I was trying to think of an edge case with JsonB or JAXB that
| would be affected by this... but generally those frameworks have
| told you for quite awhile not to do stupid stuff like:
|
| ``` @Getter public class HelloMessage { @JsonbProperty private
| final String helloMessage; } ```
|
| I can't think of any frameworks offhand that do this.
| hyperpape wrote:
| Brian Goetz, chief architect of Java, once posted a "what they
| think I do" vs. "what I actually" do tweet. If I remember
| correctly, 25% - 50% of the "what I actually do" category was
| something like "get angry at serialization."
|
| So I think it's safe to say "what about serialization?" is
| always going to be asked.
| exabrial wrote:
| Serialization is unfortunately important though... want to
| suspend a program and resume it later, or transfer it over a
| network, etc. The real world is kinda a let down.
| esprehn wrote:
| That's a hydration problem though, it doesn't require
| magically serializing the running state like java
| Serializable wants to. It just needs a way to produce
| minimal inputs to then reconstitute the state again on the
| other side.
| dstine wrote:
| In this 2014 talk, Brian shows a slide in which he
| characterizes a visible fraction of his job as "regretting
| serialization" (somewhat tongue-in-cheek).
|
| https://www.youtube.com/watch?v=2y5Pv4yN0b0&t=930s
|
| Link is to the start of a sequence of three slides, the third
| of which is the slide in question.
|
| For a more recent update on serialization, watch this talk
| "Serialization: A New Hope":
| https://www.youtube.com/watch?v=mIbA2ymCWDs
| xxs wrote:
| Around 19 year late, still better than never.
| ars wrote:
| Does this mean I should start marking my variables (and function
| parameters) with Final?
|
| Up till now I always assumed the compiler would figure out on its
| own which variables were final, and optimize as needed. But this
| JEP makes it seem like there are optimizations that only happen
| if you manually mark the variable.
| pron wrote:
| No, this JEP only talks about _fields_ ; it has no impact
| whatsoever on locals. There's no positive or negative impact on
| performance when making locals final or not.
| mberning wrote:
| If they implement this in a way similar to the package visibility
| changes your list of JVM args is about to explode in order to
| support legacy apps.
| user wrote:
| This comment is non-sensical.
| theanonymousone wrote:
| If there was a r/nottheonion for tech :D
|
| Jokes aside, I thought the ability to mutate final fields was
| already removed/restricted after Java 17 :/
| satyanash wrote:
| > _Prepare to make final mean final_
|
| missed opportunity to call it "final final"
___________________________________________________________________
(page generated 2025-04-01 23:02 UTC)