I like the idea of an @Immutable annotation. So much so, that I have created one and use it heavily. However, my @Immutable is just for documentation; it does not have a rigorous semantics that would be needed for a compiler to validate its use or generate different code. But it could be used by lint-like tools to warn against suspicious code.
My @Immutable applies only to classes and interfaces, not variables or type parameters. A class or interface is either @Immutable or it is not. That avoids much complexity. I think it is fine to have an @Immutable list (type) whose elements are mutable. Is an instance of an immutable list with mutable elements immutable or not? Tell me why you want to know and I might be able to answer the question. I also think there are cases where an @Immutable type might have a mutable implementation (in other words, it might have instance variables that are not final or have non-immutable types). Examples include internal caching and configurability of performance or incidental behavior such as logging. In general, immutability is in the mind of the beholder. A case can be made that an interface or class that is @Immutable should only permit extensions and implementing classes that are @Immutable. Perhaps a compiler could validate that, but only at compilation time (to ensure binary compatibility when an @Immutable annotation is added to an existing class or interface). The idea of the compiler trying to enforce a semantics of immutability is scary. Java tried to do this with uninitialized and final instance variables, and the result has been a disaster. The rules make semantically valid code illegal, forcing complex workarounds. (For example, suppose you want to compute a value, bind it to a final instance variable, and pass it to the superclass constructor.) Yet it is still possible, actually quite easy, to write code that accesses uninitialized final variables. I have code that checks a final @NotNull instance variable to ensure that it is not null. IDEA tells me the check is unnecessary, but IDEA is wrong. Immutability is a much more complicated concept than uninitialized. Not something the compiler should mess with. Alan > On Nov 25, 2021, at 10:10 AM, Ralph Goers <ralph.go...@dslextreme.com> wrote: > > I would think that if a class is marked immutable that would imply all the > fields in it and from its inherited > classes should be immutable. If fields are marked immutable then it would > only apply to them. > > What I wonder is what its relationship to final would be. The final > annotation implies that a field must be > set in the constructor and cannot be modified after that. I would imagine > that @immutable to have to imply > @final but would also apply at runtime. For example, where declaring a List > field as final means you cannot > replace the List once it is set. I would expect @Immutable to do the same but > also mean that you cannot > add elements to the List through that reference. But that would also mean > that you cannot pass the reference > to another variable that isn’t also annotated with @Immutable - unless the > immutable attribute becomes some > kind of internal flag on the object. > > To be clear, this concept is always something I have wanted in Java. It is a > real pain to have to do things like > List<String> list = Collections.unmodifiableList(List.of(“foo”, “bar”)); > > Instead, it would be nice to be able to do > @Immutable List<?> list = List.of(“foo”, “bar”); > > Although the two could be implemented to do the same thing, the second could > prevent passing the field in a > parameter that wasn’t declared @Immutable. Likewise, passing a non-immutable > list in a parameter annotated > with @Immutable could cause the list to be copied to an immutable list > automatically. > > Ralph > >> On Nov 25, 2021, at 2:49 AM, Mariell Hoversholm <mariell.hoversh...@paf.com> >> wrote: >> >> On Thu, 25 Nov 2021 at 10:03, Andrew Haley <aph-o...@littlepinkcloud.com> >> wrote: >> >>>> Quick question, out of curiosity: how would it behave with respect to >>>> inheritance? Can a @Immutable class inherit from an non immutable one? >>> >>> And: does @Immutable mean deeply immutable? IMO it really should, >>> but that's harder to check, and we'd have to think about what this >>> means for binary compatibility. >>> >> >> As cited in the original email, >>> and the programmer could, for example, annotate a new record object with >> @Immutable only if all its fields are annotated with @Immutable. >> >> I would infer from this that it would mean deeply immutable. >> To clarify further, the following record `Wrapper` would be legal only >> because `A` has `@Immutable` on its _type_: >> >> @Immutable class A {} >> @Immutable record Wrapper(A a) {} >> >> while this would not be legal: >> >> class A {} >> @Immutable record Wrapper(A a) {} >> >> because `A` is not `@Immutable`. This could however borrow from the Rust >> concept of "auto-traits" (read: automatically apply certain traits or >> capabilities depending on how the type is defined), and infer `@Immutable` >> if it is deeply immutable, but that would raise the question of API & >> binary compatibility again. >> >> It would also not be legal with this: >> >> @Immutable >> class A { >> private int value; >> public void value(int value) { this.value = value; } >> } >> >> because `value` is _not_ immutable. >> >> Moving on from your question, this then poses the question of memoizing >> potentially very expensive optimisations, such as the following, currently >> found in the JDK: >> https://github.com/openjdk/jdk/blob/f0136ec94539d0e30ec11d44f8143196da1f7125/src/java.base/share/classes/java/lang/String.java#L2323-L2343 >> Code above from `java.base/java.lang.String`: >> >> public int hashCode() { >> // [snip large comment] >> int h = hash; // [hash is a property on the type] >> if (h == 0 && !hashIsZero) { // [hashIsZero is a property on the >> type] >> h = isLatin1() ? StringLatin1.hashCode(value) >> : StringUTF16.hashCode(value); >> if (h == 0) { >> hashIsZero = true; >> } else { >> hash = h; >> } >> } >> return h; >> } >> >> If the compiler / JRE were to infer the immutability from setters, it would >> again pose a new question of how to detect such. It could be possible that >> adding a new keyword or annotation to ignore mutable fields would be >> necessary (say `private mutable int hashCode;` as an example of my >> thoughts). At that point, however, what is the difference to not having >> this feature at all? >> >> This is definitely an idea I like as a user of the language, but it's >> something that would require a bit of work, assuming it can be boiled down >> to something feasible at all. >> >> -- >> >> *Mariell Hoversholm *(she/her) >> >> Software Developer @ <https://aboutpaf.com> >> > >