There are three places where final can show up in Java:
Making a class final prevents all subclassing of the class. Making a method final prevents subclasses of the method from overriding it. Making a field final prevents it from being changed later.
There are optimizations that happen around final methods and fields.
A final method makes it easier for HotSpot to optimize via inlining. However, HotSpot does this even if the method isn't final as it works on the assumption that it hasn't been overridden until proven otherwise. More about this on SO
A final variable can be aggressively optimized, and more about that can be read in the JLS section 17.5.3.
However, with that understanding one should be aware that neither of these optimizations are about making a class final. There is no performance gain by making a class final.
The final aspect of a class has nothing to do with immutability either. One can have an immutable class (such as BigInteger) that is not final, or a class that is mutable and final (such as StringBuilder). The decision about if a class should be final is a question of design.
Strings are one of the most used data types. They are found as keys to maps, they store user names and passwords, they are what you read in from a keyboard or a field on a web page. Strings are everywhere.
The first thing to consider of what would happen if you could subclass String is realizing that someone could construct a mutable String class that would appear to otherwise be a String. This would mess up Maps everywhere.
Consider this hypothetical code:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
This is a problem with using a mutable key in general with a Map, but the thing that I'm trying to get at there is that suddenly a number of things about the Map break. The Entry is no longer at the right spot in the map. In a HashMap, the hash value has (should have) changed and thus its no longer at the right entry. In the TreeMap, the tree is now broken because one of the nodes is on the wrong side.
Since using a String for these keys is so common, this behavior should be prevented by making the String final.
You may be interested in reading Why is String immutable in Java? for more about the immutable nature of Strings.
There are a number of various nefarious options for Strings. Consider if I made a String that always returned true when equal was called... and passed that into a password check? Or made it so that assignments to MyString would send a copy of the String to some email address?
These are very real possibilities when you have the ability to subclass String.
Java.lang String optimizations
While before I mentioned that final doesn't make String faster. However, the String class (and other classes in
java.lang) make frequent use of package level protection of fields and methods to allow other
java.lang classes to be able to tinker with the internals rather than going through the public API for String all the time. Functions like getChars without range checking or lastIndexOf that is used by StringBuffer, or the constructor that shares the underlying array (note that thats a Java 6 thing that was changed because of memory issues).
If someone made a subclass of String, it wouldn't be able to share those optimizations (unless it was part of
java.lang too, but that's a sealed package).
Its harder to design something for extension
Designing something to be extendable is hard. It means that you've got to expose parts of your internals for something else to be able to modify.
An extendable String could not have had its memory leak fixed. Those parts of it would need to have been exposed to subclasses and changing that code would then mean the subclasses would break.
Java prides itself in backwards compatibility and by opening up core classes to extension, one loses some of that ability to fix things while still maintaining the computability with third party subclasses.
Checkstyle has a rule that it enforces (that really frustrates me when writing internal code) called "DesignForExtension" which enforces that every class is either:
- Empty implementation
The rational for which is:
This API design style protects superclasses against being broken by subclasses. The downside is that subclasses are limited in their flexibility, in particular they cannot prevent execution of code in the superclass, but that also means that subclasses cannot corrupt the state of the superclass by forgetting to call the super method.
Allowing implementation classes to be extended means that the subclasses can possibly corrupt the state of the class it is based off of and make it so that various guarantees that the superclass gives are invalid. For something as complex as String, it is almost a certainty that changing part of it will break something.
Its part of being a developer. Consider the likelihood that each developer will create their own String subclass with their own collection of utils in it. But now these subclasses cannot be freely assigned back to each other.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
This way leads to madness. Casting to String all over the place and checking to see if the String is an instance of your String class or if not, making a new String based on that one and... just, no. Don't.
I'm sure you can write a good String class... but leave writing multiple string implementations to those crazy people who write C++ and have to deal with
char* and something from boost and SString, and all the rest.
Java String magic
There are several magical things that Java does with Strings. These make it easier for a programmer to deal with but introduce some inconsistencies in the language. Allowing subclasses on String would take some very significant thought about how to deal with these magical things:
String Literals (JLS 3.10.5)
Having code that allows one to do:
String foo = "foo";
This is not to be confused with boxing of the numeric types like Integer. You can't do
1.toString(), but you can do
+ operator (JLS 15.18.1)
No other reference type in Java allows for an operator to be used on it. The String is special. The string concatenation operator also works at the compiler level so that
"foo" + "bar" becomes
"foobar" when it is compiled rather than at runtime.
String Conversion (JLS 5.1.11)
All objects can be converted into Strings just by using them in a String context.
String Interning (JavaDoc)
The String class has access to the pool of Strings that allow it to have canonical representations of the object which is populated at compile type with the String literals.
Allowing a subclass of String would mean that these bits with the String that make it easier to program would become very difficult or impossible to do when other String types are possible.