Verzegelde klassen en interfaces in Java 15

1. Overzicht

Het uitkomen van Java SE 15 introduceert verzegelde klassen (JEP 360) als preview-functie.

Deze functie gaat over het mogelijk maken van meer fijnmazige overervingscontrole in Java. Door te sealen kunnen klassen en interfaces hun toegestane subtypen definiëren.

Met andere woorden, een klasse of interface kan nu bepalen welke klassen deze kunnen implementeren of uitbreiden. Het is een handige functie voor het modelleren van domeinen en het vergroten van de beveiliging van bibliotheken.

2. Motivatie

Een klassenhiërarchie stelt ons in staat om code te hergebruiken via overerving. De klassenhiërarchie kan echter ook andere doeleinden hebben. Hergebruik van code is geweldig, maar is niet altijd ons primaire doel.

2.1. Mogelijkheden voor modellering

Een alternatief doel van een klassenhiërarchie kan zijn om verschillende mogelijkheden te modelleren die in een domein bestaan.

Stel je bijvoorbeeld een zakelijk domein voor dat alleen werkt met auto's en vrachtwagens, niet met motorfietsen. Bij het maken van het Voertuig abstracte klasse in Java, zouden we alleen moeten kunnen toestaan Auto en Vrachtauto klassen om het uit te breiden. Op die manier willen we ervoor zorgen dat er geen misbruik wordt gemaakt van de Voertuig abstracte klasse binnen ons domein.

In dit voorbeeld zijn we meer geïnteresseerd in de duidelijkheid van het omgaan met bekende subklassen met code dan in het verdedigen tegen alle onbekende subklassen.

Vóór versie 15 ging Java ervan uit dat hergebruik van code altijd een doel is. Elke klasse was uitbreidbaar met een willekeurig aantal subklassen.

2.2. De pakket-private benadering

In eerdere versies bood Java beperkte mogelijkheden op het gebied van overervingsbeheer.

Een laatste klasse kan geen subklassen hebben. Een pakket-privéklasse kan alleen subklassen in hetzelfde pakket hebben.

Door de pakket-privébenadering te gebruiken, hebben gebruikers geen toegang tot de abstracte klasse zonder deze ook uit te breiden:

openbare klasse Voertuigen {abstracte statische klasse Voertuig {privé definitief String registrationNumber; openbaar voertuig (String registrationNumber) {this.registrationNumber = registrationNumber; } public String getRegistrationNumber () {retour registrationNumber; }} openbare statische laatste klasse Auto verlengt voertuig {privé final int numberOfSeats; openbare auto (int numberOfSeats, String registrationNumber) {super (registrationNumber); this.numberOfSeats = numberOfSeats; } openbare int getNumberOfSeats () {return numberOfSeats; }} public static final class Truck verlengt voertuig {private final int loadCapacity; openbare Truck (int loadCapacity, String registrationNumber) {super (registrationNumber); this.loadCapacity = loadCapacity; } public int getLoadCapacity () {return loadCapacity; }}}

2.3. Superclass toegankelijk, niet uitbreidbaar

Een superklasse die is ontwikkeld met een set van zijn subklassen, zou in staat moeten zijn om het beoogde gebruik te documenteren, niet om zijn subklassen te beperken. Ook het hebben van beperkte subklassen zou de toegankelijkheid van zijn superklasse niet moeten beperken.

De belangrijkste motivatie achter gesloten klassen is dus om de mogelijkheid te hebben dat een superklasse breed toegankelijk is, maar niet breed uitbreidbaar.

3. Creatie

De verzegelde functie introduceert een aantal nieuwe modificatoren en clausules in Java: verzegeld, niet verzegeld, en vergunningen.

3.1. Verzegelde interfaces

Om een ​​interface te verzegelen, kunnen we de verzegeld modifier voor zijn declaratie. De vergunningen clausule specificeert vervolgens de klassen die de verzegelde interface mogen implementeren:

openbare verzegelde interface Service staat auto, vrachtwagen toe {int getMaxServiceIntervalInMonths (); standaard int getMaxDistanceBetweenServicesInKilometers () {terugkeer 100000; }}

3.2. Verzegelde klassen

Net als bij interfaces kunnen we klassen verzegelen door hetzelfde toe te passen verzegeld modificator. De vergunningen clausule moet worden gedefinieerd na elke strekt zich uit of werktuigen clausules:

openbare samenvatting verzegelde klasse Voertuigvergunningen Auto, Vrachtwagen {beschermd definitief Tekenreeksregistratienummer; openbaar voertuig (String registrationNumber) {this.registrationNumber = registrationNumber; } public String getRegistrationNumber () {return registrationNumber; }}

Een toegestane subklasse moet een modificator definiëren. Het kan worden verklaard laatste om verdere uitbreidingen te voorkomen:

openbare laatste klasse Truck verlengt Voertuig implementeert Service {private final int loadCapacity; openbare Truck (int loadCapacity, String registrationNumber) {super (registrationNumber); this.loadCapacity = loadCapacity; } public int getLoadCapacity () {return loadCapacity; } @Override public int getMaxServiceIntervalInMonths () {return 18; }}

Een toegestane subklasse kan ook worden gedeclareerd verzegeld. Als we het echter aangeven niet verzegeld, dan staat het open voor verlenging:

openbare niet-verzegelde klasse Auto verlengt Voertuig implementeert Service {private final int numberOfSeats; openbare auto (int numberOfSeats, String registrationNumber) {super (registrationNumber); this.numberOfSeats = numberOfSeats; } openbare int getNumberOfSeats () {return numberOfSeats; } @Override public int getMaxServiceIntervalInMonths () {return 12; }}

3.4. Beperkingen

Een verzegelde klasse legt drie belangrijke beperkingen op aan de toegestane subklassen:

  1. Alle toegestane subklassen moeten tot dezelfde module behoren als de verzegelde klasse.
  2. Elke toegestane subklasse moet de verzegelde klasse expliciet uitbreiden.
  3. Elke toegestane subklasse moet een modifier definiëren: laatste, verzegeld, of niet verzegeld.

4. Gebruik

4.1. De traditionele manier

Bij het verzegelen van een klasse stellen we de clientcode in staat om duidelijk te redeneren over alle toegestane subklassen.

De traditionele manier om over een subklasse te redeneren, is het gebruik van een set als-anders verklaringen en instantie van controleert:

if (voertuiginstantie van auto) {retour ((auto) voertuig) .getNumberOfSeats (); } else if (voertuiginstantie van Truck) {return ((Truck) voertuig) .getLoadCapacity (); } else {gooi nieuwe RuntimeException ("Onbekende instantie van voertuig"); }

4.2. Patroonaanpassing

Door patroonaanpassing toe te passen, kunnen we de extra klasse-cast vermijden, maar we hebben nog steeds een set i nodigf-anders uitspraken:

if (voertuiginstantie van auto auto) {return car.getNumberOfSeats (); } else if (voertuiginstantie van vrachtwagenvrachtwagen) {retourneer vrachtwagen.getLoadCapacity (); } else {gooi nieuwe RuntimeException ("Onbekende instantie van voertuig"); }

Met behulp van if-anders maakt het moeilijk voor de compiler om te bepalen dat we alle toegestane subklassen hebben gedekt. Om die reden gooien we een RuntimeException.

In toekomstige versies van Java kan de clientcode een schakelaar statement in plaats van if-anders (JEP 375).

Door typetestpatronen te gebruiken, kan de compiler controleren of elke toegestane subklasse wordt gedekt. Er is dus geen behoefte meer aan een standaard clausule / case.

4. Compatibiliteit

Laten we nu eens kijken naar de compatibiliteit van verzegelde klassen met andere Java-taalfuncties zoals records en de reflectie-API.

4.1. Records

Verzegelde klassen werken heel goed met records. Omdat records impliciet definitief zijn, is de verzegelde hiërarchie nog beknopter. Laten we proberen ons klasvoorbeeld te herschrijven met behulp van records:

openbare verzegelde interface Voertuigvergunningen Auto, Vrachtwagen {String getRegistrationNumber (); } openbaar record Car (int numberOfSeats, String registrationNumber) implementeert voertuig {@Override public String getRegistrationNumber () {return registrationNumber; } openbare int getNumberOfSeats () {return numberOfSeats; }} openbaar record Truck (int loadCapacity, String registrationNumber) implementeert voertuig {@Override public String getRegistrationNumber () {return registrationNumber; } public int getLoadCapacity () {return loadCapacity; }}

4.2. Reflectie

Verzegelde klassen worden ook ondersteund door de reflectie-API, waar twee openbare methoden zijn toegevoegd aan het java.lang.Class:

  • De is verzegeld methode retourneert waar als de gegeven klasse of interface is verzegeld.
  • Methode toegestane subklassen geeft een reeks objecten terug die alle toegestane subklassen vertegenwoordigen.

We kunnen deze methoden gebruiken om beweringen te maken die zijn gebaseerd op ons voorbeeld:

Assertions.assertThat (truck.getClass (). IsSealed ()). IsEqualTo (false); Assertions.assertThat (truck.getClass (). GetSuperclass (). IsSealed ()). IsEqualTo (true); Assertions.assertThat (truck.getClass (). GetSuperclass (). AllowSubclasses ()) .contains (ClassDesc.of (truck.getClass (). GetCanonicalName ()));

5. Conclusie

In dit artikel hebben we verzegelde klassen en interfaces onderzocht, een preview-functie in Java SE 15. We behandelden het maken en gebruiken van verzegelde klassen en interfaces, evenals hun beperkingen en compatibiliteit met andere taalfuncties.

In de voorbeelden behandelden we het creëren van een verzegelde interface en een verzegelde klasse, het gebruik van de verzegelde klasse (met en zonder patroonovereenkomst) en de compatibiliteit van verzegelde klassen met records en de reflectie-API.

Zoals altijd is de volledige broncode beschikbaar op GitHub.