Zoeken…


Invoering

Overerving is een elementaire objectgeoriënteerde functie waarbij de ene klasse de eigenschappen van een andere klasse verwerft en uitbreidt, met behulp van het trefwoord extends . Voor interfaces en het trefwoord implements , zie interfaces .

Syntaxis

  • class ClassB breidt ClassA uit {...}
  • class ClassB implementeert InterfaceA {...}
  • interface InterfaceB breidt InterfaceA uit {...}
  • class ClassB breidt ClassA implementeert InterfaceC, InterfaceD {...}
  • abstracte klasse AbstractClassB breidt ClassA uit {...}
  • abstracte klasse AbstractClassB breidt AbstractClassA uit {...}
  • abstract class AbstractClassB breidt ClassA implementeert InterfaceC, InterfaceD {...}

Opmerkingen

Overerving wordt vaak gecombineerd met generieke geneesmiddelen zodat de basisklasse een of meer typeparameters heeft. Zie Een generieke klasse maken .

Abstracte klassen

Een abstracte klasse is een klasse gemarkeerd met het abstract trefwoord. In tegenstelling tot de niet-abstracte klasse, kan het abstracte - implementatieloze - methoden bevatten. Het is echter wel geldig om een abstracte klasse te maken zonder abstracte methoden.

Een abstracte klasse kan niet worden gestart. Het kan worden onderverdeeld (uitgebreid) zolang de subklasse ofwel ook abstract is, of alle methoden implementeert die als abstract zijn gemarkeerd door superklassen.

Een voorbeeld van een abstracte klasse:

public abstract class Component {
    private int x, y;
    
    public setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public abstract void render();
}

De klasse moet abstract worden gemarkeerd, wanneer deze ten minste één abstracte methode heeft. Een abstracte methode is een methode die geen implementatie heeft. Andere methoden kunnen binnen een abstracte klasse worden gedeclareerd die zijn geïmplementeerd om gemeenschappelijke code voor alle subklassen te bieden.

Proberen om deze klasse te instantiëren, levert een compilatiefout op:

//error: Component is abstract; cannot be instantiated   
Component myComponent = new Component();

Een klasse die Component uitbreidt en een implementatie biedt voor al zijn abstracte methoden en die kan worden geïnstantieerd.

public class Button extends Component {

    @Override
    public void render() {
        //render a button
    }
}

public class TextBox extends Component {

    @Override
    public void render() {
        //render a textbox
    }
}

Instanties van het erven van klassen kunnen ook worden gegoten als de bovenliggende klasse (normale overerving) en ze bieden een polymorf effect wanneer de abstracte methode wordt aangeroepen.

Component myButton = new Button();
Component myTextBox = new TextBox();

myButton.render(); //renders a button
myTextBox.render(); //renders a text box

Abstracte klassen versus interfaces

Abstracte klassen en interfaces bieden beide een manier om methodehandtekeningen te definiëren, terwijl de uitbreidende / implementerende klasse vereist is voor de implementatie.

Er zijn twee belangrijke verschillen tussen abstracte klassen en interfaces:

  • Een klasse kan slechts een enkele klasse uitbreiden, maar kan veel interfaces implementeren.
  • Een abstracte klasse kan instantieveld (niet- static ) velden bevatten, maar interfaces kunnen alleen static velden bevatten.
Java SE 8

Methoden die in interfaces zijn aangegeven, konden geen implementaties bevatten, dus werden abstracte klassen gebruikt wanneer het nuttig was om aanvullende methoden te bieden die implementaties de abstracte methoden noemden.

Java SE 8

Met Java 8 kunnen interfaces standaardmethoden bevatten, meestal geïmplementeerd met behulp van de andere methoden van de interface , waardoor interfaces en abstracte klassen in dit opzicht even krachtig zijn.

Anonieme subklassen van abstracte klassen

Voor het gemak maakt java het mogelijk om anonieme instanties van subklassen van abstracte klassen te instantiëren, die implementaties voor de abstracte methoden bieden bij het maken van het nieuwe object. Met het bovenstaande voorbeeld kan dit er zo uitzien:

Component myAnonymousComponent = new Component() {
    @Override
    public void render() {
        // render a quick 1-time use component
    }
}

Statische erfenis

Statische methode kan op dezelfde manier worden geërfd als normale methoden, maar in tegenstelling tot normale methoden is het onmogelijk om " abstracte " methoden te maken om de statische methode te negeren. Het schrijven van een methode met dezelfde handtekening als een statische methode in een superklasse lijkt een vorm van overschrijven, maar in feite creëert dit gewoon een nieuwe functie die de andere verbergt.

public class BaseClass {
    
    public static int num = 5;

    public static void sayHello() {
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        BaseClass.sayHello();
        System.out.println("BaseClass's num: " + BaseClass.num);
            
        SubClass.sayHello();
        //This will be different than the above statement's output, since it runs
        //A different method
        SubClass.sayHello(true);
        
        StaticOverride.sayHello();
        System.out.println("StaticOverride's num: " + StaticOverride.num);
    }
}

public  class SubClass extends BaseClass {
    
    //Inherits the sayHello function, but does not override it   
    public static void sayHello(boolean test) {
        System.out.println("Hey");
    }
}

public static class StaticOverride extends BaseClass {

    //Hides the num field from BaseClass
    //You can even change the type, since this doesn't affect the signature
    public static String num = "test";
        
    //Cannot use @Override annotation, since this is static
    //This overrides the sayHello method from BaseClass
    public static void sayHello() {
        System.out.println("Static says Hi");
    }

}

Het uitvoeren van een van deze klassen levert de uitvoer op:

Hello
BaseClass's num: 5
Hello
Hey
Static says Hi
StaticOverride's num: test

Merk op dat in tegenstelling tot normale overerving, methoden voor statische overerving niet zijn verborgen. U kunt de base sayHello methode altijd aanroepen met BaseClass.sayHello() . Maar klassen nemen statische methoden over als er geen methoden met dezelfde handtekening in de subklasse worden gevonden. Als de handtekeningen van twee methoden variëren, kunnen beide methoden worden uitgevoerd vanuit de subklasse, zelfs als de naam hetzelfde is.

Statische velden verbergen elkaar op een vergelijkbare manier.

'Final' gebruiken om overerving en opheffing te beperken

Laatste lessen

Bij gebruik in een class verklaring de final modificeermiddel kunnen andere klassen van wordt verklaard extend klasse. Een final klasse is een "leaf" -klasse in de hiërarchie van de overervingsklasse.

// This declares a final class
final class MyFinalClass {
    /* some code */
}

// Compilation error: cannot inherit from final MyFinalClass
class MySubClass extends MyFinalClass {
    /* more code */
}

Use-cases voor eindklassen

Laatste klassen kunnen worden gecombineerd met een private constructeur om de instantiatie van een klasse te regelen of te voorkomen. Dit kan worden gebruikt om een zogenaamde "utility class" te maken die alleen statische leden definieert; dat wil zeggen constanten en statische methoden.

public final class UtilityClass {

    // Private constructor to replace the default visible constructor
    private UtilityClass() {}

    // Static members can still be used as usual
    public static int doSomethingCool() {
        return 123;
    }

}

Onveranderlijke klassen moeten ook als final worden verklaard. (Een onveranderlijke klasse is een klasse waarvan de instanties niet kunnen worden gewijzigd nadat ze zijn gemaakt; zie het onderwerp I mmutable Objects .) Hiermee maakt u het onmogelijk om een veranderlijke subklasse van een onveranderlijke klasse te maken. Dat zou in strijd zijn met het Liskov-vervangingsprincipe, dat vereist dat een subtype het "gedragscontract" van zijn supertypen moet gehoorzamen.

Vanuit praktisch oogpunt maakt het verklaren van een onveranderlijke klasse als final het gemakkelijker om te redeneren over programmagedrag. Het lost ook beveiligingsproblemen op in het scenario waarin niet-vertrouwde code wordt uitgevoerd in een beveiligingssandbox. (Omdat String bijvoorbeeld als final wordt verklaard, hoeft een vertrouwde klasse zich geen zorgen te maken dat deze misleid kan worden tot het accepteren van een veranderlijke subklasse, die de niet-vertrouwde beller vervolgens heimelijk zou kunnen wijzigen.)

Een nadeel van final is dat ze niet werken met sommige bespottingskaders zoals Mockito. Update: Mockito versie 2 ondersteunt nu het bespotten van laatste klassen.

Laatste methoden

De final modificator kan ook worden toegepast op methoden om te voorkomen dat ze worden overschreven in subklassen:

public class MyClassWithFinalMethod {

    public final void someMethod() {
    }
}

public class MySubClass extends MyClassWithFinalMethod {

    @Override
    public void someMethod() { // Compiler error (overridden method is final)
    }
}

Definitieve methoden worden meestal gebruikt wanneer u wilt beperken wat een subklasse in een klasse kan veranderen zonder subklassen volledig te verbieden.


De final modificator kan ook worden toegepast op variabelen, maar de betekenis van final voor variabelen staat los van overerving.

Het Liskov-vervangingsprincipe

Substitueerbaarheid is een principe in objectgeoriënteerd programmeren dat Barbara Liskov introduceerde in een keynote in 1987, waarin staat dat, als klasse B een subklasse van klasse A , overal waar A wordt verwacht, B in plaats daarvan kan worden gebruikt:

class A {...}
class B extends A {...}

public void method(A obj) {...}

A a = new B(); // Assignment OK
method(new B()); // Passing as parameter OK

Dit is ook van toepassing wanneer het type een interface is, waarbij geen hiërarchische relatie tussen de objecten nodig is:

interface Foo {
    void bar();
}

class A implements Foo {
    void bar() {...}
}

class B implements Foo {
    void bar() {...}
}

List<Foo> foos = new ArrayList<>();
foos.add(new A()); // OK
foos.add(new B()); // OK

Nu bevat de lijst objecten die niet uit dezelfde klassenhiërarchie komen.

Erfenis

Met het gebruik van het extends sleutelwoord onder klassen, zijn alle eigenschappen van de superklasse (ook bekend als de ouderklasse of basisklasse ) aanwezig in de subklasse (ook bekend als de kindklasse of afgeleide klasse )

public class BaseClass {

    public void baseMethod(){
        System.out.println("Doing base class stuff");
    }
}

public class SubClass extends BaseClass {

}

SubClass van SubClass hebben de methode baseMethod() overgenomen :

SubClass s = new SubClass();
s.baseMethod();  //Valid, prints "Doing base class stuff"

Extra inhoud kan worden toegevoegd aan een subklasse. Hierdoor is er extra functionaliteit in de subklasse mogelijk zonder dat de basisklasse of andere subklassen uit dezelfde basisklasse worden gewijzigd:

public class Subclass2 extends BaseClass {

    public void anotherMethod() {
        System.out.println("Doing subclass2 stuff");
    }
}

Subclass2 s2 = new Subclass2();
s2.baseMethod(); //Still valid , prints "Doing base class stuff"
s2.anotherMethod(); //Also valid, prints "Doing subclass2 stuff" 

Velden zijn ook geërfd:

public class BaseClassWithField {

    public int x;

}

public class SubClassWithField extends BaseClassWithField {

    public SubClassWithField(int x) {
        this.x = x; //Can access fields
    }
}

private velden en methoden bestaan nog steeds binnen de subklasse, maar zijn niet toegankelijk:

public class BaseClassWithPrivateField {

    private int x = 5;

    public int getX() {
        return x;
    }
}

public class SubClassInheritsPrivateField extends BaseClassWithPrivateField {

    public void printX() {
        System.out.println(x); //Illegal, can't access private field x
        System.out.println(getX()); //Legal, prints 5
    }
}

SubClassInheritsPrivateField s = new SubClassInheritsPrivateField();
int x = s.getX(); //x will have a value of 5.

In Java mag elke klasse maximaal één andere klasse uitbreiden.

public class A{}
public class B{}
public class ExtendsTwoClasses extends A, B {} //Illegal

Dit staat bekend als multiple inheritance, en hoewel het in sommige talen legaal is, staat Java dit niet toe met klassen.

Als gevolg hiervan heeft elke klasse een niet-vertakkende voorouderlijke reeks klassen die naar Object leidt, waaruit alle klassen afstammen.

Overerving en statische methoden

In Java kunnen bovenliggende en onderliggende klasse beide statische methoden met dezelfde naam hebben. Maar in dergelijke gevallen verbergt de implementatie van de statische methode in het kind de implementatie van de ouderklasse, het is niet de methode die de norm overschrijft. Bijvoorbeeld:

class StaticMethodTest {

  // static method and inheritance
  public static void main(String[] args) {
    Parent p = new Child();
    p.staticMethod(); // prints Inside Parent
    ((Child) p).staticMethod(); // prints Inside Child
  }

  static class Parent {
    public static void staticMethod() {
      System.out.println("Inside Parent");
    }
  }

  static class Child extends Parent {
    public static void staticMethod() {
      System.out.println("Inside Child");
    }
  }
}

Statische methoden zijn bindend voor een klasse en niet voor een instantie en deze methodebinding gebeurt tijdens het compileren. Omdat in de eerste aanroep van staticMethod() referentie p werd gebruikt, wordt de Parent van staticMethod() aangeroepen. In het tweede geval hebben we p in de Child klasse gegoten, Child 's staticMethod() uitgevoerd.

Variabele schaduw

Variabelen zijn SHADOWED en methoden zijn OVERRIDDEN. Welke variabele zal worden gebruikt, hangt af van de klasse waarvan de variabele wordt aangegeven. Welke methode zal worden gebruikt, hangt af van de werkelijke klasse van het object waarnaar door de variabele wordt verwezen.

class Car {
    public int gearRatio = 8;

    public String accelerate() {
        return "Accelerate : Car";
    }
}

class SportsCar extends Car {
    public int gearRatio = 9;

    public String accelerate() {
        return "Accelerate : SportsCar";
    }

    public void test() {

    }


    public static void main(String[] args) {

        Car car = new SportsCar();
        System.out.println(car.gearRatio + "  " + car.accelerate());
        // will print out 8  Accelerate : SportsCar
    }
}

Versmalling en verbreding van objectverwijzingen

Een instantie van een basisklasse casten naar een subklasse zoals in: b = (B) a; wordt vernauwing genoemd (omdat u probeert het basisklasseobject te beperken tot een specifieker klasseobject) en heeft een expliciete typecast nodig.

Een instantie van een subklasse naar een basisklasse casten zoals in: A a = b; wordt verbreding genoemd en hoeft niet te worden gegoten.

Overweeg ter illustratie de volgende klassenverklaringen en testcode:

class Vehicle {
}

class Car extends Vehicle {
}

class Truck extends Vehicle {
}

class MotorCycle extends Vehicle {
}

class Test {

    public static void main(String[] args) {
    
        Vehicle vehicle = new Car();
        Car car = new Car();        
    
        vehicle = car; // is valid, no cast needed

        Car c = vehicle // not valid
        Car c = (Car) vehicle; //valid
    }
}

De verklaring Vehicle vehicle = new Car(); is een geldige Java-instructie. Elk exemplaar van Car is ook een Vehicle . Daarom is de opdracht legaal zonder dat er een expliciete typefout nodig is.

Anderzijds is Car c = vehicle; is niet geldig. Het statische type van de vehicle is Vehicle wat betekent dat deze kan verwijzen naar een instantie van Car , Vrachtwagen , MotorCycle , or any other current or future subclass of Voertuig . (Or indeed, an instance of Vehicle itself, since we did not declare it as an abstracte class.) The assignment cannot be allowed, since that might lead to itself, since we did not declare it as an class.) The assignment cannot be allowed, since that might lead to auto die referring to a instantie van Truck.

Om deze situatie te voorkomen, moeten we een expliciete typecast toevoegen:

Car c = (Car) vehicle;

De typegroep vertelt de compiler dat we verwachten dat de waarde van het vehicle een Car of een subklasse van een Car . Indien nodig zal de compiler code invoegen om een runtime-typecontrole uit te voeren. Als de controle mislukt, wordt een ClassCastException gegenereerd wanneer de code wordt uitgevoerd.

Merk op dat niet alle type-casts geldig zijn. Bijvoorbeeld:

String s = (String) vehicle;  // not valid

De Java-compiler weet dat een instantie die type-compatibel is met Vehicle nooit type-compatibel kan zijn met String . De type-cast zou nooit kunnen slagen, en de JLS verplicht dat dit een compilatiefout oplevert.

Programmeren naar een interface

Het idee achter programmeren naar een interface is om de code primair op interfaces te baseren en alleen concrete klassen te gebruiken op het moment van instantiëren. In deze context zal goede code die bijvoorbeeld Java-collecties behandelt er ongeveer zo uitzien (niet dat de methode zelf van enig nut is, alleen illustratie):

public <T> Set<T> toSet(Collection<T> collection) {
  return Sets.newHashSet(collection);
}

terwijl slechte code er als volgt kan uitzien:

public <T> HashSet<T> toSet(ArrayList<T> collection) {
  return Sets.newHashSet(collection);
}

Niet alleen de eerste kan worden toegepast op een bredere keuze aan argumenten, de resultaten ervan zullen meer compatibel zijn met code die wordt verstrekt door andere ontwikkelaars die zich in het algemeen houden aan het concept van programmeren op een interface. De belangrijkste redenen om de eerste te gebruiken zijn echter:

  • meestal hoeft en moet de context waarin het resultaat wordt gebruikt niet zoveel details bevatten als de concrete implementatie;
  • vasthouden aan een interface dwingt schonere code en minder hacks zoals nog een andere openbare methode wordt toegevoegd aan een klasse die een specifiek scenario dient;
  • de code kan beter worden getest, omdat interfaces eenvoudig te bespotten zijn;
  • ten slotte helpt het concept zelfs als er slechts één implementatie wordt verwacht (althans voor de testbaarheid).

Dus hoe kan men het concept van programmeren eenvoudig toepassen op een interface bij het schrijven van nieuwe code met het oog op een bepaalde implementatie? Een optie die we vaak gebruiken is een combinatie van de volgende patronen:

  • programmeren naar een interface
  • fabriek
  • bouwer

Het volgende voorbeeld op basis van deze principes is een vereenvoudigde en ingekorte versie van een RPC-implementatie geschreven voor een aantal verschillende protocollen:

public interface RemoteInvoker {
  <RQ, RS> CompletableFuture<RS> invoke(RQ request, Class<RS> responseClass);
}

Het is niet de bedoeling dat de bovenstaande interface direct via een fabriek wordt geïnstantieerd, in plaats daarvan leiden we meer concrete interfaces af, een voor HTTP-aanroep en een voor AMQP, die elk een fabriek en een bouwer hebben om instanties te bouwen, die op hun beurt ook instanties zijn van de bovenstaande interface:

public interface AmqpInvoker extends RemoteInvoker {
  static AmqpInvokerBuilder with(String instanceId, ConnectionFactory factory) {
    return new AmqpInvokerBuilder(instanceId, factory);
  }
}

Exemplaren van RemoteInvoker voor gebruik met AMQP kunnen nu zo eenvoudig worden geconstrueerd als (of meer betrokken, afhankelijk van de bouwer):

RemoteInvoker invoker = AmqpInvoker.with(instanceId, factory)
  .requestRouter(router)
  .build();

En een aanroep van een verzoek is net zo eenvoudig als:

Response res = invoker.invoke(new Request(data), Response.class).get();

Omdat Java 8 het plaatsen van statische methoden rechtstreeks in interfaces toestaat, is de tussenliggende fabriek impliciet geworden in de bovenstaande code vervangen door AmqpInvoker.with() . In Java voorafgaand aan versie 8 kan hetzelfde effect worden bereikt met een interne Factory klasse:

public interface AmqpInvoker extends RemoteInvoker {
  class Factory {
    public static AmqpInvokerBuilder with(String instanceId, ConnectionFactory factory) {
      return new AmqpInvokerBuilder(instanceId, factory);
    }
  }
}

De bijbehorende instantiëring zou dan veranderen in:

RemoteInvoker invoker = AmqpInvoker.Factory.with(instanceId, factory)
  .requestRouter(router)
  .build();

De hierboven gebruikte builder zou er zo uit kunnen zien (hoewel dit een vereenvoudiging is omdat de werkelijke het mogelijk maakt om tot 15 parameters te definiëren die afwijken van standaardwaarden). Merk op dat de constructie niet openbaar is, en dus specifiek alleen kan worden gebruikt vanuit de bovenstaande AmqpInvoker interface:

public class AmqpInvokerBuilder {
  ...
  AmqpInvokerBuilder(String instanceId, ConnectionFactory factory) {
    this.instanceId = instanceId;
    this.factory = factory;
  }

  public AmqpInvokerBuilder requestRouter(RequestRouter requestRouter) {
    this.requestRouter = requestRouter;
    return this;
  }

  public AmqpInvoker build() throws TimeoutException, IOException {
    return new AmqpInvokerImpl(instanceId, factory, requestRouter);
  }
}

Over het algemeen kan een bouwer ook worden gegenereerd met behulp van een tool zoals FreeBuilder.

Ten slotte is de standaard (en de enige verwachte) implementatie van deze interface gedefinieerd als een pakket-lokale klasse om het gebruik van de interface, de fabriek en de bouwer af te dwingen:

class AmqpInvokerImpl implements AmqpInvoker {
  AmqpInvokerImpl(String instanceId, ConnectionFactory factory, RequestRouter requestRouter) {
    ...
  }

  @Override
  public <RQ, RS> CompletableFuture<RS> invoke(final RQ request, final Class<RS> respClass) {
    ...
  }
}

Ondertussen bleek dit patroon zeer efficiënt bij het ontwikkelen van al onze nieuwe code, ongeacht hoe eenvoudig of complex de functionaliteit is.

Abstract klasse- en interface-gebruik: "Is-een" relatie versus "Heeft-een" mogelijkheid

Wanneer abstracte klassen gebruiken: hetzelfde of ander gedrag implementeren bij meerdere gerelateerde objecten

Wanneer interfaces te gebruiken: om een contract door meerdere niet-gerelateerde objecten te implementeren

Abstracte klassen creëren "is een" relaties, terwijl interfaces bieden "een" mogelijkheid heeft.

Dit is te zien in de onderstaande code:

public class InterfaceAndAbstractClassDemo{
    public static void main(String args[]){
        
        Dog dog = new Dog("Jack",16);
        Cat cat = new Cat("Joe",20);
            
        System.out.println("Dog:"+dog);
        System.out.println("Cat:"+cat);
        
        dog.remember();
        dog.protectOwner();
        Learn dl = dog;
        dl.learn();
                
        cat.remember();
        cat.protectOwner();
        
        Climb c = cat;
        c.climb();
        
        Man man = new Man("Ravindra",40);
        System.out.println(man);
        
        Climb cm = man;
        cm.climb();
        Think t = man;
        t.think();
        Learn l = man;
        l.learn();
        Apply a = man;
        a.apply();
    }
}

abstract class Animal{
    String name;
    int lifeExpentency;
    public Animal(String name,int lifeExpentency ){
        this.name = name;
        this.lifeExpentency=lifeExpentency;
    }
    public abstract void remember();
    public abstract void protectOwner();
    
    public String toString(){
        return this.getClass().getSimpleName()+":"+name+":"+lifeExpentency;
    }
}
class Dog extends Animal implements Learn{
    
    public Dog(String name,int age){
        super(name,age);
    }
    public void remember(){
        System.out.println(this.getClass().getSimpleName()+" can remember for 5 minutes");
    }
    public void protectOwner(){
        System.out.println(this.getClass().getSimpleName()+ " will protect owner");
    }
    public void learn(){
        System.out.println(this.getClass().getSimpleName()+ " can learn:");
    }
}
class Cat extends Animal implements Climb {
    public Cat(String name,int age){
        super(name,age);
    }
    public void remember(){
        System.out.println(this.getClass().getSimpleName() + " can remember for 16 hours");
    }
    public void protectOwner(){
        System.out.println(this.getClass().getSimpleName()+ " won't protect owner");
    }
    public void climb(){
        System.out.println(this.getClass().getSimpleName()+ " can climb");
    }
}
interface Climb{
    void climb();
}
interface Think {
    void think();
}

interface Learn {
    void learn();
}
interface Apply{
    void apply();
}

class Man implements Think,Learn,Apply,Climb{
    String name;
    int age;

    public Man(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void think(){
        System.out.println("I can think:"+this.getClass().getSimpleName());
    }
    public void learn(){
        System.out.println("I can learn:"+this.getClass().getSimpleName());
    }
    public void apply(){
        System.out.println("I can apply:"+this.getClass().getSimpleName());
    }
    public void climb(){
        System.out.println("I can climb:"+this.getClass().getSimpleName());
    }
    public String toString(){
        return "Man :"+name+":Age:"+age;
    }
}

output:

Dog:Dog:Jack:16
Cat:Cat:Joe:20
Dog can remember for 5 minutes
Dog will protect owner
Dog can learn:
Cat can remember for 16 hours
Cat won't protect owner
Cat can climb
Man :Ravindra:Age:40
I can climb:Man
I can think:Man
I can learn:Man
I can apply:Man

Belangrijke opmerkingen:

  1. Animal is een abstracte klasse met gedeelde kenmerken: name en lifeExpectancy en abstracte methoden: remember() en protectOwner() . Dog en Cat zijn Animals die de methoden remember() en protectOwner() hebben geïmplementeerd.

  2. Cat kan climb() maar Dog niet. Dog kan think() maar Cat niet. Deze specifieke mogelijkheden worden door implementatie aan Cat en Dog toegevoegd.

  3. Man is geen Animal maar hij kan Think , Learn , Apply en Climb .

  4. Cat is geen Man maar hij kan wel Climb .

  5. Dog is geen Man maar hij kan Learn

  6. Man is noch een Cat noch een Dog maar kan sommige van de laatste twee hebben zonder Animal , Cat of Dog te breiden. Dit gebeurt met interfaces.

  7. Hoewel Animal een abstracte klasse is, heeft het een constructor, in tegenstelling tot een interface.

TL; DR:

Niet-gerelateerde klassen kunnen mogelijkheden hebben via interfaces, maar gerelateerde klassen veranderen het gedrag door uitbreiding van basisklassen.

Raadpleeg de Java-documentatie pagina om te begrijpen welke u wilt gebruiken in een bepaalde use case.

Overweeg om abstracte klassen te gebruiken als ...

  1. U wilt code delen tussen verschillende nauw verwante klassen.
  2. U verwacht dat klassen die uw abstracte klasse uitbreiden, veel voorkomende methoden of velden hebben, of andere toegangsmodificaties vereisen dan openbaar (zoals beschermd en privé).
  3. U wilt niet-statische of niet-definitieve velden aangeven.

Overweeg het gebruik van interfaces als ...

  1. U verwacht dat niet-gerelateerde klassen uw interface zouden implementeren. Veel niet-gerelateerde objecten kunnen bijvoorbeeld de Serializable interface implementeren.
  2. U wilt het gedrag van een bepaald gegevenstype opgeven, maar maakt u zich geen zorgen over wie het gedrag implementeert.
  3. U wilt profiteren van meervoudige overerving van het type.

Overheersen in erfenis

Overschrijven in overerving wordt gebruikt wanneer u een al gedefinieerde methode uit een superklasse in een subklasse gebruikt, maar op een andere manier dan hoe de methode oorspronkelijk in de superklasse is ontworpen. Door het negeren kan de gebruiker code hergebruiken door bestaand materiaal te gebruiken en deze aan te passen aan de behoeften van de gebruiker.


Het volgende voorbeeld laat zien hoe ClassB de functionaliteit van ClassA door te wijzigen wat wordt verzonden via de afdrukmethode:

Voorbeeld:

public static void main(String[] args) {
    ClassA a = new ClassA();
    ClassA b = new ClassB();
    a.printing();
    b.printing();
}

class ClassA {
    public void printing() {        
        System.out.println("A");
    }
}

class ClassB extends ClassA {
    public void printing() {
         System.out.println("B");
    }
}

Output:

EEN

B



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow