Proposal: Collection mutability marker interfaces

Remi Forax forax at univ-mlv.fr
Wed Aug 24 14:03:19 UTC 2022


> From: "Ethan McCue" <ethan at mccue.dev>
> To: "John Hendrikx" <john.hendrikx at gmail.com>
> Cc: "core-libs-dev" <core-libs-dev at openjdk.org>
> Sent: Wednesday, August 24, 2022 3:38:26 PM
> Subject: Re: Proposal: Collection mutability marker interfaces

> A use case that doesn't cover is adding to a collection.

> Say as part of a method's contract you state that you take ownership of a List.
> You aren't going to copy even if the list is mutable.

> Later on, you may want to add to the list. Add is supported on ArrayList so you
> don't need to copy and replace your reference, but you would if the list you
> were given was made with List.of or Arrays.asList

You can ask if the spliterator considers the collection as immutable or not, 
list.spliterator().hasCharacteristics(Spliterator.IMMUTABLE) 

sadly, List.of()/Map.of() does not report the spliterator characteristics correctly (the spliterator implementations are inherited from AbstracList/AbstractMap). 

so it's perhaps better to call add() inside a try/catch on UnsupportedOperationException. 

Rémi 

> On Wed, Aug 24, 2022, 8:13 AM John Hendrikx < [ mailto:john.hendrikx at gmail.com |
> john.hendrikx at gmail.com ] > wrote:

>> Would it be an option to not make the receiver responsible for the decision
>> whether to make a copy or not? Instead put this burden (using default methods)
>> on the various collections?

>> If List/Set/Map had a method like this:

>> List<T> immutableCopy(); // returns a (shallow) immutable copy if list is
>> mutable (basically always copies, unless proven otherwise)

>> Paired with methods on Collections to prevent collections from being modified:

>> Collections.immutableList(List<T>)

>> This wrapper is similar to `unmodifiableList` except it implements
>> `immutableCopy` as `return this`.

>> Then for the various scenario's, where `x` is an untrusted source of List with
>> unknown status:

>> // Create a defensive copy; result is a private list that cannot be modified:

>> List<T> y = x.immutableCopy();

>> // Create a defensive copy for sharing, promising it won't ever change:

>> List<T> y = Collections.immutableList(x.immutableCopy());

>> // Create a defensive copy for mutating:

>> List<T> y = new ArrayList<>(x); // same as always

>> // Create a mutable copy, modify it, then expose as immutable:

>> List<T> y = new ArrayList<>(x); // same as always

>> y.add( <some element> );

>> List<T> z = Collections.immutableList(y);

>> y = null; // we promise `z` won't change again by clearing the only path to
>> mutating it!

>> The advantage would be that this information isn't part of the type system where
>> it can easily get lost. The actual implementation knows best whether a copy
>> must be made or not.

>> Of course, the immutableList wrapper can be used incorrectly and the promise
>> here can be broken by keeping a reference to the original (mutable) list, but I
>> think that's an acceptable trade-off.

>> --John

>> PS. Chosen names are just for illustration; there is some discussion as what
>> "unmodifiable" vs "immutable" means in the context of collections that may
>> contain elements that are mutable. In this post, immutable refers to shallow
>> immutability .
>> On 24/08/2022 03:24, Ethan McCue wrote:

>>> Ah, I'm an idiot.

>>> There is still a proposal here somewhere...maybe. right now non jdk lists can't
>>> participate in the special casing?

>>> On Tue, Aug 23, 2022, 9:00 PM Paul Sandoz < [ mailto:paul.sandoz at oracle.com |
>>> paul.sandoz at oracle.com ] > wrote:

>>>> List.copyOf already does what you want.

>>>> [
>>>> https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/List.java#L1068
>>>> |
>>>> https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/List.java#L1068
>>>> ]
>>>> [
>>>> https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ImmutableCollections.java#L168
>>>> |
>>>> https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ImmutableCollections.java#L168
>>>> ]

>>>> Paul.

>>>>> On Aug 23, 2022, at 4:49 PM, Ethan McCue < [ mailto:ethan at mccue.dev |
>>>> > ethan at mccue.dev ] > wrote:

>>>> > Hi all,

>>>>> I am running into an issue with the collections framework where I have to choose
>>>> > between good semantics for users and performance.

>>>>> Specifically I am taking a java.util.List from my users and I need to choose to
>>>> > either
>>>>> * Not defensively copy and expose a potential footgun when I pass that List to
>>>> > another thread
>>>> > * Defensively copy and make my users pay an unnecessary runtime cost.

>>>>> What I would really want, in a nutshell, is for List.copyOf to be a no-op when
>>>> > used on lists made with List.of().

>>>>> Below the line is a pitch I wrote up on reddit 7 months ago for a mechanism I
>>>>> think could accomplish that. My goal is to share the idea a bit more widely and
>>>> > to this specific audience to get feedback.

>>>>> [
>>>>> https://www.reddit.com/r/java/comments/sf8qrv/comment/hv8or92/?utm_source=share&utm_medium=web2x&context=3
>>>>> |
>>>>> https://www.reddit.com/r/java/comments/sf8qrv/comment/hv8or92/?utm_source=share&utm_medium=web2x&context=3
>>>> > ]

>>>> > Important also for context is Ron Pressler's comment above.
>>>> > --------------

>>>> > What if the collections api added more marker interfaces like RandomAccess?

>>>>> It's already a common thing for codebases to make explicit null checks at error
>>>> > boundaries because the type system can't encode null | List<String>.

>>>> > This feels like a similar problem.
>>>>> If you have a List<T> in the type system then you don't know for sure you can
>>>>> call any methods on it until you check that its not null. In the same way,
>>>>> there is a set of methods that you don't know at the type/interface level if
>>>> > you are allowed to call.

>>>> > If the List is actually a __
>>>> > Then you can definitely call
>>>> > And you know other reference holders might call
>>>> > And you can confirm its this case by
>>>> > null
>>>> > no methods
>>>> > no methods
>>>> > list == null
>>>> > List.of(...)
>>>> > get, size
>>>> > get, size
>>>> > ???
>>>> > Collections.unmodifiableList(...)
>>>> > get, size
>>>> > get, size, add, set
>>>> > ???
>>>> > Arrays.asList(...)
>>>> > get, size, set
>>>> > get, size, set
>>>> > ???
>>>> > new ArrayList<>()
>>>> > get, size, add, set
>>>> > get, size, add, set
>>>> > ???
>>>>> While yes, there is no feasible way to encode these things in the type system.
>>>> > Its not impossible to encode it at runtime though.
>>>> > interface FullyImmutable {
>>>> > // So you know the existence of this implies the absence
>>>> > // of the others
>>>> > default Void cantIntersect() { return null; }
>>>> > }

>>>> > interace MutationCapability {
>>>> > default String cantIntersect() { return ""; }
>>>> > }

>>>> > interface Addable extends MutationCapability {}
>>>> > interface Settable extends MutationCapability {}

>>>> > If the List is actually a __
>>>> > Then you can definitely call
>>>> > And you know other reference holders might call
>>>> > And you can confirm its this case by
>>>> > null
>>>> > no methods
>>>> > no methods
>>>> > list == null
>>>> > List.of(...)
>>>> > get, size
>>>> > get, size
>>>> > instanceof FullyImmutable
>>>> > Collections.unmodifiableList(...)
>>>> > get, size
>>>> > get, size, add, set
>>>> > !(instanceof Addable) && !(instanceof Settable)
>>>> > Arrays.asList(...)
>>>> > get, size, set
>>>> > get, size, set
>>>> > instanceof Settable
>>>> > new ArrayList<>()
>>>> > get, size, add, set
>>>> > get, size, add, set
>>>> > instanceof Settable && instanceof Addable
>>>>> In the same way a RandomAccess check let's implementations decide whether they
>>>>> want to try an alternative algorithm or crash, some marker "capability"
>>>>> interfaces would let users of a collection decide if they want to clone what
>>>> > they are given before working on it.


>>>> > --------------

>>>>> So the applicability of this would be that the list returned by List.of could
>>>>> implement FullyImmutable, signifying that there is no caller which might have a
>>>>> mutable handle on the collection. Then List.of could check for this interface
>>>> > and skip a copy.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20220824/f0acba11/attachment-0001.htm>


More information about the core-libs-dev mailing list