This is a nice article that outlines two reasons already mentioned on the above answers:
- Security: the system can hand out
sensitive bits of read-only
information without worrying that
they will be altered
- Performance: immutable data is very
useful in making things thread-safe.
And this probably is the most detailed comment in that article. Its has to do with the string pool in Java and security issues. Its about how to decide what goes into the string pool. Assuming both strings are equal if their sequence of characters are the same, then we have a race condition on who gets there first and along with it security issues. If not, then the string pool will contain redundant strings thus losing the advantage of having it in the first place. Just read it out for yourself, will ya?
Extending String would play havoc with equals and intern. JavaDoc says equals:
Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object.
Assuming java.lang.String
wasn't final, a SafeString
could equal a String
, and vice versa; because they'd represent the same sequence of characters.
What would happen if you applied intern
to a SafeString
-- would the SafeString
go into the JVM's string pool? The ClassLoader
and all objects the SafeString
held references to would then get locked in place for the lifetime of the JVM. You'd get a race condition about who could be the first to intern a sequence of characters -- maybe your SafeString
would win, maybe a String
, or maybe a SafeString
loaded by a different classloader (thus a different class).
If you won the race into the pool, this would be a true singleton and people could access your whole environment (sandbox) through reflection and secretKey.intern().getClass().getClassLoader()
.
Or the JVM could block this hole by making sure that only concrete String objects (and no subclasses) were added to the pool.
If equals was implemented such that SafeString
!= String
then SafeString.intern
!= String.intern
, and SafeString
would have to be added to the pool. The pool would then become a pool of <Class, String>
instead of <String>
and all you'd need to enter the pool would be a fresh classloader.