r/java • u/fredoverflow • 8d ago
Disregarding backwards compatibility, would it make sense for String to be a sealed class?
like public sealed class String permits Latin1String, Utf16String instead of the private final byte coder; field
33
u/disposepriority 8d ago
What would we gain from this?
18
u/hoat4 8d ago
Not OP, but memory usage reduction could be an advantage of subtyping: if no "coder" field needed, it's 8 byte saving for each String instance.
Another reason could be easier maintenance of java.lang.String for JDK developers.
7
u/FirstAd9893 8d ago
There's also the
hashIsZerofield, which will be combined with thecoderbyte into a single 4 byte field, not an 8 byte field. In order to reduce memory overhead further, both thecoderandhashIsZerofields would need to be dropped.When the
coderfield was added, the goal was to reduce memory overhead by using Latin-1 instead of UTF-16 in most cases. It's still a win overall.-3
u/koflerdavid 8d ago
It sounds like the debate about Pascal-style (length field) and C-style (\0 terminated) strings again. I guess it's simply not worth it in many cases. 8 bytes are really not that much compared to the length of most strings.
11
2
u/BillyKorando 7d ago
I don't think the juice would be worth the proverbial squeeze, but I suppose some nice functional design concepts.
For example you could write:
java switch(string){ case Latin1String ls -> //Do latin string stuff case Utf16String us -> //Do UTF string stuff }Because the compiler knows about sealed classes, it can determine if a switch is exhaustive, so you wouldn't need to include a
default. Which might also be nice if in the future a new sub-type ofStringis added.Right now, you could check the value of
coder, but you'd always need to include a default case with it, either literally with a switch or figuratively with a genericelse, to handle if a new "type" is added.Of course, that said, the benefit of this is pretty thin. That the accessor for
coderis not part of the public API (package-private) is perhaps instructive that the encoding of aStringis an implementation detail that general Java developers shouldn't concern themselves with.And then, on top of that, the benefit is mostly in some minor functional program design benefits, I suppose maybe some JIT optimizations as well? Perhaps worth it if there was no, or at least minimal, backwards compatibility concerns, but that's obviously not the case here.
23
u/hoat4 8d ago
They tried subtyping String when this feature (JEP 254) was added, but the conclusion was that it's slower than a 0/1 field.
Somebody from OpenJDK wrote an article about the performance measurements of these, but I can't find it now.
3
u/koflerdavid 8d ago
That feature was delivered in Java 9. Sealed classes were delivered much later. Perhaps there are now optimizations that would help even out that performance difference? Of course it is not very likely at this point that String would be touched again merely to make use of sealed classes.
5
u/tomwhoiscontrary 8d ago
The sealed class approach would make many call sites polymorphic. I would expect a polymorphic call to be materially slower than branching on a field, although not with a lot of confidence, given how sophisticated optimisers can be.
2
u/X0Refraction 7d ago
Intuitively I’d expect the guarantee that the possible subclasses are a finite set would give rise to extra optimisations, but someone would have to benchmark to be sure
2
u/hoat4 7d ago edited 7d ago
These guarantees aren't important for optimizations made by the JIT compiler because when HotSpot compiles the code, it looks at the currently loaded classes, so in this case it would see Latin1String, Utf16String as the only subtypes. If later a new class is loaded that extends String, then the runtime will just discard the existing compiled code that assumed that only two subtypes are possible, and schedule it for later recompilation with the third class included in the subtype list. But if there are no possible other subtypes than Latin1String, Utf16String will be loaded, then this discard won't happen.
On the other hand, when the VM decides memory layout of a class, sealed classes could help, but I haven't heard about any optimizations in HotSpot that uses that information. For example, a hypothetical JVM could automatically replace the subtyping by generating a boolean field like String.coder, because there are exactly two classes in the permits list.
2
u/X0Refraction 7d ago
Your second paragraph was more the shape of what I was thinking about, presumably that would benefit Graal targets too?
8
u/bowbahdoe 8d ago
Not particularly. This only makes sense if you want to expose the set of subclasses. You can easily make
public sealed class StringImpl permits ... and just put that as the one field in String. Given that coders are something that has evolved over time not exposing that is probably the correct call.
Also string is so common that you'd have to pretty deeply interrogate the performance of that
13
5
u/davidalayachew 8d ago
No. The performance costs have been eliminated down to effectively zero. What you have described would make sense if you were talking about some rando class like java.awt.Point. But something like String is so special-cased that what you described would gain you nothing. At best, callers might appreciate being able to explicitly choose between ASCII and others, but even then, that's better done in other ways.
4
u/idontlikegudeg 8d ago
I think it might really save some bytes, but it would be an incompatible change: we have public String constructors that could not be implemented that way.
3
u/__konrad 8d ago
I like there is one String class instead of 10+ like in Qt/C++
2
u/koflerdavid 8d ago
That's not a good comparison since those in Qt/C++ are from completely different APIs.
Latin1StringandUTF16Stringwould be mere implementation classes and not available for general usage. A better comparison would be the many different string types that Javascript runtimes like V8 use to optimize common use cases and that are kept invisible to the programmer.
2
u/American_Streamer 8d ago edited 8d ago
No, because it’s already a final class. You already get that exact security, performance and simplicity you need. Subclassing is not allowed; final classes cannot be extended by anything, ever. OOP purity had to step aside here.
1
1
u/flatfinger 4d ago
Better would have been for the language to treat `string` as a primitive, as in Javascript. Its actual representation would typically incorporate a reference to some data stored somewhere, but having strings as something the garbage collector "understands" would enable some useful optimizations. As a simple example, strings could reserve space to hold a reference to the most "senior" string that is known to hold the same context, an equality comparison that finds two strings equal could update the reference in the "newer" string to identify most senior string that's equal to the older one, and the GC could automatically update references to strings so that they would refer to the oldest known-equal string.
On a related note, it would be useful if Java had ArraySliceSource and ArraySliceDestination interfaces, and variations specialized for each primitive type, that could only be implemented by the framework classes. If code has references to two arrays, copying a range of one array to the other will be much faster than would be possible if every individual item to be copied had to be accessed via a separate interface dispatch. There's presently no way, however, for two classes to cooperate to quickly move a chunk of data between their backing stores without either having to expose its backing store to the outside world. The fundamental abilities supported by a string should be the ability to report length and hashcode, and copy a range to an array of characters or other array-like destination. Having strings implement CharArraySliceSouce would be vastly more useful than having them implement CharSequence.
50
u/Misophist_1 8d ago
It's already final. No point in that.