Forseglede klasser og grænseflader i Java 15

1. Oversigt

Frigivelsen af Java SE 15 introducerer forseglede klasser (JEP 360) som en preview-funktion.

Denne funktion handler om at muliggøre mere finkornet arvskontrol i Java. Forsegling tillader klasser og grænseflader at definere deres tilladte undertyper.

Med andre ord kan en klasse eller en grænseflade nu definere, hvilke klasser der kan implementere eller udvide den. Det er en nyttig funktion til domæne modellering og øge sikkerheden i biblioteker.

2. Motivation

Et klassehierarki giver os mulighed for at genbruge kode via arv. Klassehierarkiet kan dog også have andre formål. Genbrug af koder er fantastisk, men det er ikke altid vores primære mål.

2.1. Modelleringsmuligheder

Et alternativt formål med et klassehierarki kan være at modellere forskellige muligheder, der findes i et domæne.

Forestil dig et eksempel på et forretningsdomæne, der kun fungerer med biler og lastbiler, ikke motorcykler. Når du opretter Køretøj abstrakt klasse i Java, skulle vi kun kunne tillade det Bil og Lastbil klasser for at udvide det. På den måde vil vi sikre, at der ikke sker misbrug af Køretøj abstrakt klasse inden for vores domæne.

I dette eksempel er vi mere interesserede i klarheden i kodehåndtering af kendte underklasser end at forsvare sig mod alle ukendte underklasser.

Før version 15 antog Java, at genbrug af kode altid er et mål. Hver klasse kunne udvides med et vilkårligt antal underklasser.

2.2. Pakken-privat tilgang

I tidligere versioner leverede Java begrænsede muligheder inden for arvskontrol.

En afsluttende klasse kan ikke have nogen underklasser. En pakke-privat klasse kan kun have underklasser i den samme pakke.

Ved hjælp af den pakke-private tilgang kan brugerne ikke få adgang til den abstrakte klasse uden også at tillade dem at udvide den:

offentlig klasse køretøjer {abstrakt statisk klasse køretøj {privat endelig String registrationNumber; public Vehicle (String registrationNumber) {this.registrationNumber = registrationNumber; } public String getRegistrationNumber () {return registrationNumber; }} offentlig statisk endelig klasse Bil udvider køretøj {privat endelig int antalOfSeats; public Car (int numberOfSeats, String registrationNumber) {super (registrationNumber); this.numberOfSeats = numberOfSeats; } public int getNumberOfSeats () {return numberOfSeats; }} offentlig statisk slutklasse Truck udvider køretøj {privat endelig int loadCapacity; offentlig lastbil (int loadCapacity, String registrationNumber) {super (registrationNumber); this.loadCapacity = loadCapacity; } public int getLoadCapacity () {return loadCapacity; }}}

2.3. Superklasse tilgængelig, ikke udvidelig

En superklasse, der er udviklet med et sæt af dens underklasser, skal kunne dokumentere den tilsigtede anvendelse og ikke begrænse dens underklasser. At have begrænsede underklasser bør heller ikke begrænse tilgængeligheden af ​​dens superklasse.

Således er den vigtigste motivation bag lukkede klasser at have muligheden for, at en superklasse er bredt tilgængelig, men ikke bredt udvidelig.

3. Skabelse

Den forseglede funktion introducerer et par nye modifikatorer og klausuler i Java: forseglet, ikke-forseglet, og tillader det.

3.1. Forseglede grænseflader

For at forsegle en grænseflade kan vi anvende forseglet modifikator til dens erklæring. Det tillader det klausul specificerer derefter de klasser, der er tilladt at implementere den forseglede grænseflade:

offentlig forseglet grænseflade Service tillader bil, lastbil {int getMaxServiceIntervalInMonths (); standard int getMaxDistanceBetweenServicesInKilometers () {return 100000; }}

3.2. Forseglede klasser

Svarende til grænseflader kan vi forsegle klasser ved at anvende det samme forseglet modifikator. Det tillader det klausul skal defineres efter enhver strækker sig eller redskaber klausuler:

offentlig abstrakt forseglet klasse Køretøjstilladelser bil, lastbil {beskyttet endelig strengregistreringsnummer; public Vehicle (String registrationNumber) {this.registrationNumber = registrationNumber; } public String getRegistrationNumber () {return registrationNumber; }}

En tilladt underklasse skal definere en modifikator. Det kan blive erklæret endelig for at forhindre yderligere udvidelser:

offentlig slutklasse Truck udvider køretøjsimplementering Service {privat endelig int loadCapacity; offentlig lastbil (int loadCapacity, String registrationNumber) {super (registrationNumber); this.loadCapacity = loadCapacity; } public int getLoadCapacity () {return loadCapacity; } @Override public int getMaxServiceIntervalInMonths () {return 18; }}

En tilladt underklasse kan også erklæres forseglet. Men hvis vi erklærer det ikke-forseglet, så er den åben for udvidelse:

offentlig ikke-forseglet klasse Bil udvider køretøjsimplementering Service {private final int numberOfSeats; public Car (int numberOfSeats, String registrationNumber) {super (registrationNumber); this.numberOfSeats = numberOfSeats; } public int getNumberOfSeats () {return numberOfSeats; } @Override public int getMaxServiceIntervalInMonths () {return 12; }}

3.4. Begrænsninger

En forseglet klasse pålægger de tilladte underklasser tre vigtige begrænsninger:

  1. Alle tilladte underklasser skal tilhøre det samme modul som den forseglede klasse.
  2. Hver tilladt underklasse skal eksplicit udvide den forseglede klasse.
  3. Hver tilladt underklasse skal definere en modifikator: endelig, forseglet, eller ikke-forseglet.

4. Anvendelse

4.1. Den traditionelle måde

Når vi forsegler en klasse, sætter vi klientkoden i stand til at resonnere klart om alle tilladte underklasser.

Den traditionelle måde at ræsonnere om underklasse på er at bruge et sæt hvis ellers udsagn og forekomst af kontrol:

hvis (køretøjsinstans af bil) {retur ((bil) køretøj) .getNumberOfSeats (); } ellers hvis (køretøjsinstans af lastbil) {retur ((lastbil) køretøj) .getLoadCapacity (); } ellers {kast ny RuntimeException ("Ukendt forekomst af køretøj"); }

4.2. Mønster Matching

Ved at anvende mønstermatchning kan vi undgå den ekstra klasse-rollebesætning, men vi har stadig brug for et sæt if-ellers udsagn:

hvis (køretøjsinstans af bilbil) {return car.getNumberOfSeats (); } ellers hvis (køretøjsinstans af lastbil) {retur truck.getLoadCapacity (); } ellers {kast ny RuntimeException ("Ukendt forekomst af køretøj"); }

Brug af if-ellers gør det vanskeligt for compileren at bestemme, at vi dækkede alle tilladte underklasser. Af den grund kaster vi en RuntimeException.

I fremtidige versioner af Java vil klientkoden kunne bruge en kontakt erklæring i stedet for if-ellers (JEP 375).

Ved at bruge typetestmønstre vil compileren være i stand til at kontrollere, at alle tilladte underklasser er dækket. Der vil således ikke være behov for mere Standard klausul / sag.

4. Kompatibilitet

Lad os nu se på kompatibiliteten af ​​forseglede klasser med andre Java-sprogfunktioner som poster og refleksions-API.

4.1. Optegnelser

Forseglede klasser fungerer meget godt med optegnelser. Da poster er implicit endelige, er det forseglede hierarki endnu mere kortfattet. Lad os prøve at omskrive vores klasseeksempel ved hjælp af poster:

offentlig forseglet grænseflade Køretøjstilladelser Bil, lastbil {String getRegistrationNumber (); } offentlig rekord Bil (int numberOfSeats, String registrationNumber) implementerer køretøj {@Override public String getRegistrationNumber () {return registrationNumber; } public int getNumberOfSeats () {return numberOfSeats; }} offentlig post Truck (int loadCapacity, String registrationNumber) implementerer køretøj {@Override public String getRegistrationNumber () {return registrationNumber; } public int getLoadCapacity () {return loadCapacity; }}

4.2. Afspejling

Forseglede klasser understøttes også af refleksions-API'en, hvor to offentlige metoder er blevet føjet til java.lang.Klasse:

  • Det er forseglet metoden vender tilbage rigtigt hvis den givne klasse eller grænseflade er forseglet.
  • Metode tilladt Underklasser returnerer en matrix af objekter, der repræsenterer alle de tilladte underklasser.

Vi kan bruge disse metoder til at skabe påstande, der er baseret på vores eksempel:

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

5. Konklusion

I denne artikel udforskede vi forseglede klasser og grænseflader, en preview-funktion i Java SE 15. Vi dækkede oprettelsen og brugen af ​​forseglede klasser og grænseflader samt deres begrænsninger og kompatibilitet med andre sprogfunktioner.

I eksemplerne dækkede vi oprettelsen af ​​en forseglet grænseflade og en forseglet klasse, brugen af ​​den forseglede klasse (med og uden mønstertilpasning) og forseglede klassekompatibilitet med poster og reflektions-API.

Som altid er den komplette kildekode tilgængelig på GitHub.