Adding an @Immutable annotation to Java
Alan Snyder
javalists at cbfiddle.com
Thu Nov 25 23:11:20 UTC 2021
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.goers at 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.hoversholm at paf.com> wrote:
>>
>> On Thu, 25 Nov 2021 at 10:03, Andrew Haley <aph-open at 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>
>>
>
>
More information about the core-libs-dev
mailing list