Proposal: Collection mutability marker interfaces
John Hendrikx
john.hendrikx at gmail.com
Fri Aug 26 15:20:54 UTC 2022
On 24/08/2022 15:38, Ethan McCue wrote:
> 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
I don't think this is a common enough use case that should be catered
for. It might be better handled with concurrent lists instead.
The most common use case by far is wanting to make sure a collection
you've received is not going to be modified while you are working with
it. I don't think another proposal which does cover the most common
cases should be dismissed out of hand because it doesn't support a
rather rare use case.
--John
>
> On Wed, Aug 24, 2022, 8:13 AM John Hendrikx <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
>> <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/ImmutableCollections.java#L168
>>
>> Paul.
>>
>> > On Aug 23, 2022, at 4:49 PM, Ethan McCue <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/20220826/5865e1df/attachment-0001.htm>
More information about the core-libs-dev
mailing list