CodeGym /Java blogg /Slumpmässig /Java polymorfism
John Squirrels
Nivå
San Francisco

Java polymorfism

Publicerad i gruppen
OOP-relaterade frågor är en integrerad del av den tekniska intervjun för en Java-utvecklartjänst i ett IT-företag. I den här artikeln kommer vi att prata om en princip för OOP - polymorfism. Vi kommer att fokusera på de aspekter som ofta frågas om under intervjuer, och även ge några exempel för tydlighetens skull.

Vad är polymorfism i Java?

Polymorfism är ett programs förmåga att behandla objekt med samma gränssnitt på samma sätt, utan information om objektets specifika typ. Om du svarar på en fråga om vad polymorfism är, kommer du troligen att bli ombedd att förklara vad du menade. Utan att utlösa en massa ytterligare frågor, lägg upp allt för intervjuaren igen. Intervjutid: polymorfism i Java - 1Du kan börja med att OOP-metoden går ut på att bygga ett Java-program baserat på interaktionen mellan objekt, som är baserade på klasser. Klasser är tidigare skrivna ritningar (mallar) som används för att skapa objekt i programmet. Dessutom har en klass alltid en specifik typ, som med bra programmeringsstil har ett namn som antyder dess syfte. Vidare kan det noteras att eftersom Java är starkt skrivet måste programkoden alltid specificera en objekttyp när variabler deklareras. Lägg till detta att strikt typning förbättrar kodens säkerhet och tillförlitlighet, och gör det möjligt, även vid kompilering, att förhindra fel på grund av inkompatibilitetstyper (till exempel att försöka dividera en sträng med ett nummer). Naturligtvis måste kompilatorn "veta" den deklarerade typen – det kan vara en klass från JDK eller en som vi skapat själva. Påpeka för intervjuaren att vår kod inte bara kan använda objekten av den typ som anges i deklarationen utan även dess ättlingar.Detta är en viktig punkt: vi kan arbeta med många olika typer som en enda typ (förutsatt att dessa typer härrör från en bastyp). Detta betyder också att om vi deklarerar en variabel vars typ är en superklass, så kan vi tilldela en instans av en av dess avkomlingar till den variabeln. Intervjuaren kommer att gilla det om du ger ett exempel. Välj en klass som kan delas av (en basklass för) flera klasser och få ett par av dem att ärva den. Basklass:

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 underklasserna åsidosätter du metoden för basklassen:

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!");
    }
}
Ett exempel på polymorfism och hur dessa objekt kan användas i ett 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 huvudmetoden , visa att linjerna

Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
deklarera en variabel för en superklass och tilldela den ett objekt som är en instans av en av dess avkomlingar. Du kommer med största sannolikhet att få frågan varför kompilatorn inte flippar ut vid inkonsekvensen av de typer som deklareras på vänster och höger sida av tilldelningsoperatorn - trots allt är Java starkt skriven. Förklara att en breddande typkonvertering är på gång här - en referens till ett objekt behandlas som en referens till dess basklass. Dessutom, efter att ha stött på en sådan konstruktion i koden, utför kompilatorn konverteringen automatiskt och implicit. Exempelkoden visar att typen som deklareras på vänster sida av uppdragsoperatören ( Dancer ) har flera former (typer), som deklareras på höger sida ( Breakdancer , ElectricBoogieDancer). Varje form kan ha sitt eget unika beteende med avseende på den allmänna funktionaliteten definierad i superklassen (dansmetoden ) . Det vill säga, en metod som deklareras i en superklass kan implementeras annorlunda i dess avkomlingar. I det här fallet har vi att göra med metodöverstyrning, vilket är precis det som skapar flera former (beteenden). Detta kan ses genom att köra koden i huvudmetoden: Programutgång: Jag är Fred. Jag är 18 år gammal. Jag dansar som alla andra. Jag är Jay. Jag är 19 år. Jag breakdansar! Jag är Marcia. Jag är 20 år gammal. Jag dansar elektrisk boogie! Om vi ​​inte åsidosätter metoden i underklasserna kommer vi inte att få något annat beteende. Till exempel,ElectricBoogieDancer- klasser, då blir resultatet av programmet så här: Jag är Fred. Jag är 18 år gammal. Jag dansar som alla andra. Jag är Jay. Jag är 19 år. Jag dansar som alla andra. Jag är Marcia. Jag är 20 år gammal. Jag dansar som alla andra. Och detta betyder att det helt enkelt inte är meningsfullt att skapa Breakdancer- och ElectricBoogieDancer -klasserna. Var specifikt manifesteras principen för polymorfism? Var används ett objekt i programmet utan kunskap om dess specifika typ? I vårt exempel händer det när dance() -metoden anropas på Dancer d -objektet. I Java betyder polymorfism att programmet inte behöver veta om objektet är ettBreakdancer eller ElectricBoogieDancer . Det viktiga är att det är en ättling till Dansarklassen . Och om du nämner ättlingar, bör du notera att arv i Java inte bara sträcker sig utan också implementerar. Nu är det dags att nämna att Java inte stöder multipelt arv - varje typ kan ha en förälder (superklass) och ett obegränsat antal avkomlingar (underklasser). Följaktligen används gränssnitt för att lägga till flera uppsättningar funktioner till klasser. Jämfört med underklasser (arv) är gränssnitt mindre kopplade till den överordnade klassen. De används mycket flitigt. I Java är ett gränssnitt en referenstyp, så programmet kan deklarera en variabel av gränssnittstypen. Nu är det dags att ge ett exempel. Skapa ett gränssnitt:

public interface CanSwim {
    void swim();
}
För tydlighetens skull tar vi olika orelaterade klasser och får dem att implementera gränssnittet:

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.");
    }
}
huvudmetod :

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();
        }
    }
}
Resultaten som kallar en polymorf metod definierad i ett gränssnitt visar oss skillnaderna i beteendet hos de typer som implementerar detta gränssnitt. I vårt fall är det här de olika strängarna som visas av simmetoden . Efter att ha studerat vårt exempel kan intervjuaren fråga varför den här koden körs i huvudmetoden

for (Swim s : swimmers) {
            s.swim();        
}
gör att de överordnade metoderna som definieras i våra underklasser anropas? Hur väljs metodens önskade implementering medan programmet körs? För att svara på dessa frågor måste du förklara sen (dynamisk) bindning. Bindning innebär att upprätta en mappning mellan ett metodanrop och dess specifika klassimplementering. I huvudsak bestämmer koden vilken av de tre metoderna som definieras i klasserna som kommer att exekveras. Java använder sen bindning som standard, dvs bindning sker vid körning och inte vid kompilering som är fallet med tidig bindning. Detta betyder att när kompilatorn kompilerar denna kod

for (Swim s : swimmers) {
            s.swim();        
}
den vet inte vilken klass ( Människa , Fisk eller Uboat ) som har koden som kommer att köras när simningenmetod kallas. Detta bestäms endast när programmet körs, tack vare den dynamiska bindningsmekanismen (kontrollera ett objekts typ vid körning och välj korrekt implementering för denna typ). Om du får frågan hur detta implementeras kan du svara att vid laddning och initialisering av objekt bygger JVM tabeller i minnet och länkar variabler med deras värden och objekt med deras metoder. Om en klass ärvs eller implementerar ett gränssnitt, är det första uppdraget att kontrollera förekomsten av åsidosatta metoder. Om det finns några är de bundna till denna typ. Om inte, flyttas sökningen efter en matchningsmetod till klassen som är ett steg högre (föräldern) och så vidare upp till roten i en flernivåhierarki. När det gäller polymorfism i OOP och dess implementering i kod, vi noterar att det är god praxis att använda abstrakta klasser och gränssnitt för att tillhandahålla abstrakta definitioner av basklasser. Denna praxis följer av abstraktionsprincipen - att identifiera vanligt beteende och egenskaper och placera dem i en abstrakt klass, eller bara identifiera vanligt beteende och placera det i ett gränssnitt. Att designa och skapa en objekthierarki baserad på gränssnitt och klassarv krävs för att implementera polymorfism. När det gäller polymorfism och innovationer i Java, noterar vi att från och med Java 8, när du skapar abstrakta klasser och gränssnitt är det möjligt att använda eller bara identifiera vanligt beteende och placera det i ett gränssnitt. Att designa och skapa en objekthierarki baserad på gränssnitt och klassarv krävs för att implementera polymorfism. När det gäller polymorfism och innovationer i Java, noterar vi att från och med Java 8, när du skapar abstrakta klasser och gränssnitt är det möjligt att använda eller bara identifiera vanligt beteende och placera det i ett gränssnitt. Att designa och skapa en objekthierarki baserad på gränssnitt och klassarv krävs för att implementera polymorfism. När det gäller polymorfism och innovationer i Java, noterar vi att från och med Java 8, när du skapar abstrakta klasser och gränssnitt är det möjligt att användastandard nyckelord för att skriva en standardimplementering för abstrakta metoder i basklasser. Till exempel:

public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
Ibland frågar intervjuare om hur metoder i basklasser måste deklareras så att principen om polymorfism inte kränks. Svaret är enkelt: dessa metoder får inte vara statiska , privata eller slutgiltiga . Privat gör en metod tillgänglig endast inom en klass, så du kommer inte att kunna åsidosätta den i en underklass. Static associerar en metod med klassen snarare än något objekt, så superklassens metod kommer alltid att anropas. Och final gör en metod oföränderlig och dold från underklasser.

Vad ger polymorfism oss?

Du kommer troligen också att få frågan om hur polymorfism gynnar oss. Du kan svara kort på detta utan att fastna i de håriga detaljerna:
  1. Det gör det möjligt att ersätta klassimplementeringar. Testning bygger på det.
  2. Det underlättar utbyggbarheten, vilket gör det mycket lättare att skapa en grund som kan byggas på i framtiden. Att lägga till nya typer baserade på befintliga är det vanligaste sättet att utöka funktionaliteten i OOP-program.
  3. Det låter dig kombinera objekt som delar en gemensam typ eller beteende i en samling eller array och hantera dem enhetligt (som i våra exempel, där vi tvingade alla att dansa() eller simma() :)
  4. Flexibilitet i att skapa nya typer: du kan välja förälderns implementering av en metod eller åsidosätta den i en underklass.

Några avskedsord

Polymorfism är ett mycket viktigt och omfattande ämne. Det är ämnet för nästan hälften av den här artikeln om OOP i Java och utgör en stor del av språkets grund. Du kommer inte att kunna undvika att definiera denna princip i en intervju. Om du inte vet det eller inte förstår det, kommer intervjun förmodligen att ta slut. Så var inte en slappare – bedöm dina kunskaper innan intervjun och uppdatera den om det behövs.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION