Adding an @Immutable annotation to Java
Rob Spoor
openjdk at icemanx.nl
Fri Nov 26 15:12:29 UTC 2021
If this is really something that's desired, then why use an annotation
when there's a keyword already available: const. However, there's a
reason that's never been actually used in Java, and that's because it's
so hard to get right.
On 26/11/2021 00:11, Alan Snyder wrote:
> 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