You can make it superficially unmodifiable thus:
return Collections.unmodifiableList(list)
This wraps the list in order that invoking any of the mutation methods will result in an exception.
However, does it really matter to you if somebody modifies the list? You are giving back a new list instance each time this method is invoked, so there is no issue with two callers getting the same instance and having to deal with interactions between the mutations.
Whatever step you take to attempt to make it unmodifiable, I can just copy the list into a modifiable container myself.
It's also quite inconvenient to me as a caller if you give me back something which I can't detect whether it is mutable or not - it looks like a List
; the only way to see if I can mutate it is by calling a mutation method. That's a runtime failure, which makes it a PITA.
Update, to add an alternative.
An alternative to Collections.unmodifiableList
is something like Guava's ImmutableList
: this will likely be faster than Collections.unmodifiableList
since it does not simply provide an unmodifiable view of existing list by wrapping, but rather provides a custom implementation of List
which forbids mutation (there is less indirection).
The advantage of this is that you can return ImmutableList
rather than just the List
returned by Collections.unmodifiableList
: this allows you to give the caller a bit more type information that they can't mutate it. ImmutableList
(and its siblings like ImmutableSet
) mark the mutation methods @Deprecated
so your IDE can warn you that you might be doing something iffy.
The caller might choose to disregard that information (they assign the result to a List
, rather than an ImmutableList
), but that's their choice.