- JEP 401: Value Classes and Objects hat die Stufe erreicht, als echtes JDK-Preview aufgenommen zu werden
- Das Kernziel ist, Java-Objekte so zu machen, dass sie „wie eine Klasse codiert sind und wie ein int arbeiten“, um die Kosten von Objekt-Headern, Heap-Allokation, GC und Pointer-Indirektion zu senken
- Die Value Class in JDK 28 ist noch ein nullable Referenztyp; Non-Null-Typen, spezialisierte Generics und 128-Bit-Codierung sind nicht enthalten, und
--enable-preview ist erforderlich
- Die JVM kann Value Objects skalieren oder in Feldern und Arrays eine Heap-Abflachung vornehmen, aber bei höheren Typen wie erasure-basierten Generics oder
Object können sie als Heap-Objekte materialisiert werden
- Java-Entwickler müssen den Unterschied zwischen Identity und Value in ihr Code-Design einfließen lassen; betroffen sind unter anderem
==, synchronized, Primitive-Wrapper, Array-Performance und künftig auch die Generics-Spezialisierung
Der Umfang von Valhalla in JDK 28
- Am 15. Juni bestätigte Oracle-Ingenieurin Lois Foltan die Integration von JEP 401: Value Classes and Objects in das OpenJDK-Haupt-Repository und das Ziel JDK 28
- Der zugehörige Pull Request fügt in 1.816 Dateien mehr als 197.000 Zeilen hinzu
- Wegen des großen Umfangs der Änderungen gab es während der Integration die Bitte an andere Committer, große Commits vorübergehend zurückzustellen
- JEP 401 ist eine standardmäßig deaktivierte Preview-Funktion
- Für die Verwendung der Syntax ist
--enable-preview erforderlich
- Brian Goetz grenzte dies als „den ersten Teil von Valhalla“ ein
- JDK 28 soll im März 2027 erscheinen, die Integration in die Mainline ist etwa für Juli 2026 geplant
Die Kosten des Java-Objektmodells, auf die Valhalla zielt
- Das Motto von Valhalla lautet „codes like a class, works like an int“
- Ziel ist, normale Klassen mit Methoden, Konstruktorvalidierung und aussagekräftigen Feldnamen zu verwenden, während die JVM sie so effizient wie Primitive behandeln kann
- In Java ist mit Ausnahme von acht Primitives fast alles ein Referenztyp
- Bei
Point p = new Point(1, 2) ist p nicht der Point selbst, sondern ein Pointer auf ein Heap-Objekt
- Jedes Mal, wenn ein Feld gelesen wird, muss die JVM dem Pointer folgen
- Mit wachsender Objektzahl steigen die Kosten stark an
- Jedes Objekt hat einen Objekt-Header für Typ, Synchronisierungsstatus und Ähnliches
- Objekte werden auf dem Heap allokiert und später Ziel der GC
- Ein Array mit einer Million
Point besteht in Wirklichkeit aus einer Million Pointern und einer Million über den Heap verteilter Objekte
- Brian Goetz bezeichnete dieses Speicherlayout in „State of Valhalla“ als fluffy
- Valhalla zielt stattdessen auf ein densees Layout, bei dem Daten direkt nebeneinander liegen
Die Hardware-Lücke und die Grenzen der Escape Analysis
- Ein dichtes Speicherlayout ist wegen der Geschwindigkeitslücke zwischen CPU und Arbeitsspeicher wichtig
- 1995 waren die Kosten eines Speicherzugriffs ähnlich hoch wie eine CPU-Operation
- Heute sind CPUs um Größenordnungen schneller als der Hauptspeicher, und Caches überbrücken diese Lücke
- CPUs lesen Speicher meist in 64-Byte-Cache-Lines
- Wenn Daten dicht und in Reihenfolge angeordnet sind, werden auf einmal viele nützliche Werte geladen
- Greift man über Pointer auf verstreute Objekte zu, kann es zu Cache-Misses kommen, die deutlich langsamer sind als Hits
- Die Escape Analysis der JVM kann einige Objektallokationen eliminieren
- Wenn festgestellt wird, dass ein Objekt nicht aus einem lokalen Codeabschnitt „entkommt“, kann es statt auf dem Heap in Variablen oder Registern in seine Felder zerlegt werden
- Allerdings ist Escape Analysis wenig vorhersehbar und anfällig
- Wenn ein Objekt in Felder anderer Klassen gelangt, in Arrays gespeichert wird, an komplexe Methoden übergeben wird oder Grenzen überschreitet, die der JIT nicht analysieren kann, kann die Optimierung stoppen
- Schon kleine Refactorings, JDK-Updates oder Änderungen an der Codestruktur können dazu führen, dass das Objekt wieder auf dem Heap landet
- Gibt man für Performance Objekte auf und codiert direkt als rohe Bytes wie
r, g, b, gewinnt man zwar Geschwindigkeit, verliert aber Sicherheit, Lesbarkeit, Validierung und Methoden
Der Start 2014 und der Wechsel von Q World zu L World
- Project Valhalla startete offiziell im Jahr 2014
- James Gosling beschrieb es damals als „six PhDs tied into a single knot“
- Die Java-Schöpfer wollten schon seit Java 1.0 Value Types, gaben das Thema 1995 aber auf, weil das Problem zu schwierig war
- Das frühe Ziel war, die Ausrichtung von Programmiermodell und den Performance-Eigenschaften moderner Hardware wiederherzustellen
- Die Richtung war, dass Nutzer selbst flache und dichte Typen wie Primitive deklarieren können, die aber wie normale Klassen aussehen und funktionieren
- Frühe Prototypen folgten dem Ansatz Q World
- Dabei wurden neue Value Types als grundsätzlich andere Entitäten als Objekte behandelt, mit separatem Type Descriptor, Bytecode und Top Type
- Dadurch musste das gesamte JVM-Typsystem zwei Varianten tragen, was die Komplexität erhöhte
- Der um 2019 aufgekommene Ansatz L World wurde zum Wendepunkt
- Value Types teilen sich denselben „L carrier“ wie normale Referenzen
- Das Team erwartete, dass diese Integration schwierig werden würde, aber sie funktionierte ohne große Kompromisse und löste mehrere Probleme früherer Prototypen
- In L World entstand eine wichtige Trennung
- JVM-Modell und Sprachmodell müssen sich nicht zu 100 % decken
- Die JVM kann ein L-World-Modell haben, während Programmierern ein komfortableres Sprachmodell angeboten wird
- Die anschließende Arbeit wurde in zwei Phasen aufgeteilt: Value Classes und spezialisierte Generics
Änderungen bei Namen und Modell
- Die Terminologie von Valhalla hat sich mehrfach geändert; das war nicht nur eine Umbenennung, sondern spiegelte Änderungen am Modell wider
- Der frühe Begriff war value types
- Damals war noch nicht genau klar, was diese Typen eigentlich sein sollten
- Etwa 2019–2020 setzte sich das Modell der inline classes durch
- Bestehende Klassen wurden als identity classes verstanden, neue Klassen als inline classes ohne Identität
- Für inline classes galten Beschränkungen wie standardmäßig
final, final-Felder und keine Möglichkeit zur Synchronisierung
- „State of Valhalla“ von 2021 behandelte primitive classes und ein Modell mit zwei Projektionen
- Die Idee war, dass ein Typ sowohl eine flache, nicht-nullfähige value-Variante als auch eine nullfähige reference-Variante besitzt
- Es wurden sogar Syntaxen wie
Point.val / Point.ref, später Point! / Point?, erprobt
- Dieses Modell war leistungsfähig, brachte aber eine hohe kognitive Belastung mit sich
- Programmierer mussten im Alltag zwei Formen desselben Typs und die Zeitpunkte ihrer Umwandlung verstehen
- Letztlich wurde dieser Dualismus reduziert, um das Benutzermodell zu vereinfachen
- Das aktuelle JEP 401 verwendet value class und value object
- Mit dem Modifier
value wird eine value class deklariert
- Instanzen sind value objects ohne Identität
- Eine value class ist weiterhin ein reference type
- Die Nicht-Nullfähigkeit wurde in das separate optionale JEP Null-Restricted Value Class Types ausgelagert
- Sie ist nicht in JDK 28 enthalten
- Ältere Artikel, die das frühere Modell der „primitive classes“ erklären, können vom aktuellen Stand in OpenJDK abweichen
- In JEP 401 ist auch das Preview-JEP 402: Enhanced Primitive Boxing enthalten
- Es geht in Richtung reibungsloserer Konvertierungen zwischen Primitive und Wrapper
- Man sollte nicht davon ausgehen, dass alles in seiner endgültigen Form gemeinsam mit JEP 401 ausgeliefert wird
Das value-class-Modell in JDK 28
- Eine value class wird mit dem Modifier
value deklariert
value class USDCurrency implements Comparable<USDCurrency> {
private int cents; // implicitly final
public USDCurrency(int dollars, int cents) {
this.cents = dollars * 100 + cents;
}
public USDCurrency plus(USDCurrency that) {
return new USDCurrency(0, this.cents + that.cents);
}
// dollars(), cents(), compareTo(), toString()...
}
- Auch ein value record ist möglich
- Die wichtigsten Regeln sind folgende
- Alle Instanzfelder sind implizit final
- Methoden dürfen nicht
synchronized sein
- Die Klasse ist standardmäßig final
- Hierarchien aus value classes und abstract value classes sind möglich
- Eine Klasse mit Identität kann nicht erweitert werden
- Interfaces können implementiert werden
- Die Kerneigenschaft ist das Fehlen von Identität
- Normale Objekte sind verschieden, selbst wenn sie denselben Inhalt haben und zweimal mit
new Point(1, 2) erzeugt werden
- Ein value object hat keine Identität, so wie es beim
int-Wert 4 nicht „zwei verschiedene Vierer“ gibt
Änderungen bei ==, synchronized und null
- Bei value objects ist
== kein Identitätsvergleich mehr, sondern eine Prüfung auf Substituierbarkeit
- Rekursiv wird verglichen, ob es dieselbe Klasse ist und dieselben Feldwerte vorliegen
- Primitive-Felder werden bitweise verglichen, Objektfelder wiederum mit
==
new USDCurrency(3,95) == new USDCurrency(3,95) ergibt also true
- Da
== jedoch den internen Zustand betrachtet, ist für die Frage, ob „dieselben Daten dargestellt werden“, meist equals geeigneter
- Ein value object hat keine Identität, an die synchronisiert werden könnte
- Ein Synchronisierungsversuch löst eine IdentityException aus
- Wenn Identität ausdrücklich geprüft werden muss, können
Objects.requireIdentity und Objects.hasIdentity verwendet werden
- value classes in JDK 28 sind weiterhin nullfähig
USDCurrency d = null; ist zulässig
- Ein Typ, der null verbietet, ist Gegenstand eines separaten künftigen JEP und nicht Teil von JDK 28
- Nicht-Nullfähigkeit ist nicht nur eine Frage der Syntax, sondern ein Performance-Hebel, der eine stärkere Abflachung von value classes ermöglicht
Skalarisierung und Heap-Abflachung
- JEP 401 gibt der JVM die Freiheit, value objects zu optimieren
- Skalarisierung (scalarization) ist eine JIT-Technik, bei der eine value-object-Referenz in eine Menge von Feldern zerlegt wird
- Statt eines
Color-Zeigers können etwa die Bytes r, g, b und ein Null-Flag übergeben werden
- Zuweisungs- und GC-Kosten können damit entfallen
- Es ähnelt der Escape Analysis, ist aber vorhersagbarer und kann über die Grenzen nicht inlineter Methodenaufrufe hinweg angewendet werden
- Die Skalarisierung hat Grenzen
- Wenn der Variablentyp ein Obertyp der value class wie
Object oder ein durch Erasure entstandener generischer Parameter ist, funktioniert sie in der Regel nicht
- In diesem Fall muss das Objekt auf dem Heap materialisiert werden
- Heap-Abflachung (heap flattening) bedeutet, dass Feldwerte eines value object als kompakter Bitvektor codiert und direkt in ein Feld oder Array-Element geschrieben werden
- Ein Zeiger auf eine andere Stelle im Heap ist nicht nötig
- Das erhöht Datendichte und Lokalität
- Abgeflachte Daten müssen bei konkurrierendem Zugriff atomar gelesen und geschrieben werden können, um Tearing zu vermeiden
- Auf gängigen Plattformen kann „klein genug“ inklusive Null-Flag in der Größenordnung von 64 Bit liegen
- Kleine value classes lassen sich gut abflachen, aber schon zwei
int-Felder oder nur ein double können die Größe atomarer Schreibvorgänge überschreiten, sodass wieder ein normales Heap-Objekt nötig wird
- Künftig könnten 128-Bit-Codierungen und null-restricted types die Abflachung größerer value classes ermöglichen
Auswirkungen bei Boxing, Wrappern und Arrays
- Wenn Preview aktiviert ist, werden primitive wrapper classes wie
Integer, Long und Double selbst zu value classes
- Boxed-Werte verlieren ihre Identität, sodass die JVM sie skalarisieren und abflachen kann
Integer[] nähert sich dann in der Effizienz int[] an, und der Boxing-Overhead soll stark sinken
- JEP 402: Enhanced Primitive Boxing erweitert Konvertierungen zwischen Primitive und Box weiter
- Es öffnet den Weg zu Ausdrücken wie
List<int>, ist aber weiterhin ein separates, noch reifendes Vorhaben
- Die Wirkung zeigt sich besonders deutlich bei Arrays
- Ein herkömmliches
Color[] kann aus einer Million Zeigern und einer Million über den Heap verstreuten Objekten bestehen
- Ein
Color[] aus value classes kann zu einem contiguous block werden, der die Farbwerte direkt fortlaufend speichert
- Die CPU kann dann mehrere Werte sequenziell cache-line-weise lesen
Vorher-Nachher-Unterschied am Beispiel Point[]
- Das folgende Beispiel zeigt eine normale class vor Valhalla
final class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Point[] points = new Point[1_000_000];
- Dieses Array enthält eine Million Zeiger
- Jeder Zeiger verweist auf ein separates
Point-Objekt irgendwo auf dem Heap
- Jedes Objekt hat zusätzlich zu den beiden
int einen Objekt-Header
- Beim Iterieren muss man den Zeiger lesen, zu dieser Adresse springen und dann die Felder lesen
- Nach Valhalla sieht ein Beispiel für eine Value Class so aus
value class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Point[] points = new Point[1_000_000];
- Im Code besteht der Unterschied nur aus dem Wort
value, aber das Speicherlayout ändert sich
- Die JVM kann die Werte jedes Point dicht gepackt im Array speichern
- Es gibt weder Header pro Element noch Zeiger
- Bezogen auf die beiden
int x und y kann das als 8 Byte plus möglichem Null-Flag zusammenhängend angeordnet werden
- Auch die Wartbarkeit bleibt erhalten
Point ist weiterhin eine class mit Namen, Konstruktor, Validierung und Methoden
- Man kann vermeiden, in
int[] xs und int[] ys aufzuteilen und die Indizes manuell synchron zu halten
Warum spezialisierte Generics noch ausstehen
- Java-Generics werden über Type Erasure implementiert
List<String> und List<Integer> sind zur Runtime dasselbe List
- Der Typparameter
T wird zu Object erased
- Erasure war eine bewusste Entscheidung, um Generics einzuführen, ohne die bestehende Java-Codebasis zu brechen
- Selbst wenn eine nicht-generische class in eine generische geändert wird, bleiben bestehende Source-Dateien und kompilierte Klassen kompatibel
- Valhalla und Erasure geraten bei der Performance aneinander
- Wenn man ein Value Object in
List<Point> legt, wird T zu Object erased, daher muss das Objekt auf dem Heap materialisiert werden
- Der Vorteil der Flattening in
Point[] kann in ArrayList<Point> verloren gehen
- Der Plan zur Behebung besteht aus zwei Schritten
- Universal Generics: Auf Sprachebene sollen Typvariablen auch Value Types handhaben können
- Weiterhin mit Erasure
- Weil ein
T-Feld standardmäßig mit null beginnt, kann eine Compiler-Warnung zur „null pollution“ entstehen
- Wenn diese Warnung behoben ist, ist die API weitgehend specialization-ready
- Specialized Generics: Auf JVM-Ebene werden spezialisierte Class-Layouts pro konkretem Typargument erzeugt
- In der Projektsprache sind species und type restriction dabei relevant
- Erst in diesem Schritt kann
ArrayList<Point> tatsächlich flachen Speicher nutzen
- JDK 28 enthält keine vollständigen Specialized Generics
- Dass Collections, Streams und APIs auf Value Types flach und ohne Allokationen arbeiten, ist Arbeit für ein zukünftiges Release
Was in JDK 28 enthalten ist und was nicht
- In JDK 28 kommt Folgendes
- Deklarationen für
value class und value record
- Die Migration bestehender value-based classes im JDK, darunter Klassen wie Primitive Wrapper, zu Value Classes
- Skalarisierung und Flattening für Klassen, die die Bedingungen erfüllen
- Günstigeres Boxing
- Nicht in JDK 28 enthalten ist Folgendes
- null-restricted types
- vollständige Specialized Generics
- 128-Bit-Kodierung
- ein vollständig ausgereiftes JEP 402
- Da es sich um ein Preview-Feature handelt, können sich Syntax und Verhalten je Release je nach Feedback ändern
- JDK 28 ist kein LTS
- Das nächste LTS wird voraussichtlich JDK 29 im September 2027 sein
- Viele Unternehmen werden stabiles Valhalla wohl erst in einem LTS sehen, aber die Preview in JDK 28 startet den echten Feedback-Loop aus produktivem Code
Veränderungen für Ökosystem und Code
- Im High-Performance-Java-Bereich eröffnet Valhalla einen Weg zu dichten Datenstrukturen, ohne auf Abstraktion zu verzichten
- Dazu zählen Bereiche wie Datenverarbeitung, Vektorberechnungen, ML, Spieleentwicklung, Finanzen und Codecs
- Frameworks und Bibliotheken können mit der Migration von value-based classes beginnen
- Code, der auf Identity angewiesen ist, kann Verhaltensunterschiede erleben
== bedeutet bei Value Objects keinen Adressvergleich mehr, sondern einen Vergleich auf Substituierbarkeit
synchronized führt bei Value Objects zu einer IdentityException
- Auch wenn
Integer zu einer Value Class wird, bleibt das Binary in den meisten Fällen weiter verlinkbar
- Neu auftretende Compile-Fehler betreffen Fälle, in denen auf solchen Typen synchronisiert werden soll
== mit Abhängigkeit von Integer-Identity oder synchronized(someInteger) kann betroffen sein
- Early-Access-Builds sind unter jdk.java.net/valhalla verfügbar
Zusammenfassung häufiger Fragen
- Eine Value Class ist nicht dasselbe wie ein Record
record ist die Entscheidung, dass der Inhalt aus Komponenten besteht
value ist die Entscheidung, auf Identity zu verzichten
- Es sind alle Kombinationen aus normaler class, record, value class und value record möglich
- Value Objects können mit
== verglichen werden
- Die Bedeutung ist nicht Adressvergleich, sondern Substituierbarkeit
- Für die Gleichheit der dargestellten Daten ist meist
equals besser geeignet
- Value Classes in JDK 28 können null sein
- Nicht-nullbare Typen sind ein zukünftiges JEP
- Das ist auch für das Flattening größerer Value Classes wichtig
- Ein schnelles flaches
ArrayList<Point> gibt es noch nicht
- Wegen Type Erasure werden Objekte in generischen Collections auf dem Heap materialisiert
- Ein typischer Fall, in dem Flattening in JDK 28 direkt wirkt, sind Felder und Arrays von Value Types wie
Point[]
- Escape Analysis kann nicht alles ersetzen
- Wenn Objekte in Felder, Arrays oder über die Grenzen der Analyse hinaus gelangen, kann die Optimierung scheitern
- Die Skalarisierung von Value Objects ist vorhersehbarer und kann weiter über Method-Call-Grenzen hinausreichen
- Valhalla insgesamt wird sich über mehrere Releases hinweg erweitern
- JDK 28 ist die erste Preview von Value Classes
- Specialized Generics, null-restricted types und 128-Bit-Kodierung sind Arbeit für zukünftige Releases
2 Kommentare
Die über einen langen Zeitraum hinweg veröffentlichten virtuellen Threads von Project Loom sind praktisch und lösen auf JVM-Runtime-Ebene vieles, sodass Entwickler vergleichsweise wenig grübeln müssen.
Es wäre schön, wenn auch Project Valhalla bei seinem finalen Release so wirken würde, als käme es einem kostenlosen Mittagessen ziemlich nahe.
Hacker-News-Kommentare
Es hieß zwar, der Speicherunterschied sei grundlegend, aber ich frage mich, ob der Artikel überhaupt ordentlich redigiert wurde.
Wurde nicht noch eben erklärt, dass Objekte mit einer Darstellung von mehr als 64 Bit nicht im Heap flachgelegt werden? Das
Pointim Beispiel hat zwei 32-Bit-Integer plus ein Null-Flag und kommt damit auf mindestens 65 Bit.Bei Formulierungen wie „es könnte ein Null-Flag geben“ und den folgenden kurzen Hervorhebungssätzen wirkt es, als habe eine KI die Betonungssätze erzeugt und sei dabei vom Weg abgekommen, und auch der mittlere Block
"[IMAGE: the same Point[] array in two variants..."ist bedauerlich.Es ist okay, sich beim Schreiben von KI helfen zu lassen, aber wenn keine eigene Stimme erkennbar ist, gibt es keinen Grund, das zu lesen.
https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
Aber nach ein paar Absätzen war klar, dass der Artikel durch ein LLM gegangen ist oder auf noch schlimmere Weise erzeugt wurde.
Ob technischer Blog oder sonst etwas: Bitte lasst nicht die KI an eurer Stelle schreiben. Niemand will so etwas lesen.
Heute habe ich gelernt, dass es in Rust
NonZeroU64gibt und man in Kombination mitOptionaldas gewünschte Verhalten mit nur 64 Bit pro Eintrag bekommt.https://doc.rust-lang.org/std/num/type.NonZeroU64.html
Wie auch im JEP klar gesagt wird, ist das nur das erste Ergebnis eines riesigen Features, und wie bei aktuellen Java-Features üblich wird es stückweise ausgeliefert.
Natürlich ist das Ziel, auch größere Werte flachzulegen, und der Mechanismus dafür existiert bereits in der JVM. Es bleibt nur noch, auf Sprachebene die Absicht auszudrücken, dass „Tearing erlaubt ist“.
Ich erkenne die Mühe an, die tatsächlich in Valhalla geflossen ist, aber der Deutung „das Modell war mächtig, aber mental schwergewichtig“ kann ich nur schwer zustimmen.
Zu sagen, dass eine Variable nicht null sein kann, ist keine mental belastende Unterscheidung, erst recht nicht, wenn alles ausreichend gekennzeichnet ist.
Auch die Haltung „wir opfern die obere Leistungsgrenze, um das Benutzermodell zu vereinfachen“ könnte den Nutzern gerade eine Vereinfachung genommen haben.
Das Typsystem einer Programmiersprache soll Entwicklern nützliche Garantien auf einer CPU bieten, die letztlich nur mit Zahlen arbeitet. Es gibt keinen Grund, optionale Sicherheitsgarantien zu verringern, nur weil sie „zu komplex“ seien.
Und das, obwohl man sogar schon zu der Einsicht gelangt ist, dass sich „Sprachmodell und JVM-Modell nicht zu 100 % überlappen müssen“.
Die Java-Governance wirkt schwach, besonders im Vergleich zu .NET, wo man von Anfang an meist die richtigen Entscheidungen getroffen hat.
Ich frage mich inzwischen sogar, ob Java innerhalb von Oracle überhaupt noch Wert oder Aufmerksamkeit genießt. Das Unternehmen wirkt heute wie ein Datacenter-/Compute-Geschäft mit angehängten Legacy-Aktivitäten und enormen Verbindlichkeiten.
Manchmal habe ich das Gefühl, die einzigen Bereiche bei Oracle, die noch Geld verdienen, seien die Rechtsabteilung und die Rasenmähersparte.
Und ein Null-Marker kommt ebenfalls: https://openjdk.org/jeps/8303099
Es muss nur schrittweise ausgeliefert werden, und allein dieser PR zur Einführung von Wertklassen/-objekten umfasst schon 200.000 Zeilen.
Es klingt nicht so, als würde jemand sagen, dass es unmöglich sei; eher, dass man einen Elefanten nicht in einem Bissen essen kann.
Wobei man an diesem einen Bein inzwischen schon ziemlich lange nagt.
Wenn das seit 2012 ein gelöstes Konzept ist, gibt es keinen Grund, verschiedene Geschmacksrichtungen von Zustand direkt in die Sprache einzubauen. Die Schienen führen nur nach A oder B, und der Zug wird je nach Ladezustand umgeleitet.
Wenn Konzepte kommen und gehen und dabei Sprachkriege auslösen, ist das ein Zeichen dafür, dass eine Nachfrage existiert, die die Sprache entweder nicht richtig bedient oder nur um den Preis mentaler Überlastung.
Dinge wie
Value,Errorstates,Null,IoExceptions,WeirdOsStatesNeededToHandleUpstairs.https://fsharpforfunandprofit.com/rop/
Um es mit Monty Python zu sagen: Machen wir jetzt bitte weiter.
Integer/int.Das Valhalla-Team hat sich dagegen entschieden, für jeden Typ eine Projektion mit Identität und eine ohne Identität zu haben; stattdessen haben Werttypen grundsätzlich keine Identität, sodass
Integerundintsynonym werden.Das Speicherlayout wird dann je nach Kontext und Optimierungsentscheidung automatisch festgelegt. Deshalb hat sich auch die Bedeutung von
==für primitive Wrapper wieIntegergeändert und hängt nun nicht mehr davon ab, ob man eine „Referenzprojektion“ oder eine „Wertprojektion“ verwendet.Hier ist also nicht passiert, dass optionale Sicherheitsgarantien mit der Begründung „mental zu belastend“ reduziert wurden.
In HN-Kommentaren zu Java/JVM sieht man immer wieder, dass erstaunlich viele Menschen zwar noch das alte Bild der JVM oder von Java im Kopf haben, aber kaum wissen, wie es heute aussieht
Die JVM des Jahres 2026 ist ein sehr gesunder Spitzenprädator. Hat sie Schwächen? Natürlich, aber das Fundament ist extrem gut
Im Beruf nutze ich das aktuelle Java 26 mit Preview-Features, vor allem
StructuredConcurrency, und es ist hervorragend. Selbst aus der Perspektive von jemandem, der in früheren Firmen Haskell und Python benutzt hat, bereue ich das überhaupt nichtIch kenne die neuen Features der letzten Jahre persönlich, aber im tatsächlichen Arbeitsalltag steckt Java buchstäblich noch in der Vergangenheit fest
Ein großer Teil der Kommentare hier ist etwas unfair im Vergleich zu der großartigen laufenden Arbeit und den noch spannenderen JEPs, die kommen werden
Wenn man Java mit einem Kind vergleicht, dann ist es erst ein paar Jahre bei liebevollen Eltern (Sun) aufgewachsen, dann mit den anderen Kindern in die Garage geworfen und von einem bösen Vormund (Oracle) vernachlässigt worden
Bis JDK 8 war es vernachlässigt und ungeliebt, also musste es im Grunde aufholen
Dass man sagt, „jetzt bekommt es endlich Dinge wie Structs oder Value Types“, stimmt zwar, aber sein Wachstum wurde durch riesige, bürokratische und feindselige Unternehmensprozesse gehemmt. Jetzt ist es frei und wird durch die OpenJDK-Familie geliebt
Auch in Zukunft werden wir weiter die Freude an write once, deploy anywhere haben
Eher haben liebevolle Eltern es großgezogen, mussten es wegen finanzieller Probleme in eine Pflegefamilie geben, wo es dann vernachlässigt wurde
Danach wurde es von neuen, liebevollen Eltern, Oracle, adoptiert, und Java ist aufgeblüht und zu einem gesunden, stabilen Erwachsenen geworden
Oracle hat OpenJDK zur Referenzimplementierung gemacht und damit die Open-Source-Umstellung der Plattform vollendet, und auch zuvor proprietäre Werkzeuge wie JFR und Mission Control als Open Source veröffentlicht
Viele Gründungsmitglieder des Sprach-Teams wurden ebenfalls gehalten, was bei solchen Übernahmen ziemlich selten ist, und Java hat sich sowohl bei der Sprache als auch bei der Runtime stark verbessert
Oracle hat Java mit beispielloser Geschwindigkeit vorangebracht und dabei den größten Teil der Abwärtskompatibilität erhalten
.NET habe es „von Anfang an richtig gemacht“, heißt es, aber wenn damit die Trennung und das Neuschreiben von .NET Framework/.NET Core/.NET gemeint ist, ergibt das auch in dieser Diskussion keinen Sinn. .NET konnte von Java lernen und hat trotzdem manches vermasselt
Dasselbe gilt für MySQL. Auf dieser Seite hieß es, es sei „tot“, aber für Leute, die sich wirklich auskennen, wurde es unter Oracle wiederbelebt
Die letzte Java-Version unter Sun erschien 2006, Oracle kaufte Sun 2010, JDK 7 erschien 2011 und JDK 8 2014
Das Team blieb im Großen und Ganzen dasselbe, und der größte Unterschied war, dass Oracle die Vernachlässigung beendete und mehr Geld bereitstellte. Deshalb hat sich das Tempo von Java nach der Übernahme erhöht
Von „Aufholen“ zu sprechen ist fragwürdig, weil unklar ist, zu wem eigentlich aufgeschlossen werden soll. Die einzigen Sprachen, die so populär oder populärer als Java sind, sind JS/TS und Python
Leute, die sagen, Java sei zurückgefallen, vergleichen es meist mit Sprachen, die viel schlechter dastehen als Java. Wer bestimmte Features liebt, übersieht oft, dass die Sprachen, die diese Features haben, nicht wegen dieser Features erfolgreich sind, sondern trotz ihnen schwächeln
Manager mögen schnelle Releases, und ironischerweise waren es technische Führungskräfte schon aus der Sun-Zeit, die auf Vorsicht und darauf pochten, Dinge langsam und richtig zu machen
Ich verstehe die Stimmung, dass Java nicht mehr so populär ist wie 2003, aber das war nicht nur für Java, sondern für das gesamte Software-Ökosystem eine außergewöhnlich einheitliche Zeit, und davor oder danach war es nie wieder so einheitlich
Die Technologie, die heute wirklich write once, deploy anywhere kann, ist WebAssembly. Die JVM hatte ihre Chance und hat verloren
Trotzdem würde ich nicht so weit gehen, zu sagen, Java sei „im Wachstum gehemmt“ worden. Es wurden Entscheidungen getroffen, manche vernünftig, manche nicht, und solche Entscheidungen sind später sehr schwer zu korrigieren
Schon bei C++ sehe ich die Halbkompatibilität mit C persönlich als einen 150-Fuß-Albatros, den man nicht loswerden kann, und viele Versionen seit C++11 bestanden darin, diesen Albatros ein wenig erträglicher zu machen
Dass auf der JVM alle Value Classes als ein einheitlicher L-Typ wie Primitive behandelt werden, halte ich für eine ziemlich elegante Lösung für ein schwieriges Problem
Letztlich geht das alles auf die Entscheidung von Java 2 zurück, Generics aus Gründen der Abwärtskompatibilität mit Type Erasure zu implementieren, und C3 hat dieses Ergebnis gesehen und diesen Weg abgelehnt
Allein über die Evolution der Value Types in Java könnte man wohl schon einen technischen Thriller schreiben
Ich habe die Mailinglisten gelesen und alle zugehörigen Videos gesehen, und ich bin wirklich beeindruckt davon, wie das Design zu etwas zusammengeführt wurde, das immer noch unverkennbar nach Java aussieht
Gleichzeitig wurde viel feiner ausgelotet, was Value Types eigentlich bedeuten und welche Optimierungen wo möglich sind
valuehinzuzufügenBei Wertklassen funktioniert
==im Grunde wiememcmp()Das ist etwas bedauerlich, weil es die Kapselung aufbricht und Implementierungsdetails offenlegt
Client-Code kann abhängig davon verzweigen, wie ein gegebener Wert intern dargestellt ist. In gewisser Hinsicht ist das sogar schlimmer als Identitätsvergleich, denn der legt wenigstens keinen internen Zustand offen
Es ist nicht klassische Objektorientierung in neuer Form, sondern eher ein weiterer Schritt einer aus objektorientierten Ideen hervorgegangenen Sprache in eine post-objektorientierte Welt
Ich nehme an, dass man auf Java-Seite zumindest darüber nachgedacht hat, Padding beim Vergleich auszuschließen oder Padding-Bytes auf 0 zu zwingen
Das müsste auch für Strings funktionieren. Strings werden offensichtlich weiterhin auf dem Heap allokiert, und Zeiger innerhalb einer neuen „Struct“ per
memcmpzu vergleichen ist exakt IdentitätsvergleichJava trennt zwischen dem Prüfen der Identität eines Objekts und dem Prüfen auf Gleichheit.
==prüft grundsätzlich, ob zwei Zeiger gleich sind, und Gleichheit ist ein subjektiver Begriff auf Basis von Interfaces wieequals/hashCodeDeshalb war
new Integer(1000) == new Integer(1000)früherfalse, wird jetzt abertrue, undnew Integer(1000).equals(new Integer(1000))isttrue, währendnew Integer(10) == new Long(10)früherfalsewar und jetzt ein Compilerfehler istIm alten Java wurden Ganzzahlen bis zu einem bestimmten Wert oft durch kanonisierte Typen ersetzt, ich meine irgendwo um 128. Daher kam der Unterschied zwischen 10 und 1000
Jetzt scheinen diese Vergleiche implizit entboxt zu werden. Dass der Vergleich
Integer/Longfrüherfalsewar und jetzt ein Compilerfehler ist, zeigt klar, dass Unboxing im Spiel istMit Variablen bekommt man vielleicht weiterhin das alte Verhalten
Jedenfalls führt der Verlust von Identität bei Wertklassen dazu, dass
==von Zeigergleichheit zu bitweiser Gleichheit wird. Ich hoffe, man löst diese ganzen Randfälle, aber technisch ist das eine breaking changeIch verstehe die Absicht von Wertklassen, aber die Umsetzung ist mangelhaft
Was gibt folgender Code aus?
Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);Bisher war die Antwort eindeutig, aber mit Wertklassen hängt sie davon ab, ob
Pointeine Wertklasse oder eine Referenzklasse ist. Dieses Design schadet also der LesbarkeitDas ist ein Verstoß gegen das Prinzip der Uniformität. In Weinbergs The Psychology of Computer Programming wird Uniformität als psychologisches Prinzip beschrieben, nach dem Benutzer erwarten, dass ähnlich aussehende Dinge sich ähnlich verhalten und unterschiedlich aussehende Dinge sich unterschiedlich verhalten
Wenn eine Programmiersprache an der Verwendungsstelle zwei Syntaxformen zulässt, die fast gleich aussehen, sich semantisch aber unterschiedlich verhalten, steigt die kognitive Last für Leser. Um zu wissen, ob Zuweisung, Gleichheit, Identität und Mutation sich wie bei normalen Referenzobjekten oder wie bei Werten verhalten, muss man die Typdeklaration prüfen oder sich auf Werkzeuge verlassen
Man hätte das beheben können, wenn das Schlüsselwort
valuenicht nur bei der Deklaration, sondern auch bei der Verwendung erforderlich wäre. Also etwavalue Point a = new Point(10, 10);https://openjdk.org/jeps/401
finalNatürlich kann man allein anhand dieser vier Zeilen nicht wissen, dass so etwas passiert, aber dieses Problem gibt es schon heute. Dasselbe passiert, wenn
Pointein Record ista.x = 100;wäre nicht gültig. Record-Typen sind unveränderlichDaher sollte die befürchtete Situation unmöglich sein
Mit einer Schreibweise wie
value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x);wäre sehr viel klarer, dass eine Duplikation/Kopie stattfindet und dass eine Feldänderung sich nicht auf die Felder eines anderen Objekts auswirktIch weiß, dass es in der Java-Welt als unhöflich gilt, die Existenz von .NET anzuerkennen, aber ich frage mich, worin sich das von .NET-Structs unterscheidet
Wenn man Werttypen, Generics-Spezialisierung und Boxing grob überfliegt, sieht es so aus, als hätte man dieselben Entscheidungen getroffen
Während C# C aus Low-Level-Perspektive im Wesentlichen kopiert hat, ist Java das Thema auf höherer Ebene angegangen und hat detailliert analysiert, welche Einschränkungen welche Vorteile bringen
In anderen Sprachen ist die Einteilung in Structs/Klassen binär, aber Java erlaubt feinere Kontrolle, die die Semantik der zugrunde liegenden Domäne widerspiegelt
Und gerade bei Structs zeigt sich, dass es verschiedene Fußkanonen gibt, besonders in parallelen Kontexten
Persönlich würde ich sagen, dass Structs in C/C# veränderlich sind und per Kopie übergeben werden, während Wertklassen unveränderlich sind und als Wert übergeben werden
Ich glaube nicht, dass Stack-Allokation in Java möglich sein wird
Diese falsche Dichotomie nach dem Muster „C#-Structs haben Identität und Mutation, deshalb muss Kopiersemantik bei Zuweisung oder Übergabe exakt definiert werden, was Programmierern ein schwergewichtigeres Modell und der Runtime weniger Freiheit gibt“ passt nicht besonders gut zu dem, was erklärt wird
Es wird zwar keine Identität im Sinne der Referenzsemantik von Java-Klassen geben, aber im Sinne einer eindeutigen Speicherstruktur an einer bestimmten Adresse gibt es natürlich weiterhin Identität. Das ist fast schon Wortklauberei anhand der Java-Terminologie
Fußnote 6, „Wie unterscheidet sich das von
structin C#“, ist ungenauWenn man sieht, dass der Artikel voller KI-generierter Bilder ist, fragt man sich, ob nicht auch beim Schreiben oder zumindest bei der Recherche reichlich Halluzinationen eingeflossen sind
Der Text ist etwas vage und dramatisch, aber zum Glück sind die Originaldokumente ziemlich gut lesbar
Oberste Seite: https://openjdk.org/projects/jdk/28/spec/
JEP-Status: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
Es wäre schön, wenn jemand die entsprechenden Entwicklungen in C#, Swift, Java und Rust verfolgen würde. Alle haben darum konkurriert, mit der Hardware Schritt zu halten, und beeinflussen sich meiner Meinung nach gegenseitig
Persönlich mache ich mir Sorgen, welche Auswirkungen diese Änderungen auf FFI-Memory-Sharing haben werden