I was checking out the default implementation of Stream.toList and noticed it uses "Collections.unmodifiableList(new ArrayList(Arrays.asList(this.toArray())))". I'm curious as to why it needs to create a new ArrayList. Why not just use "Collections.unmodifiableList(Arrays.asList(this.toArray()))" instead? What's the reasoning behind this?
2 Answers
You might think that’s actually the implementation used by the JDK, but it’s not quite right. The standard implementation (ReferencePipeline) has an optimized version. This default is just a fallback for any third-party Stream implementations that don’t provide their own. That’s why it looks the way it does, and there have been discussions about the specifics on the openjdk mailing lists.
Check out the explanation in the PR discussion. The default implementation seems to do some unnecessary copying, but the reason is that we can’t be sure toArray() always gives us a fresh array. Wrapping it in Arrays.asList and then creating a new ArrayList is a way to avoid any issues where someone might hold a direct reference to the internal array of a List that’s meant to be unmodifiable.
I don't see why they have to complicate it like that. The toArray() spec says it should return a fresh array. Shouldn’t implementations just trust that? Or is there a security reason for this?