[HN Gopher] JEP Draft: Prepare to Make Final Mean Final
___________________________________________________________________
JEP Draft: Prepare to Make Final Mean Final
Author : mfiguiere
Score : 115 points
Date : 2025-03-31 19:35 UTC (3 hours 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?
| 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.
| 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.
| 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.
| 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.
| 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
| 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.
| 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.
| 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.
___________________________________________________________________
(page generated 2025-03-31 23:00 UTC)