> so it's perhaps better to call add() inside a try/catch on UnsupportedOperationException.
As much as sin is wrong, sketching out what that might look like... forgive the toyness of the example final class Ex { private Ex() {} /* * Adds the odd numbers from 1 to 10 to the List then makes all the odds even. * * Takes ownership of the list, avoids making copies if it doesn't need to */ static List<Integer> addOdds(List<Integer> l) { for (int i = 1; i <= 10; i++) { l.add(i); } for (int i = 0; i < l.size(); i++) { if (l.get(i) % 2 == 1) { l.set(i, l.get(i) + 1); } } } } VS final class Ex { private Ex() {} /* * Adds the odd numbers from 1 to 10 to the List then makes all the odds even. * * Takes ownership of the list, avoids making copies if it doesn't need to */ static List<Integer> addOdds(List<Integer> l) { for (int i = 1; i <= 10; i++) { try { l.add(i); } catch (UnsupportedOperationException e) { l = new ArrayList<>(l); } } for (int i = 0; i < l.size(); i++) { if (l.get(i) % 2 == 1) { try { l.set(i, l.get(i) + 1); } catch (UnsupportedOperationException e) { l = new ArrayList<>(l); } } } } } VS final class Ex { private Ex() {} /* * Adds the odd numbers from 1 to 10 to the List then makes all the odds even. * * Takes ownership of the list, avoids making copies if it doesn't need to */ static List<Integer> addOdds(List<Integer> l) { if (!(l instanceof Settable && l instanceof Addable)) { l = new ArrayList<>(l); } for (int i = 1; i <= 10; i++) { l.add(i); } for (int i = 0; i < l.size(); i++) { if (l.get(i) % 2 == 1) { l.set(i, l.get(i) + 1); } } } } On Wed, Aug 24, 2022 at 10:03 AM Remi Forax <fo...@univ-mlv.fr> wrote: > > > ------------------------------ > > *From: *"Ethan McCue" <et...@mccue.dev> > *To: *"John Hendrikx" <john.hendr...@gmail.com> > *Cc: *"core-libs-dev" <core-libs-dev@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 <john.hendr...@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.san...@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 <et...@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 >>> > >>> > 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. >>> > >>> > >>> >>> >