Forseglede klasser og grensesnitt i Java 15

1. Oversikt

Utgivelsen av Java SE 15 introduserer forseglede klasser (JEP 360) som forhåndsvisning.

Denne funksjonen handler om å muliggjøre mer finkornet arvskontroll i Java. Forsegling tillater klasser og grensesnitt å definere de tillatte undertypene.

Med andre ord kan en klasse eller et grensesnitt nå definere hvilke klasser som kan implementere eller utvide den. Det er en nyttig funksjon for domenemodellering og økt sikkerhet for biblioteker.

2. Motivasjon

Et klassehierarki gjør det mulig for oss å gjenbruke kode via arv. Klassehierarkiet kan imidlertid også ha andre formål. Gjenbruk av koder er bra, men er ikke alltid vårt primære mål.

2.1. Modelleringsmuligheter

Et alternativt formål med et klassehierarki kan være å modellere ulike muligheter som finnes i et domene.

Tenk deg et forretningsdomene som bare fungerer med biler og lastebiler, ikke motorsykler. Når du oppretter Kjøretøy abstrakt klasse i Java, burde vi bare kunne tillate det Bil og Lastebil klasser for å utvide den. På den måten ønsker vi å sikre at det ikke blir noe misbruk av Kjøretøy abstrakt klasse innenfor vårt domene.

I dette eksemplet er vi mer interessert i klarheten i kodehåndtering av kjente underklasser enn å forsvare oss mot alle ukjente underklasser.

Før versjon 15 antok Java at gjenbruk av kode alltid er et mål. Hver klasse kunne utvides med et hvilket som helst antall underklasser.

2.2. Pakken-privat tilnærming

I tidligere versjoner ga Java begrensede muligheter innen arvskontroll.

En siste klasse kan ikke ha noen underklasser. En pakke-privat klasse kan bare ha underklasser i samme pakke.

Ved å bruke den pakke-private tilnærmingen kan brukerne ikke få tilgang til abstraktklassen uten å tillate dem å utvide den:

public class Vehicles {abstract static class Vehicle {private final String registrationNumber; public Vehicle (String registrationNumber) {this.registrationNumber = registrationNumber; } public String getRegistrationNumber () {return registrationNumber; }} offentlig statisk sluttklasse Car extends Vehicle {private final int numberOfSeats; public Car (int numberOfSeats, String registrationNumber) {super (registrationNumber); this.numberOfSeats = numberOfSeats; } public int getNumberOfSeats () {return numberOfSeats; }} offentlig statisk sluttklasse Truck utvider Vehicle {private final int loadCapacity; offentlig lastebil (int loadCapacity, String registrationNumber) {super (registrationNumber); this.loadCapacity = loadCapacity; } public int getLoadCapacity () {return loadCapacity; }}}

2.3. Superklasse tilgjengelig, ikke utvidbar

En superklasse som er utviklet med et sett med underklasser, skal kunne dokumentere den tiltenkte bruken, ikke begrense underklassene. Å ha begrensede underklasser bør heller ikke begrense tilgjengeligheten til superklassen.

Dermed er hovedmotivasjonen bak forseglede klasser å ha muligheten for en superklasse å være allment tilgjengelig, men ikke bredt utvidbar.

3. Skapelse

Den forseglede funksjonen introduserer et par nye modifikatorer og klausuler i Java: forseglet, ikke forseglet, og tillatelser.

3.1. Forseglede grensesnitt

For å forsegle et grensesnitt, kan vi bruke forseglet modifikator til erklæringen. De tillatelser klausul spesifiserer deretter klassene som har lov til å implementere det forseglede grensesnittet:

offentlig forseglet grensesnitt Tjenestetillatelser Bil, lastebil {int getMaxServiceIntervalInMonths (); standard int getMaxDistanceBetweenServicesInKilometers () {return 100000; }}

3.2. Forseglede klasser

I likhet med grensesnitt kan vi forsegle klasser ved å bruke det samme forseglet modifikator. De tillatelser klausul bør defineres etter noen strekker eller redskaper klausuler:

offentlig abstrakt forseglet klasse Kjøretøystillatelser Bil, lastebil {beskyttet slutt StrengregistreringNummer; public Vehicle (String registrationNumber) {this.registrationNumber = registrationNumber; } public String getRegistrationNumber () {return registrationNumber; }}

En tillatt underklasse må definere en modifikator. Det kan bli erklært endelig for å forhindre ytterligere utvidelser:

offentlig sluttklasse Truck utvider kjøretøyredskaper Service {private final int loadCapacity; offentlig lastebil (int loadCapacity, String registrationNumber) {super (registrationNumber); this.loadCapacity = loadCapacity; } public int getLoadCapacity () {retur loadCapacity; } @Override public int getMaxServiceIntervalInMonths () {retur 18; }}

En tillatt underklasse kan også bli erklært forseglet. Imidlertid hvis vi erklærer det ikke forseglet, så er den åpen for utvidelse:

offentlig ikke-forseglet klasse Bil utvider Kjøretøyredskaper 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 () {retur 12; }}

3.4. Begrensninger

En forseglet klasse pålegger de tillatte underklassene tre viktige begrensninger:

  1. Alle tillatte underklasser må tilhøre samme modul som den forseglede klassen.
  2. Hver tillatte underklasse må eksplisitt utvide den forseglede klassen.
  3. Hver tillatte underklasse må definere en modifikator: endelig, forseglet, eller ikke forseglet.

4. Bruk

4.1. Den tradisjonelle måten

Når vi forsegler en klasse, gjør vi klientkoden i stand til å resonnere tydelig om alle tillatte underklasser.

Den tradisjonelle måten å resonnere om underklasse er å bruke et sett med hvis-annet uttalelser og tilfelle av sjekker:

if (vehicle instanceof Car) {return ((Car) vehicle) .getNumberOfSeats (); } annet hvis (kjøretøyforekomst av lastebil) {retur ((lastebil) kjøretøy) .getLoadCapacity (); } annet {kast ny RuntimeException ("Ukjent forekomst av kjøretøy"); }

4.2. Mønster Matching

Ved å bruke mønstermatching kan vi unngå den ekstra klassekasten, men vi trenger fortsatt et sett med if-annet uttalelser:

if (vehicle instanceof Car car) {return car.getNumberOfSeats (); } annet hvis (kjøretøyinstans av lastebil) {retur truck.getLoadCapacity (); } annet {kast ny RuntimeException ("Ukjent forekomst av kjøretøy"); }

Bruke if-annet gjør det vanskelig for kompilatoren å fastslå at vi dekket alle tillatte underklasser. Av den grunn kaster vi a RuntimeException.

I fremtidige versjoner av Java vil klientkoden kunne bruke en bytte om uttalelse i stedet for if-annet (JEP 375).

Ved å bruke typetestmønstre vil kompilatoren kunne kontrollere at alle tillatte underklasser er dekket. Dermed vil det ikke lenger være behov for en misligholde klausul / sak.

4. Kompatibilitet

La oss nå se på kompatibiliteten til forseglede klasser med andre Java-språkfunksjoner som poster og refleksjon API.

4.1. Records

Forseglede klasser fungerer veldig bra med poster. Siden poster er implisitt endelige, er det forseglede hierarkiet enda mer kortfattet. La oss prøve å skrive om klasseeksemplet vårt ved hjelp av poster:

offentlig forseglet grensesnitt Kjøretøystillatelser Bil, lastebil {String getRegistrationNumber (); } public record Car (int numberOfSeats, String registrationNumber) implementerer Vehicle {@Override public String getRegistrationNumber () {return registrationNumber; } public int getNumberOfSeats () {return numberOfSeats; }} offentlig postbil (int loadCapacity, String registrationNumber) implementerer kjøretøy {@Override public String getRegistrationNumber () {return registrationNumber; } offentlig int getLoadCapacity () {retur loadCapacity; }}

4.2. Speilbilde

Forseglede klasser støttes også av refleksjon API, der to offentlige metoder er lagt til java.lang.Klasse:

  • De er forseglet metoden returnerer ekte hvis den gitte klassen eller grensesnittet er forseglet.
  • Metode tillatt Underklasser returnerer en rekke objekter som representerer alle tillatte underklasser.

Vi kan bruke disse metodene for å lage påstander som er basert på vårt 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. Konklusjon

I denne artikkelen utforsket vi forseglede klasser og grensesnitt, en forhåndsvisningsfunksjon i Java SE 15. Vi dekket opprettelsen og bruken av forseglede klasser og grensesnitt, samt begrensninger og kompatibilitet med andre språkfunksjoner.

I eksemplene dekket vi opprettelsen av et forseglet grensesnitt og en forseglet klasse, bruken av den forseglede klassen (med og uten mønstermatching), og forseglede klassekompatibilitet med poster og refleksjon API.

Som alltid er den komplette kildekoden tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found