De diamanten operator (<>), geïntroduceerd in Java 7, ondersteunde aanvankelijk alleen concrete class-instantie-creaties terwijl anonieme interne klassen expliciet werden uitgesloten. Toen ontwikkelaars pogingen ondernamen om constructies zoals new Comparable<String>() { ... } te maken, wees de compiler de diamanten variant new Comparable<>() { ... } af omdat anonieme klassen typeleden konden introduceren die verwezen naar inferentie-typeparameters, wat mogelijk onbetrouwbare typesystemen zou creëren.
Het kernprobleem draaide om niet-aangevende types. Anonieme klassen kunnen methoden of velden declareren waarvan de typen van de typeparameters van de klasse afhankelijk zijn. Als de compiler een complexe snijdingstype voor de diamant inferentie, zoals geïllustreerd in het problematische scenario waar een anonieme klasse void foo(Box<T> t) {} declareert, kan het type T een gevangen wildcard vertegenwoordigen die niet kan worden uitgedrukt in de brontekst. Dit creëerde een scenario waarin de API van de anonieme klasse types bevatte die onmogelijk te benoemen of te controleren waren op het niveau van de brontekst, wat in strijd was met de fundamentele vereiste van Java dat alle types in publieke API's benoembaar moeten zijn.
Java 9 loste dit op via JEP 213 door denotabele types analyse te implementeren. De compiler verifieert nu dat het inferentietype voor de instantiatie van de anonieme klasse benoembaar is—dat wil zeggen, uitdrukbaar met behulp van Java-type-syntaxis. Het volgende voorbeeld toont legaal gebruik aan:
// Geldig in Java 9+ Comparator<String> c = new Comparator<>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } };
Als de inferentie een complex type produceert dat wildcards of snijdingen bevat die niet benoembaar zijn, valt de compiler terug op het vereisen van expliciete type-argumenten. Dit garandeert typeveiligheid terwijl de beknopte syntaxis voor veelvoorkomende gevallen wordt toegestaan.
In een financieel handelsplatform dat is gebouwd op Java 8, onderhield het ontwikkelingsteam duizenden evenementhandlers. Deze handlers maakten overal in de ordermatching-engine gebruik van anonieme implementaties van Comparator<TradeEvent> en Predicate<MarketData>, wat expliciete type-argumenten vereiste en aanzienlijke visuele ruis creëerde tijdens codebeoordelingen.
Het team overwoog drie benaderingen om boilerplate te verminderen. De eerste benadering hield in om alle anonieme klassen om te zetten naar lambda-expressies. Hoewel dit de omvang voor eenvoudige gevallen verminderde, vereisten veel handlers private hulpfuncties of exception-handlingblokken die de mogelijkheden van lambda overschreden. Deze beperking dwong tot ongemakkelijke refactoring naar benoembare interne klassen, wat het aantal klassen verhoogde en de nabijheid van gedrag verminderde.
De tweede benadering stelde voor om de expliciete type-argumenten te behouden. Dit behield de volledige functionaliteit en werkte met de bestaande Java 8 infrastructuur, maar bleef de onderhoudsdruk voortzetten. Ontwikkelaars kregen vaak te maken met samenvoegconflicten bij het wijzigen van typehandtekeningen, en de overbodige declaraties verhoogden de cognitieve belasting tijdens foutopsporingssessies.
De derde benadering stelde voor om te upgraden naar Java 9 om gebruik te maken van de ondersteuning van de diamanten operator voor anonieme klassen. Na beoordeling van de migratiekosten tegen productiviteitswinst, koos het team voor de Java 9 upgrade omdat het platform toch integratie met het Jigsaw module-systeem vereiste. De denotabele types-analyse stelde hen in staat om new Comparator<>() { public int compare(TradeEvent a, TradeEvent b) { ... } } te schrijven terwijl de compiler verifieerde dat TradeEvent een denotabel type vertegenwoordigde.
Deze wijziging verminderde de gemiddelde definitie van een handler van vier regels naar één, waarbij ongeveer 2.400 regels overbodige type-declaraties werden geëlimineerd. Dienovereenkomstig nam het aantal samenvoegconflicten in generieke modules aanzienlijk af door de noodzaak te verwijderen om expliciete type-argumenten over functietakken te synchroniseren. De ontwikkelingsefficiëntie verbeterde met vijftien procent in de daaropvolgende kwartalen door verminderde refactoringslast.
Waarom faalt de diamanten operator bij het afleiden van type-argumenten voor generieke constructeurs in ruwe types?
Bij het instantiëren van een ruw type zoals new ArrayList()<>, kan de diamanten operator geen type-argumenten afleiden omdat ruwe types de generieke informatie volledig wissen. De compiler beschouwt het ruwe type als geen typeparameters hebbend, waardoor inferentie onmogelijk wordt aangezien de constructiehandtekening zelf parametrisering verliest. Kandidaten verwarren dit vaak met waarschuwingen voor niet-gecontroleerde conversies, maar het fundamentele probleem betreft de volledige uitwissing van generieke metadata in ruwe type-contexten, niet slechts niet-gecontroleerde bewerkingen.
Hoe beïnvloedt de interactie tussen poly-expressies en de diamanten operator de methode-overlaadresolutie?
De diamanten operator creëert een poly-expressie waarvan het type afhankelijk is van de toewijzingscontext. In contexten van methode-aanroepen zoals process(new ArrayList<>()), moet de compiler het doeltype bepalen uit de formele parameters van de methode voordat de type-inferentie is voltooid. Dit creëert een bidirectionele afhankelijkheid: de toepasbaarheid van de methode hangt af van het afgeleide type, maar het afgeleide type hangt af van het doeltype. De compiler lost dit op via constraint-generatie en incorporatiefases, waarbij mogelijk andere overloads worden geselecteerd dan die zouden optreden met expliciete type-argumenten. Kandidaten zien vaak over het hoofd dat overladen resolutie plaatsvindt vóór de volledige type-inferentie, wat leidt tot verrassende compile-tijdfouten wanneer meerdere overloads zouden kunnen overeenkomen.
Wat onderscheidt de beperking van denotabele types van de vereiste voor reifiable types bij het maken van arrays?
Hoewel beide beperkingen bepaalde generieke bewerkingen voorkomen, zorgen denotabele types (relevant voor inferentie met de diamanten operator) ervoor dat types kunnen worden uitgedrukt in de brontekst, terwijl reifiable types (relevant voor new T[10]) runtime type-informatie vereisen. Een type zoals List<String> is denotabel maar niet reifiable. Kandidaten verwarren deze beperkingen vaak, in de veronderstelling dat niet-denotabele types runtimeveiligheidsrisico's bieden die vergelijkbaar zijn met array-opslaaf-excepties. In werkelijkheid compromitteren niet-denotabele types de expressiebaarheid van types op brontekstsniveau en de consistentie van de API, terwijl niet-reifiable types de runtime typeveiligheid compromitteren. Het is cruciaal om dit onderscheid te begrijpen bij het ontwerpen van generieke API's die compatibel moeten blijven met zowel anonieme klassen als op arrays gebaseerde legacy-code.