Java >> Java tutorial >  >> Tag >> class

Arbejde med grænseflader og indre klasser i Java

Dette kapitel viser dig adskillige avancerede teknikker, der er almindeligt anvendte. På trods af deres mindre indlysende karakter, bliver du nødt til at mestre dem for at færdiggøre din Java-værktøjskasse.

I dette kapitel

  • Grænseflader
  • Objektkloning
  • Grænseflader og tilbagekald
  • Indre klasser
  • Fuldmagter

Du har nu set alle de grundlæggende værktøjer til objektorienteret programmering i Java. Dette kapitel viser dig adskillige avancerede teknikker, der er almindeligt anvendte. På trods af deres mindre indlysende karakter, bliver du nødt til at mestre dem for at færdiggøre din Java-værktøjskasse.

Den første teknik, kaldet grænseflader , er en måde at beskrive hvad på klasser skal klare sig uden at specificere hvordan de burde gøre det. En klasse kan implementere en eller flere grænseflader. Du kan derefter bruge objekter fra disse implementeringsklasser, når der kræves overensstemmelse med grænsefladen. Efter at vi har dækket grænseflader, begynder vi at klone et objekt (eller dyb kopiering, som det nogle gange kaldes). En klon af et objekt er et nyt objekt, der har samme tilstand som originalen. Især kan du ændre klonen uden at påvirke originalen.

Dernæst går vi videre til mekanismen for indre klasser . Indre klasser er teknisk set noget komplekse - de er defineret inde i andre klasser, og deres metoder kan få adgang til felterne i den omgivende klasse. Indre klasser er nyttige, når du designer samlinger af samarbejdende klasser. Især giver indre klasser dig mulighed for at skrive kortfattet, professionelt udseende kode til at håndtere GUI-begivenheder.

Dette kapitel afsluttes med en diskussion af proxies , objekter, der implementerer vilkårlige grænseflader. En proxy er en meget specialiseret konstruktion, der er nyttig til at bygge værktøjer på systemniveau. Du kan roligt springe det afsnit over ved første læsning.

6.1. Grænseflader

I programmeringssproget Java er en grænseflade ikke en klasse, men et sæt krav for de klasser, der ønsker at tilpasse sig grænsefladen.

Typisk siger leverandøren af ​​nogle tjenester:"Hvis din klasse er i overensstemmelse med en bestemt grænseflade, så udfører jeg tjenesten." Lad os se på et konkret eksempel. sorteringen metoden for Arrays klasse lover at sortere et array af objekter, men under én betingelse:Objekterne skal tilhøre klasser, der implementerer Comparable grænseflade.

Her er hvad Comparable grænsefladen ser sådan ud:

public interface Comparable
{
   int compareTo(Object other);
}

Dette betyder, at enhver klasse, der implementerer Comparable interface er påkrævet for at have en compareTo metode, og metoden skal tage et Objekt parameter og returnerer et heltal.

BEMÆRK

Fra Java SE 5.0 er Comparable grænsefladen er blevet forbedret til at være en generisk type.

public interface Comparable<T>
{
   int compareTo(T other); // parameter has type T
}

For eksempel en klasse, der implementerer Comparable skal levere en metode

int compareTo(Employee other)

Du kan stadig bruge den "rå" Sammenlignelige type uden en type-parameter, men så skal du manuelt caste parameteren for compareTo metode til den ønskede type.

Alle metoder i en grænseflade er automatisk offentlige . Af den grund er det ikke nødvendigt at angive søgeordet offentlig når du erklærer en metode i en grænseflade.

Selvfølgelig er der et yderligere krav, som grænsefladen ikke kan stave:Når du kalder x.compareTo(y) , sammenlign til metode skal faktisk være i stand til at sammenligne de to objekter og returnerer en indikation om x eller y er større. Metoden formodes at returnere et negativt tal hvis x er mindre end y , nul hvis de er ens, og et positivt tal ellers.

Denne særlige grænseflade har en enkelt metode. Nogle grænseflader har flere metoder. Som du vil se senere, kan grænseflader også definere konstanter. Hvad der dog er vigtigere er, hvilke grænseflader ikke kan levere. Interfaces har aldrig instansfelter, og metoderne implementeres aldrig i grænsefladen. At levere instansfelter og metodeimplementeringer er opgaven for de klasser, der implementerer grænsefladen. Du kan tænke på en grænseflade som at ligne en abstrakt klasse uden instansfelter. Der er dog nogle forskelle mellem disse to begreber – vi ser på dem senere i nogle detaljer.

Antag nu, at vi vil bruge sorteringen metoden for Arrays klasse for at sortere et array af medarbejder genstande. Derefter medarbejderen klasse skal implementere den Sammenlignelige grænseflade.

For at få en klasse til at implementere en grænseflade, udfører du to trin:

  1. Du erklærer, at din klasse har til hensigt at implementere den givne grænseflade.
  2. Du angiver definitioner for alle metoder i grænsefladen.

For at erklære, at en klasse implementerer en grænseflade, skal du bruge implementerne søgeord:

class Employee implements Comparable

Selvfølgelig, nu medarbejderen klasse skal levere compareTo metode. Lad os antage, at vi vil sammenligne medarbejdere ud fra deres løn. Her er en implementering af compareTo metode:

public int compareTo(Object otherObject)
{
   Employee other = (Employee) otherObject;
    return Double.compare(salary, other.salary);
}

Her bruger vi den statiske Double.compare metode, der returnerer en negativ, hvis det første argument er mindre end det andet argument, 0 hvis de er ens, og en positiv værdi ellers.

FORSIGTIG

I grænsefladeerklæringen er compareTo metoden blev ikke erklæret offentlig fordi alle metoder i en grænseflade er automatisk offentlige. Men når du implementerer grænsefladen, skal du erklære metoden som offentlig . Ellers antager compileren, at metoden har pakkesynlighed - standarden for en klasse . Compileren klager derefter over, at du forsøger at give et svagere adgangsprivilegium.

Fra Java SE 5.0 kan vi gøre det lidt bedre. Vi implementerer den Sammenlignelige grænsefladetype i stedet.

class Employee implements Comparable<Employee>
{
   public int compareTo(Employee other)
   {
      return Double.compare(salary, other.salary);
   }
   . . .
}

Bemærk, at Objektets grimme rollebesætning parameter er gået væk.

TIP

sammenlign med metoden for Sammenlignelig interface returnerer et heltal. Hvis objekterne ikke er ens, er det lige meget, hvilken negativ eller positiv værdi du returnerer. Denne fleksibilitet kan være nyttig, når du sammenligner heltalsfelter. Antag for eksempel, at hver medarbejder har et unikt heltal id og du vil sortere efter medarbejder-id-nummer. Så kan du blot returnere id - andet.id . Denne værdi vil være en negativ værdi, hvis det første ID-nummer er mindre end det andet, 0 hvis de er det samme ID og ellers en positiv værdi. Der er dog en advarsel:Heltallenes rækkevidde skal være lille nok til at subtraktionen ikke løber over. Hvis du ved, at ID'erne ikke er negative, eller at deres absolutte værdi højst er (Integer.MAX_VALUE - 1) / 2 , du er i sikkerhed.

Selvfølgelig virker subtraktionstricket ikke for flydende kommatal. Forskellen løn - anden.løn kan afrunde til 0 hvis lønningerne ligger tæt på hinanden, men ikke er identiske. Kaldet Double.compare(x, y) returnerer blot -1 hvis x eller 1 hvis x> 0 .

Nu så du, hvad en klasse skal gøre for at benytte sig af sorteringstjenesten – den skal implementere en compareTo metode. Det er udmærket rimeligt. Der skal være en måde til sortering metode til at sammenligne objekter. Men hvorfor kan medarbejderen ikke klasse giver blot en compareTo metode uden at implementere Comparable grænseflade?

Grunden til grænseflader er, at Java-programmeringssproget er stærkt skrevet . Når du laver et metodekald, skal compileren være i stand til at kontrollere, at metoden faktisk eksisterer. Et eller andet sted i sorten metode vil være udsagn som denne:

if (a[i].compareTo(a[j]) > 0)
{
   // rearrange a[i] and a[j]
   . . .
}

Compileren skal vide, at a[i] har faktisk en compareTo metode. Hvis a er en række Sammenlignelige objekter, så er eksistensen af ​​metoden sikret, fordi hver klasse, der implementerer Comparable interface skal levere metoden.

BEMÆRK

Du ville forvente, at sorte metode i Arrays klasse er defineret til at acceptere en Comparable[] array, så compileren kan klage, hvis nogen nogensinde kalder sort med et array, hvis elementtype ikke implementerer Comparable interface. Det er desværre ikke tilfældet. I stedet sortér metode accepterer et Objekt[] array og bruger en klodset rollebesætning:

// Approach used in the standard library--not recommended
if (((Comparable) a[i]).compareTo(a[j]) > 0)
{
   // rearrange a[i] and a[j]
   . . .
}

Hvis a[i] hører ikke til en klasse, der implementerer Comparable interface, kaster den virtuelle maskine en undtagelse.

Liste 6.1 præsenterer den fulde kode til sortering af et array af forekomster af klassen Medarbejder (Opstilling 6.2). til sortering af et medarbejderarray.

Fortegnelse 6.1. interfaces/EmployeeSortTest.java

 1  package interfaces;
 2
 3  import java.util.*;
 4
 5  /**
 6   * This program demonstrates the use of the Comparable interface.
 7   * @version 1.30 2004-02-27
 8   * @author Cay Horstmann
 9   */
10  public class EmployeeSortTest
11  {
12     public static void main(String[] args)
13     {
14        Employee[] staff = new Employee[3];
15
16        staff[0] = new Employee("Harry Hacker", 35000);
17        staff[1] = new Employee("Carl Cracker", 75000);
18        staff[2] = new Employee("Tony Tester", 38000);
19
20        Arrays.sort(staff);
21
22        // print out information about all Employee objects
23        for (Employee e : staff)
24           System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
25     }
26  }

Fortegnelse 6.2. interfaces/Employee.java

 1  package interfaces;
 2
 3  public class Employee implements Comparable<Employee>
 4  {
 5     private String name;
 6     private double salary;
 7
 8     public Employee(String n, double s)
 9     {
10        name = n;
11        salary = s;
12     }
13
14     public String getName()
15     {
16        return name;
17     }
18
19     public double getSalary()
20     {
21        return salary;
22     }
23
24     public void raiseSalary(double byPercent)
25     {
26        double raise = salary * byPercent / 100;
27        salary += raise;
28     }
29
30     /**
31      * Compares employees by salary
32      * @param other another Employee object
33      * @return a negative value if this employee has a lower salary than
34      * otherObject, 0 if the salaries are the same, a positive value otherwise
35      */
36     public int compareTo(Employee other)
37     {
38        return Double.compare(salary, other.salary);
39     }
40  }

java.lang.Comparable 1.0

  • int compareTo(T andet)

    sammenligner dette objekt med andet og returnerer et negativt heltal, hvis dette objekt er mindre end andet , nul, hvis de er ens, og et positivt heltal ellers.

java.util.Arrays 1.2

  • static void sort(Object[] a)

    sorterer elementerne i arrayet a , ved hjælp af en indstillet mergesort algoritme. Alle elementer i arrayet skal tilhøre klasser, der implementerer Comparable grænseflade, og de skal alle være sammenlignelige med hinanden.

java.lang.Integer 7

  • statisk int compare(int x, int y)

    returnerer et negativt heltal hvis x , nul hvis x og y er lige store og ellers et positivt heltal.

java.lang.Double 7

  • static int compare(double x, double y)

    returnerer et negativt heltal hvis x , nul hvis x og y er lige store og ellers et positivt heltal.

BEMÆRK

Ifølge sprogstandarden:"Implementatoren skal sikre sgn(x.compareTo(y)) =-sgn(y.compareTo(x)) for alle x og y . (Dette indebærer, at x.compareTo(y) skal give en undtagelse hvis y.compareTo(x) kaster en undtagelse.)” Her, sgn er tegnet af et tal:sgn(n ) er –1 hvis n er negativ, 0 hvis n er lig med 0 og 1 hvis n er positiv. På almindeligt engelsk, hvis du vender parametrene for compareTo , skal tegnet (men ikke nødvendigvis den faktiske værdi) af resultatet også vende.

Som med lig med metode, kan der opstå problemer, når arv kommer i spil.

Siden Manager udvider Medarbejder , implementerer den Sammenlignelig og ikke Sammenlignelig . Hvis Manager vælger at tilsidesætte compareTo , skal den være forberedt på at sammenligne ledere med medarbejdere. Det kan ikke bare caste en medarbejder til en leder:

class Manager extends Employee
{
   public int compareTo(Employee other)
   {
      Manager otherManager = (Manager) other; // NO
      . . .
   }
   . . .
}

Det er i strid med reglen om "antisymmetri". Hvis x er medarbejder og y er Manager , derefter kaldet x.compareTo(y) giver ikke en undtagelse - den sammenligner blot x og y som medarbejdere. Men omvendt, y.compareTo(x) , kaster en ClassCastException .

Dette er den samme situation som med equals metode, som vi diskuterede i kapitel 5, og midlet er det samme. Der er to adskilte scenarier.

Hvis underklasser har forskellige forestillinger om sammenligning, bør du forbyde sammenligning af objekter, der tilhører forskellige klasser. Hver sammenlign med metoden skal starte med testen

if (getClass() != other.getClass()) throw new ClassCastException();

Hvis der er en fælles algoritme til sammenligning af underklasseobjekter, skal du blot angive en enkelt compareTo metode i superklassen og erklære den som final .

Antag for eksempel, at du ønsker, at ledere skal være bedre end almindelige medarbejdere, uanset lønnen. Hvad med andre underklasser såsom Executive og sekretær ? Hvis du har brug for at etablere en hakkefølge, skal du angive en metode såsom rang i Medarbejder klasse. Få hver underklasse til at tilsidesætte rangering , og implementer en enkelt compareTo metode, der tager rangen værdier i betragtning.

6.1.1. Egenskaber for grænseflader

Grænseflader er ikke klasser. Især kan du aldrig bruge den nye operatør for at instansiere en grænseflade:

x = new Comparable(. . .); // ERROR

Men selvom du ikke kan konstruere grænsefladeobjekter, kan du stadig erklære grænsefladevariabler.

Comparable x; // OK

En grænsefladevariabel skal referere til et objekt i en klasse, der implementerer grænsefladen:

x = new Employee(. . .); // OK provided Employee implements Comparable

Dernæst, ligesom du bruger instanceof for at kontrollere om et objekt er af en bestemt klasse, kan du bruge instanceof for at kontrollere, om et objekt implementerer en grænseflade:

if (anObject instanceof Comparable) { . . . }

Ligesom du kan bygge hierarkier af klasser, kan du udvide grænseflader. Dette giver mulighed for flere kæder af grænseflader, der går fra en større grad af generalitet til en større grad af specialisering. Antag for eksempel, at du havde en grænseflade kaldet Moveable .

public interface Moveable
{
   void move(double x, double y);
}

Så kunne du forestille dig en grænseflade kaldet Powered der udvider det:

public interface Powered extends Moveable
{
   double milesPerGallon();
}

Selvom du ikke kan sætte instansfelter eller statiske metoder i en grænseflade, kan du levere konstanter i dem. For eksempel:

public interface Powered extends Moveable
{
   double milesPerGallon();
   double SPEED_LIMIT = 95; // a public static final constant
}

Ligesom metoder i en grænseflade automatisk er offentlige , felter er altid offentlige statiske endelige .

BEMÆRK

Det er lovligt at mærke grænseflademetoder som offentlige , og felter som offentlig statisk endelig . Nogle programmører gør det, enten af ​​vane eller for større klarhed. Java-sprogspecifikationen anbefaler dog, at de overflødige søgeord ikke leveres, og vi følger denne anbefaling.

Nogle grænseflader definerer kun konstanter og ingen metoder. For eksempel indeholder standardbiblioteket en grænseflade SwingConstants der definerer konstanter NORD , SYD , HORIZONTALT , og så videre. Enhver klasse, der vælger at implementere SwingConstants interface arver automatisk disse konstanter. Dens metoder kan blot henvise til NORD frem for de mere besværlige SwingConstants.NORTH . Denne brug af grænseflader virker dog ret degenereret, og vi anbefaler det ikke.

Mens hver klasse kun kan have én superklasse, kan klasser implementere flere grænseflader. Dette giver dig den maksimale fleksibilitet til at definere en klasses adfærd. For eksempel har programmeringssproget Java en vigtig grænseflade indbygget, kaldet Klonbar . (Vi vil diskutere denne grænseflade i detaljer i næste afsnit.) Hvis din klasse implementerer Klonbar , klonen metode i Objekt klasse vil lave en nøjagtig kopi af din klasses objekter. Antag derfor, at du ønsker kloningsevne og sammenlignelighed. Så implementerer du blot begge grænseflader.

class Employee implements Cloneable, Comparable

Brug kommaer til at adskille de grænseflader, der beskriver de egenskaber, du vil levere.

6.1.2. Grænseflader og abstrakte klasser

Hvis du læser afsnittet om abstrakte klasser i kapitel 5, kan du undre dig over, hvorfor designere af programmeringssproget Java gad med at introducere begrebet grænseflader. Hvorfor kan Sammenlignelig ikke blot være en abstrakt klasse:

abstract class Comparable // why not?
{
   public abstract int compareTo(Object other);
}

Medarbejderen klasse ville så blot udvide denne abstrakte klasse og levere compareTo metode:

class Employee extends Comparable // why not?
{
   public int compareTo(Object other) { . . . }
}

Der er desværre et stort problem med at bruge en abstrakt basisklasse til at udtrykke en generisk egenskab. En klasse kan kun forlænge en enkelt klasse. Antag, at medarbejderen klasse udvider allerede en anden klasse, f.eks. Person . Så kan den ikke forlænge en anden klasse.

class Employee extends Person, Comparable // ERROR

Men hver klasse kan implementere så mange grænseflader, som den vil:

class Employee extends Person implements Comparable // OK

Andre programmeringssprog, især C++, tillader en klasse at have mere end én superklasse. Denne funktion kaldes multipel arv . Designerne af Java valgte ikke at understøtte multipel nedarvning, fordi det gør sproget enten meget komplekst (som i C++) eller mindre effektivt (som i Eiffel).

I stedet giver grænseflader de fleste af fordelene ved multipel nedarvning, samtidig med at de undgår kompleksiteten og ineffektiviteten.

C++ BEMÆRK

C++ har multipel nedarvning og alle de komplikationer, der følger med det, såsom virtuelle basisklasser, dominansregler og tværgående pointerkast. Få C++ programmører bruger multipel arv, og nogle siger, at det aldrig bør bruges. Andre programmører anbefaler kun at bruge multipel arv til "mix-in"-stilen for arv. I mix-in-stilen beskriver en primær basisklasse det overordnede objekt, og yderligere basisklasser (de såkaldte mix-ins) kan levere hjælpekarakteristika. Denne stil ligner en Java-klasse med en enkelt basisklasse og ekstra grænseflader. Men i C++ kan mix-ins tilføje standardadfærd, hvorimod Java-grænseflader ikke kan.


Java tag