14 ms·
JEP draft: Prepare to make final mean final
- Almondsetat 2y agofinal ly
- stickfigure 2y agoGreat! 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 2y agoConst-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 2y agoYeah 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 2y ago> 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 2y agoThe 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 2y ago> 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 2y ago> 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 2y agoExactly. 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 2y agoFair point. I guess this is the cost of dynamic languages.
- titzer 2y agoIf 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 2y ago> 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.
- pjmlp 2y agoD goes one step further, as const is transitive.
- jstimpfle 2y agoconst is weird and I see no way of getting it right. Making const transitive in the way described in tha Dlang reference certainly sounds weird. It prevents totally valid use cases. Why wouldn't I have a const pointer to a mutable thing? The real problem is that it's not defined what is an object, and where, in a web of memory-objects pointing to each other, a conceptual object begins and ends. It's arbitrary. Also, what is const from one standpoint is not const from another. It's fluid. Type systems generally don't play well with that. The weirdness of the original `strstr()` C API showcases that problem well. The only thing that makes some sense here is to keep it simple -- any struct-like thing is an individual object. Embedded objects of course inherit const-ness, but pointed-to objects do not. I think the C++ model is also somewhat right -- any class can define individually through which indirections const-ness follows through. But the C++ way causes lots and lots of boilerplate. In any case, my solution is to use const very sparingly, it can hardly be gotten right in practice beyond basic use. Being more rigid of const may have prevents 2-3 bugs in my life, but cost sooo much more time and required many rewrites to make it fit somehow. I use const mostly for - static const data - some string-slice datatypes (these are often initialized from static const data like string literals). - read-only function parameters passed by reference (Foo const *thing).
- pjmlp 2y agoHence why D has two approaches with similar semantics, const and immutable. And also allows to alias non const references to the same data. Thus the data can be modified by a mutable reference, owned by a specific part of the code, while everyone else only gets to see the const chain.
- duskwuff 2y agoWhat 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 2y ago>but please come up with something to discourage mutability) Records?
- throwaway92422 2y agoRecords in Java have final fields.
- nirvdrum 2y agoI'd be happy with a `val` keyword. I thought that was a seriously missed opportunity when they introduced `var`.
- jjmarr 2y agoFrom 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 2y ago> 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 2y ago(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 2y agoRight, 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.
- magicalhippo 2y agoSo 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 2y agoA 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 2y agosetAccessible 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 2y agoSo 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?
- keybored 2y agoI 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 2y ago> --illegal-final-final-mutation=deny will result in Field::set throwing an IllegalAccessException for every illegal final field mutation. Seems like this way?
- aardvark179 2y agoSo happy to see this, thank you Ron and Alan.
- Traubenfuchs 2y agoI would like to see some realistic example scenarios where this could actually lead to a speedup and how much it actually speeds up.
- kelnos 2y agoThe 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 2y agoI'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 2y agoHmm, 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.
- deleted 2y ago[deleted]
- nightpool 2y agoThey 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
- owlstuffing 2y agoThe 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.
- nightpool 2y ago[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 2y agoI suppose serializing the JVM state itself to avoid the cold start problem might take advantage of this?
- pron 2y agoThere'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-linking/ https://www.morling.dev/blog/jep-483-aot-class-loading-linki...
- xxs 2y agoThis 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.azulsystems.com/blog/cliff/2011-10-17-writing-to-final-fields-via-reflection https://web.archive.org/web/20121016082428/http://www.azulsy...
- TOGoS 2y ago> 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 2y ago.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 2y agoWhen 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 2y agoThat 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 https://perldoc.perl.org/perlmodlib
- dapperdrake 2y agoThis approach is surprisingly (or unsurprisingly) one of the most robust.
- exabrial 2y agoI'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 2y agoBrian 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 2y agoSerialization 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 2y agoThat'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 2y agoIn 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 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 https://www.youtube.com/watch?v=mIbA2ymCWDs
- xxs 2y agoAround 19 year late, still better than never.
- ars 2y agoDoes 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 2y agoNo, 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 2y agoIf 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 2y agoThis comment is non-sensical.
- satyanash 2y ago> Prepare to make final mean final missed opportunity to call it "final final"
- theanonymousone 2y agoIf 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 :/
- immibis 2y agoI don't see the point in putting another guard rail behind the override that's supposed to remove all guard rails. Java doesn't even have a security model any more, so what are you protecting? Programmers from their own mistakes? That's fine, but this is about cases where they set the "I really mean it and this isn't a mistake" flag. The JVM's optimized code from programmers? Plausible, but aren't there already many cases where things get deoptimized based on run-time state changes? It feels like someone just said "final means final!" without really thinking about the purpose of a JVM. Will there be a proposal to enforce checked exceptions, too?
- samus 2y ago> Java doesn't even have a security model any more, so what are you protecting? That security model was obsolete and didn't fulfill its original purpose anymore. Most applications have never enabled it. > Programmers from their own mistakes? That's fine, but this is about cases where they set the "I really mean it and this isn't a mistake" flag. Not at all, the vast majority of code using this feature is part of libraries. Application developers are often not aware that this even happens. Anyway, it will also in the future be possible for developers to shoot themselves in the foot with this feature if they really want. They just have to work a bit harder for it. > The JVM's optimized code from programmers? Plausible, but aren't there already many cases where things get deoptimized based on run-time state changes? This is not a good thing because interpreted mode is terribly slow. The JIT exists to make Java fast, and developers should not made this harder than necessary. > It feels like someone just said "final means final!" without really thinking about the purpose of a JVM. Will there be a proposal to enforce checked exceptions, too? Do people really write `final` and are OK being aware that the field's value could still change after all? Checked exceptions are already enforced. If you mean changing all exceptions to be checked: no, that would be a catastrophic backwards compatibility break, with no good upside.
- immibis 2y ago> Do people really write `final` and are OK being aware that the field's value could still change after all? The converse: people really write `field.setAccessible(true)` and are OK being aware that they are breaking another programmer's assumptions. That's the point of `field.setAccessible(true)` existing at all. It's monkey-patching. Otherwise you might as well just delete the method and throw a `NoSuchMethodError` when someone calls it - nobody is calling `setAccessible(false)` I have some experience hacking on Java Minecraft. Minecraft's code is what it is - take it or leave it. The more stuff you can do without modifying that code, the simpler the overall system is. If you can set a field with reflection then you don't need to modify that class to make the field public or non-final. In other words, monkey-patching. (Newer frameworks support more modification to Minecraft's own code with less effort, though.) > This is not a good thing because interpreted mode is terribly slow. The JIT exists to make Java fast, and developers should not made this harder than necessary. And it does that by being smart. It makes assumptions, compiles code given those assumptions, and recompiles it if they actually do not hold. For example, I expect that a method with a List parameter is most likely only ever called with one implementation of List, and the JVM knows this and will compile the method as if the parameter is ArrayList, and will compile the method for the specific implementation it sees in the first few calls, with a bail-out check forcing the method to be recompiled if it's ever not that implementation. And if the JVM never sees a subclass of a certain class, it can statically dispatch all calls to references of that type, as if the class were final, even if it isn't marked final. And, likewise, if the JVM never sees a write instruction to a certain field, except once in the constructor, it should be able to treat it as if the field were final, and avoid reloading it after method calls. If it loads a class with an instruction that writes that field, it has to recompile all those methods. None of this should be news to the JVM engineers.