Proposal: Collection mutability marker interfaces
John Hendrikx
john.hendrikx at gmail.com
Wed Aug 24 12:12:20 UTC 2022
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/20220824/29ad90c7/attachment-0001.htm>
More information about the core-libs-dev
mailing list