CodeGym /Java-blogg /Tilfeldig /Java-polymorfisme
John Squirrels
Nivå
San Francisco

Java-polymorfisme

Publisert i gruppen
OOP-relaterte spørsmål er en integrert del av det tekniske intervjuet for en Java-utviklerstilling i et IT-selskap. I denne artikkelen vil vi snakke om ett prinsipp for OOP - polymorfisme. Vi vil fokusere på aspektene som ofte blir spurt om under intervjuer, og også gi noen eksempler for klarhet.

Hva er polymorfisme i Java?

Polymorfisme er et programs evne til å behandle objekter med samme grensesnitt på samme måte, uten informasjon om objektets spesifikke type. Hvis du svarer på et spørsmål om hva polymorfisme er, vil du mest sannsynlig bli bedt om å forklare hva du mente. Uten å utløse en haug med tilleggsspørsmål, legg det hele ut for intervjueren igjen. Intervjutid: polymorfisme i Java - 1Du kan starte med at OOP-tilnærmingen innebærer å bygge et Java-program basert på interaksjonen mellom objekter, som er basert på klasser. Klasser er tidligere skrevne tegninger (maler) som brukes til å lage objekter i programmet. Dessuten har en klasse alltid en bestemt type, som med god programmeringsstil har et navn som antyder formålet. Videre kan det bemerkes at siden Java er sterkt skrevet, må programkoden alltid spesifisere en objekttype når variabler er deklarert. Legg til dette at streng skriving forbedrer kodens sikkerhet og pålitelighet, og gjør det mulig, selv ved kompilering, å forhindre feil på grunn av inkompatibilitetstyper (for eksempel forsøk på å dele en streng med et tall). Naturligvis må kompilatoren "vite" den deklarerte typen – det kan være en klasse fra JDK eller en som vi har laget selv. Påpek overfor intervjueren at koden vår kan bruke ikke bare objektene av typen angitt i erklæringen, men også dens etterkommere.Dette er et viktig poeng: vi kan jobbe med mange forskjellige typer som en enkelt type (forutsatt at disse typene er avledet fra en basistype). Dette betyr også at hvis vi erklærer en variabel hvis type er en superklasse, kan vi tilordne en forekomst av en av dens etterkommere til den variabelen. Intervjueren vil like det hvis du gir et eksempel. Velg en klasse som kan deles av (en basisklasse for) flere klasser og få et par av dem til å arve den. Grunnklasse:

public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + " I dance like everyone else.");
    }

    @Override
    public String toString() {
        Return "I'm " + name + ". I'm " + age + " years old.";
    }
}
I underklassene, overstyr metoden til basisklassen:

public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
Et eksempel på polymorfisme og hvordan disse objektene kan brukes i et program:

public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Fred", 18);

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type 
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
I hovedmetoden , vis at linjene

Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
erklære en variabel av en superklasse og tilordne den et objekt som er en forekomst av en av dens etterkommere. Du vil mest sannsynlig bli spurt om hvorfor kompilatoren ikke vipper ut ved inkonsekvens av typene som er deklarert på venstre og høyre side av oppdragsoperatøren - tross alt er Java sterkt skrevet. Forklar at en utvidende typekonvertering er på jobb her - en referanse til et objekt behandles som en referanse til dets basisklasse. I tillegg, etter å ha møtt en slik konstruksjon i koden, utfører kompilatoren konverteringen automatisk og implisitt. Eksempelkoden viser at typen som er deklarert på venstre side av oppdragsoperatøren ( Dancer ) har flere former (typer), som er deklarert på høyre side ( Breakdancer , ElectricBoogieDancer). Hver form kan ha sin egen unike oppførsel med hensyn til den generelle funksjonaliteten definert i superklassen (dansemetoden ) . Det vil si at en metode deklarert i en superklasse kan implementeres annerledes i dens etterkommere. I dette tilfellet har vi å gjøre med metodeoverstyring, som er akkurat det som skaper flere former (atferd). Dette kan sees ved å kjøre koden i hovedmetoden: Programutgang: Jeg er Fred. Jeg er 18 år gammel. Jeg danser som alle andre. Jeg er Jay. Jeg er 19 år gammel. Jeg breakdanser! Jeg er Marcia. Jeg er 20 år gammel. Jeg danser elektrisk boogie! Hvis vi ikke overstyrer metoden i underklassene, får vi ikke annen oppførsel. For eksempel,ElectricBoogieDancer- klasser, da blir resultatet av programmet dette: Jeg er Fred. Jeg er 18 år gammel. Jeg danser som alle andre. Jeg er Jay. Jeg er 19 år gammel. Jeg danser som alle andre. Jeg er Marcia. Jeg er 20 år gammel. Jeg danser som alle andre. Og dette betyr at det rett og slett ikke gir mening å lage Breakdancer- og ElectricBoogieDancer -klassene. Hvor spesifikt er prinsippet om polymorfisme manifest? Hvor brukes et objekt i programmet uten kunnskap om dens spesifikke type? I vårt eksempel skjer det når dance() -metoden kalles på Dancer d -objektet. I Java betyr polymorfisme at programmet ikke trenger å vite om objektet er enBreakdancer eller ElectricBoogieDancer . Det viktige er at det er en etterkommer av Dancer -klassen. Og hvis du nevner etterkommere, bør du merke deg at arv i Java ikke bare utvides , men også implementerer. Nå er det på tide å nevne at Java ikke støtter multippel arv - hver type kan ha en forelder (superklasse) og et ubegrenset antall etterkommere (underklasser). Følgelig brukes grensesnitt for å legge til flere sett med funksjoner til klasser. Sammenlignet med underklasser (arv), er grensesnitt mindre koblet med overordnet klasse. De brukes veldig mye. I Java er et grensesnitt en referansetype, så programmet kan deklarere en variabel av grensesnitttypen. Nå er det på tide å gi et eksempel. Lag et grensesnitt:

public interface CanSwim {
    void swim();
}
For klarhetens skyld tar vi forskjellige urelaterte klasser og får dem til å implementere grensesnittet:

public class Human implements CanSwim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+" I swim with an inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}
 
public class Fish implements CanSwim {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
hovedmetode :

public class Main {

    public static void main(String[] args) {
        CanSwim human = new Human("John", 6);
        CanSwim fish = new Fish("Whale");
        CanSwim boat = new UBoat(25);

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
Resultatene som kaller en polymorf metode definert i et grensesnitt viser oss forskjellene i oppførselen til typene som implementerer dette grensesnittet. I vårt tilfelle er dette de forskjellige strengene som vises av svømmemetoden . Etter å ha studert eksempelet vårt, kan intervjueren spørre hvorfor denne koden kjøres i hovedmetoden

for (Swim s : swimmers) {
            s.swim();        
}
fører til at de overordnede metodene som er definert i underklassene våre kalles? Hvordan velges metodens ønskede implementering mens programmet kjører? For å svare på disse spørsmålene må du forklare sen (dynamisk) binding. Binding betyr å etablere en mapping mellom et metodekall og dets spesifikke klasseimplementering. I hovedsak bestemmer koden hvilken av de tre metodene som er definert i klassene som skal utføres. Java bruker sen binding som standard, dvs. binding skjer ved kjøretid og ikke ved kompilering, slik tilfellet er med tidlig binding. Dette betyr at når kompilatoren kompilerer denne koden

for (Swim s : swimmers) {
            s.swim();        
}
den vet ikke hvilken klasse ( menneske , fisk eller ubåt ) som har koden som vil bli utført når du svømmermetode kalles. Dette bestemmes kun når programmet kjøres, takket være den dynamiske bindingsmekanismen (kontrollerer et objekts type under kjøring og velger riktig implementering for denne typen). Hvis du blir spurt om hvordan dette implementeres, kan du svare at når du laster og initialiserer objekter, bygger JVM tabeller i minnet og kobler variabler med deres verdier og objekter med deres metoder. Ved å gjøre dette, hvis en klasse er arvet eller implementerer et grensesnitt, er den første forretningsordenen å sjekke for tilstedeværelsen av overstyrte metoder. Hvis det er noen, er de bundet til denne typen. Hvis ikke, flytter søket etter en matchende metode til klassen som er ett trinn høyere (overordnet) og så videre opp til roten i et flernivåhierarki. Når det gjelder polymorfisme i OOP og dens implementering i kode, vi legger merke til at det er god praksis å bruke abstrakte klasser og grensesnitt for å gi abstrakte definisjoner av basisklasser. Denne praksisen følger av abstraksjonsprinsippet - identifisere vanlig atferd og egenskaper og sette dem i en abstrakt klasse, eller bare identifisere vanlig atferd og sette den i et grensesnitt. Å designe og lage et objekthierarki basert på grensesnitt og klassearv er nødvendig for å implementere polymorfisme. Når det gjelder polymorfisme og innovasjoner i Java, merker vi at fra og med Java 8, når du lager abstrakte klasser og grensesnitt er det mulig å bruke eller bare identifisere vanlig atferd og sette den i et grensesnitt. Å designe og lage et objekthierarki basert på grensesnitt og klassearv er nødvendig for å implementere polymorfisme. Når det gjelder polymorfisme og innovasjoner i Java, merker vi at fra og med Java 8, når du lager abstrakte klasser og grensesnitt er det mulig å bruke eller bare identifisere vanlig atferd og sette den i et grensesnitt. Å designe og lage et objekthierarki basert på grensesnitt og klassearv er nødvendig for å implementere polymorfisme. Når det gjelder polymorfisme og innovasjoner i Java, merker vi at fra og med Java 8, når du lager abstrakte klasser og grensesnitt er det mulig å brukestandard nøkkelord for å skrive en standardimplementering for abstrakte metoder i basisklasser. For eksempel:

public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
Noen ganger spør intervjuere om hvordan metoder i basisklasser må deklareres slik at prinsippet om polymorfisme ikke brytes. Svaret er enkelt: disse metodene må ikke være statiske , private eller endelige . Private gjør en metode tilgjengelig kun innenfor en klasse, så du vil ikke kunne overstyre den i en underklasse. Static assosierer en metode til klassen i stedet for et hvilket som helst objekt, så superklassens metode vil alltid bli kalt. Og endelig gjør en metode uforanderlig og skjult fra underklasser.

Hva gir polymorfisme oss?

Du vil også mest sannsynlig bli spurt om hvordan polymorfisme er til fordel for oss. Du kan svare kort på dette uten å henge deg fast i de hårete detaljene:
  1. Det gjør det mulig å erstatte klasseimplementeringer. Testing er bygget på det.
  2. Det letter utvidbarhet, noe som gjør det mye enklere å lage et fundament som kan bygges på i fremtiden. Å legge til nye typer basert på eksisterende er den vanligste måten å utvide funksjonaliteten til OOP-programmer på.
  3. Den lar deg kombinere objekter som deler en felles type eller oppførsel i én samling eller rekke og håndtere dem jevnt (som i våre eksempler, der vi tvang alle til å danse() eller svømme() :)
  4. Fleksibilitet ved å lage nye typer: du kan velge forelderens implementering av en metode eller overstyre den i en underklasse.

Noen avskjedsord

Polymorfisme er et veldig viktig og omfattende tema. Det er tema for nesten halvparten av denne artikkelen om OOP i Java og utgjør en god del av språkets grunnlag. Du vil ikke kunne unngå å definere dette prinsippet i et intervju. Hvis du ikke vet det eller ikke forstår det, vil intervjuet sannsynligvis gå mot slutten. Så ikke vær en slapp – vurder kunnskapen din før intervjuet og oppdater den om nødvendig.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION