Suche…


Einführung

Vererbung ist ein grundlegendes objektorientiertes Feature, bei dem eine Klasse die Eigenschaften einer anderen Klasse mit dem Schlüsselwort extends erfasst und extends . Interfaces und die Schlüsselwörter implements finden Sie unter Interfaces .

Syntax

  • Klasse ClassB erweitert ClassA {...}
  • Klasse ClassB implementiert InterfaceA {...}
  • interface InterfaceB erweitert InterfaceA {...}
  • class ClassB erweitert ClassA implementiert InterfaceC, InterfaceD {...}
  • abstrakte Klasse AbstractClassB erweitert ClassA {...}
  • abstrakte Klasse AbstractClassB erweitert AbstractClassA {...}
  • abstrakte Klasse AbstractClassB erweitert ClassA implementiert InterfaceC, InterfaceD {...}

Bemerkungen

Vererbung wird häufig mit Generika kombiniert, sodass die Basisklasse einen oder mehrere Typparameter hat. Siehe Generische Klasse erstellen .

Abstrakte Klassen

Eine abstrakte Klasse ist eine Klasse, die mit dem abstract Schlüsselwort markiert ist. Im Gegensatz zu nicht abstrakten Klassen kann es abstrakte - implementierungslose - Methoden enthalten. Es ist jedoch zulässig, eine abstrakte Klasse ohne abstrakte Methoden zu erstellen.

Eine abstrakte Klasse kann nicht instanziiert werden. Sie kann unterklassiert (erweitert) werden, solange die Unterklasse entweder abstrakt ist oder alle von Superklassen als abstrakt markierten Methoden implementiert.

Ein Beispiel für eine abstrakte 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();
}

Die Klasse muss als abstrakt markiert sein, wenn sie mindestens eine abstrakte Methode hat. Eine abstrakte Methode ist eine Methode, die keine Implementierung hat. Andere Methoden können innerhalb einer abstrakten Klasse deklariert werden, die implementiert ist, um allgemeinen Code für alle Unterklassen bereitzustellen.

Beim Versuch, diese Klasse zu instanziieren, wird ein Kompilierungsfehler ausgegeben:

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

Eine Klasse, die Component und eine Implementierung für alle ihre abstrakten Methoden bereitstellt und instanziiert werden kann.

public class Button extends Component {

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

public class TextBox extends Component {

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

Instanzen, die Klassen erben, können auch als übergeordnete Klasse umgewandelt werden (normale Vererbung) und bieten einen polymorphen Effekt, wenn die abstrakte Methode aufgerufen wird.

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

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

Abstrakte Klassen gegen Schnittstellen

Abstrakte Klassen und Schnittstellen bieten eine Möglichkeit, Methodensignaturen zu definieren, während die Erweiterungs- / Implementierungsklasse für die Implementierung erforderlich ist.

Es gibt zwei Hauptunterschiede zwischen abstrakten Klassen und Schnittstellen:

  • Eine Klasse kann nur eine einzelne Klasse erweitern, aber viele Schnittstellen implementieren.
  • Eine abstrakte Klasse kann Instanzfelder (nicht static ) enthalten, Schnittstellen jedoch nur static Felder.
Java SE 8

In Interfaces deklarierte Methoden konnten keine Implementierungen enthalten. Daher wurden abstrakte Klassen verwendet, wenn es sinnvoll war, zusätzliche Methoden bereitzustellen, die als abstrakte Methoden bezeichnet wurden.

Java SE 8

In Java 8 können Schnittstellen Standardmethoden enthalten, die normalerweise mit den anderen Methoden der Schnittstelle implementiert werden , wodurch Schnittstellen und abstrakte Klassen in dieser Hinsicht gleichermaßen mächtig werden.

Anonyme Unterklassen von abstrakten Klassen

Aus praktischen Gründen ermöglicht Java die Instantiierung anonymer Instanzen von Unterklassen abstrakter Klassen, die Implementierungen für die abstrakten Methoden beim Erstellen des neuen Objekts bereitstellen. Das obige Beispiel könnte so aussehen:

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

Statische Vererbung

Statische Methoden können ähnlich wie normale Methoden vererbt werden. Im Gegensatz zu normalen Methoden ist es jedoch nicht möglich, " abstrakte " Methoden zu erstellen, um das Überschreiben statischer Methoden zu erzwingen. Das Schreiben einer Methode mit derselben Signatur wie eine statische Methode in eine Superklasse scheint eine Form des Überschreibens zu sein, aber in Wirklichkeit wird dadurch einfach eine neue Funktion erstellt, die die andere versteckt.

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

}

Das Ausführen einer dieser Klassen erzeugt die Ausgabe:

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

Beachten Sie, dass im Gegensatz zur normalen Vererbung die Methoden der statischen Vererbung nicht ausgeblendet werden. Sie können die sayHello BaseClass.sayHello() immer mit BaseClass.sayHello() . Klassen erben jedoch statische Methoden, wenn in der Unterklasse keine Methoden mit derselben Signatur gefunden werden. Wenn zwei Signaturen der Methode variieren, können beide Methoden von der Unterklasse ausgeführt werden, auch wenn der Name gleich ist.

Statische Felder verbergen sich auf ähnliche Weise.

Verwenden Sie 'final', um die Vererbung und das Überschreiben zu beschränken

Abschlussunterricht

Bei Verwendung in einer class verhindert der final Modifizierer, dass andere Klassen deklariert werden, extend die Klasse erweitern. Eine final Klasse ist eine "Blatt" -Klasse in der Vererbungsklassenhierarchie.

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

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

Anwendungsfälle für Abschlussklassen

Abschließende Klassen können mit einem private Konstruktor kombiniert werden, um die Instantiierung einer Klasse zu steuern oder zu verhindern. Dies kann verwendet werden, um eine sogenannte "Utility-Klasse" zu erstellen, die nur statische Member definiert. dh Konstanten und 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;
    }

}

Unveränderliche Klassen sollten auch als final deklariert werden. (Eine unveränderliche Klasse ist eine, deren Instanzen nicht mehr geändert werden können, nachdem sie erstellt wurden; siehe Thema Informierbare Objekte .) Dadurch wird das Erstellen einer veränderlichen Unterklasse einer unveränderlichen Klasse unmöglich. Dies würde gegen das Liskov-Substitutionsprinzip verstoßen, das verlangt, dass ein Subtyp dem "Verhaltensvertrag" seiner Supertypen gehorcht.

Aus praktischer Sicht ist es einfacher, eine unveränderliche Klasse als final deklarieren, um das Programmverhalten zu begründen. Außerdem werden Sicherheitsbedenken in dem Szenario angesprochen, in dem nicht vertrauenswürdiger Code in einer Sicherheits-Sandbox ausgeführt wird. (Da String zum Beispiel als final deklariert ist, muss eine vertrauenswürdige Klasse nicht befürchten, dass sie dazu verleitet wird, veränderliche Unterklassen zu akzeptieren, die der nicht vertrauenswürdige Aufrufer dann unbemerkt ändern könnte.)

Ein Nachteil der final ist, dass sie nicht mit einigen spöttischen Frameworks wie Mockito funktionieren. Update: Mockito Version 2 unterstützt jetzt das Verspotten der Abschlussklassen.

Endgültige Methoden

Der final Modifikator kann auch auf Methoden angewendet werden, um zu verhindern, dass sie in Unterklassen überschrieben werden:

public class MyClassWithFinalMethod {

    public final void someMethod() {
    }
}

public class MySubClass extends MyClassWithFinalMethod {

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

Endgültige Methoden werden normalerweise verwendet, wenn Sie einschränken möchten, was eine Unterklasse in einer Klasse ändern kann, ohne Unterklassen vollständig zu verbieten.


Der final Modifikator kann auch auf Variablen angewendet werden, die Bedeutung von final für Variablen steht jedoch nicht in Zusammenhang mit der Vererbung.

Das Liskov-Substitutionsprinzip

Ersetzbarkeit ist ein Prinzip in der objektorientierten Programmierung von Barbara Liskov in einem 1987 Konferenz Keynote eingeführt besagt , dass, wenn die Klasse B eine Unterklasse der Klasse ist A , dann überall dort , wo A erwartet wird, B kann stattdessen verwendet werden:

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

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

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

Dies gilt auch, wenn es sich bei dem Typ um eine Schnittstelle handelt, bei der keine hierarchische Beziehung zwischen den Objekten erforderlich ist:

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

Die Liste enthält jetzt Objekte, die nicht zu derselben Klassenhierarchie gehören.

Erbe

Mit der Verwendung der extends Schlüsselwort unter Klassen, alle Eigenschaften der Oberklasse sind in der Unterklasse vorhanden ist (auch als übergeordnete Klasse oder Basisklasse genannt) (auch als Child - Klasse oder Abgeleitete Klasse bekannt)

public class BaseClass {

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

public class SubClass extends BaseClass {

}

Instanzen von SubClass haben die Methode baseMethod() geerbt :

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

Zusätzlicher Inhalt kann zu einer Unterklasse hinzugefügt werden. Dies ermöglicht zusätzliche Funktionen in der Unterklasse, ohne die Basisklasse oder andere Unterklassen derselben Basisklasse zu ändern:

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" 

Felder werden auch vererbt:

public class BaseClassWithField {

    public int x;

}

public class SubClassWithField extends BaseClassWithField {

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

private Felder und Methoden sind in der Unterklasse noch vorhanden, aber nicht zugänglich:

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 kann jede Klasse höchstens eine andere Klasse erweitern.

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

Dies wird als Mehrfachvererbung bezeichnet. In einigen Sprachen ist dies zwar zulässig, in Java ist dies jedoch nicht für Klassen zulässig.

Infolgedessen verfügt jede Klasse über eine unverzweigte vorgelagerte Klassenkette, die zu Object , von der alle Klassen abstammen.

Vererbung und statische Methoden

In Java können übergeordnete und untergeordnete Klassen statische Methoden mit demselben Namen verwenden. Aber in solchen Fällen der Umsetzung von statischer Methode in Kind versteckt Elternklasse Implementierung, es ist kein Verfahren überwiegende. Zum Beispiel:

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 sind an eine Klasse gebunden, nicht an eine Instanz, und diese Methodenbindung erfolgt zur Kompilierzeit. Da in dem ersten Aufruf von staticMethod() , Elternklasse Referenz p wurde verwendet, Parent ‚s Version von staticMethod() aufgerufen wird. Im zweiten Fall haben wir p in die Child Klasse umgewandelt, die staticMethod() Child ausgeführt hat.

Variable Abschattung

Variablen sind SHADOWED und Methoden sind OVERRIDDEN. Welche Variable verwendet wird, hängt von der Klasse ab, für die die Variable deklariert ist. Welche Methode verwendet wird, hängt von der tatsächlichen Klasse des Objekts ab, auf die die Variable verweist.

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
    }
}

Verengen und Erweitern von Objektreferenzen

Umwandlung einer Instanz einer Basisklasse in eine Unterklasse wie in: b = (B) a; wird als Verengung bezeichnet (wenn Sie versuchen, das Basisklassenobjekt auf ein spezifischeres Klassenobjekt zu beschränken) und erfordert eine explizite Typumwandlung.

Umwandlung einer Instanz einer Unterklasse in eine Basisklasse wie in: A a = b; wird als Verbreiterung bezeichnet und benötigt keinen Typcast.

Zur Veranschaulichung betrachten Sie die folgenden Klassendeklarationen und den 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
    }
}

Die Aussage Vehicle vehicle = new Car(); ist eine gültige Java-Anweisung. Jede Instanz Car ist auch ein Vehicle . Daher ist die Zuweisung legal, ohne dass eine explizite Typisierung erforderlich ist.

Andererseits ist Car c = vehicle; ist ungültig. Der statische Typ der vehicle ist Vehicle Dies bedeutet, dass sie sich auf eine Instanz von Car , Truck , MotorCycle , or any other current or future subclass of Vehicle beziehen kann . (Or indeed, an instance of Vehicle itself, since we did not declare it as an abstrakte 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 car referring to a Truck-Instanz verweist.

Um dies zu vermeiden, müssen wir eine explizite Typumwandlung hinzufügen:

Car c = (Car) vehicle;

Der Typ-Cast teilt dem Compiler mit, dass wir davon ausgehen, dass der Wert eines vehicle ein Car oder eine Unterklasse des Car . Bei Bedarf fügt der Compiler Code ein, um eine Laufzeitprüfung durchzuführen. Wenn die Prüfung fehlschlägt, wird eine ClassCastException ausgelöst, wenn der Code ausgeführt wird.

Beachten Sie, dass nicht alle Typumwandlungen gültig sind. Zum Beispiel:

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

Der Java-Compiler weiß, dass eine Instanz, die mit Vehicle kompatibel ist, nicht immer mit String kompatibel sein kann. Die Typumwandlung konnte niemals erfolgreich sein, und die JLS schreibt vor, dass dies zu einem Kompilierungsfehler führt.

Programmierung an einer Schnittstelle

Die Idee hinter der Programmierung einer Schnittstelle besteht darin, den Code hauptsächlich auf Schnittstellen aufzubauen und nur zum Zeitpunkt der Instantiierung konkrete Klassen zu verwenden. In diesem Zusammenhang sieht guter Code, der sich beispielsweise mit Java-Sammlungen befasst, ungefähr so ​​aus (nicht, dass die Methode selbst von Nutzen ist, sondern nur zur Veranschaulichung):

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

während schlechter Code so aussehen könnte:

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

Nicht nur der erstere kann auf eine breitere Auswahl von Argumenten angewendet werden, sondern seine Ergebnisse werden mit dem von anderen Entwicklern bereitgestellten Code, der im Allgemeinen dem Konzept der Programmierung einer Schnittstelle entspricht, besser kompatibel. Die wichtigsten Gründe für die Verwendung des ersteren sind jedoch:

  • Meistens benötigt und sollte der Kontext, in dem das Ergebnis verwendet wird, nicht so viele Details wie die konkrete Implementierung vorsieht.
  • Das Befolgen einer Schnittstelle erzwingt einen saubereren Code, und weniger Hacks wie beispielsweise eine weitere öffentliche Methode werden einer Klasse hinzugefügt, die ein bestimmtes Szenario abdeckt.
  • Der Code ist besser zu testen, da Schnittstellen leicht zu spotteln sind.
  • Schließlich hilft das Konzept auch, wenn nur eine Implementierung erwartet wird (zumindest für die Testbarkeit).

Wie kann man also das Konzept der Programmierung auf eine Schnittstelle anwenden, wenn man neuen Code schreibt und dabei eine bestimmte Implementierung vor Augen hat? Eine Option, die wir häufig verwenden, ist eine Kombination der folgenden Muster:

  • Programmierung an einer Schnittstelle
  • Fabrik
  • Baumeister

Das folgende, auf diesen Prinzipien basierende Beispiel ist eine vereinfachte und verkürzte Version einer RPC-Implementierung, die für verschiedene Protokolle geschrieben wurde:

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

Die obige Schnittstelle soll nicht direkt über eine Factory instanziiert werden. Stattdessen leiten wir weitere konkretere Schnittstellen ab, eine für den HTTP-Aufruf und eine für AMQP. Jede dieser Instanzen verfügt über eine Factory und einen Builder, um Instanzen zu erstellen, die wiederum Instanzen sind die obige Schnittstelle:

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

Instanzen von RemoteInvoker für die Verwendung mit AMQP können jetzt so einfach erstellt werden (oder je nach Builder mehr involviert sein):

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

Und ein Aufruf einer Anfrage ist so einfach wie:

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

Da Java 8 die AmqpInvoker.with() bietet, statische Methoden direkt in Schnittstellen AmqpInvoker.with() , wurde die Zwischenfactory im obigen Code implizit durch AmqpInvoker.with() . In Java vor Version 8 kann derselbe Effekt mit einer inneren Factory Klasse erzielt werden:

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

Die entsprechende Instanziierung würde sich dann in

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

Der oben verwendete Builder könnte so aussehen (obwohl dies eine Vereinfachung darstellt, da die tatsächliche Definition von bis zu 15 Parametern erlaubt, die von den Standardwerten abweichen). Beachten Sie, dass das Konstrukt nicht öffentlich ist und daher nur von der obigen AmqpInvoker Schnittstelle aus verwendet werden kann:

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);
  }
}

Im Allgemeinen kann ein Builder auch mit einem Tool wie FreeBuilder erstellt werden.

Die standardmäßige (und die einzig erwartete) Implementierung dieser Schnittstelle wird schließlich als paketlokale Klasse definiert, um die Verwendung der Schnittstelle, der Factory und des Builders zu erzwingen:

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) {
    ...
  }
}

In der Zwischenzeit erwies sich dieses Muster als sehr effizient bei der Entwicklung unseres gesamten neuen Codes, unabhängig davon, wie einfach oder komplex die Funktionalität ist.

Abstrakte Klassen- und Interface-Nutzung: "Is-a" Relation vs. "Has-a" -Funktion

Wann sollten abstrakte Klassen verwendet werden: Um dasselbe oder unterschiedliches Verhalten bei mehreren verwandten Objekten zu implementieren

Wann sind Schnittstellen zu verwenden: um einen Vertrag durch mehrere nicht zusammenhängende Objekte zu implementieren

Abstrakte Klassen erstellen "ist eine" Beziehung, während Schnittstellen "eine Funktion" haben.

Dies ist im folgenden Code zu sehen:

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;
    }
}

Ausgabe:

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

Wichtige Hinweise:

  1. Animal ist eine abstrakte Klasse mit gemeinsamen Attributen: name und lifeExpectancy und abstrakte Methoden: remember() und protectOwner() . Dog und Cat sind Animals , die die Methoden remember() und protectOwner() implementiert haben.

  2. Cat kann climb() , Dog jedoch nicht. Dog kann think() , Cat aber nicht. Diese spezifischen Funktionen werden Cat und Dog bei der Implementierung hinzugefügt.

  3. Man ist kein Animal aber er kann Think , Learn , sich Apply und Climb .

  4. Cat ist kein Man aber sie kann Climb .

  5. Dog ist kein Man aber er kann Learn

  6. Man ist weder eine Cat noch ein Dog , kann jedoch einige der Fähigkeiten der beiden letztgenannten besitzen, ohne Animal , Cat oder Dog . Dies geschieht mit Interfaces.

  7. Obwohl Animal eine abstrakte Klasse ist, hat sie im Gegensatz zu einer Schnittstelle einen Konstruktor.

TL; DR:

Nicht verbundene Klassen können über Schnittstellen über Funktionen verfügen, verwandte Klassen ändern jedoch das Verhalten durch Erweiterung der Basisklassen.

Auf der Java-Dokumentation finden Sie Informationen darüber , welche in einem bestimmten Anwendungsfall verwendet werden soll.

Erwägen Sie die Verwendung abstrakter Klassen, wenn ...

  1. Sie möchten Code unter mehreren eng miteinander verwandten Klassen teilen.
  2. Sie erwarten, dass Klassen, die Ihre abstrakte Klasse erweitern, viele allgemeine Methoden oder Felder aufweisen oder andere Zugriffsmodifizierer als public (wie protected und private) erfordern.
  3. Sie möchten nicht statische oder nicht abschließende Felder deklarieren.

Erwägen Sie die Verwendung von Schnittstellen, wenn ...

  1. Sie erwarten, dass nicht verknüpfte Klassen Ihre Schnittstelle implementieren. Beispielsweise können viele nicht zusammenhängende Objekte die Serializable Schnittstelle implementieren.
  2. Sie möchten das Verhalten eines bestimmten Datentyps angeben, machen sich jedoch keine Gedanken darüber, wer sein Verhalten implementiert.
  3. Sie möchten die Mehrfachvererbung des Typs nutzen.

Überschreibung bei Vererbung

Das Überschreiben in Vererbung wird verwendet, wenn Sie eine bereits definierte Methode aus einer Superklasse in einer Unterklasse verwenden, jedoch auf eine andere Weise als bei der ursprünglichen Erstellung der Methode in der Superklasse. Durch das Überschreiben kann der Benutzer den Code wiederverwenden, indem er vorhandenes Material verwendet und es an die Bedürfnisse des Benutzers anpasst.


Das folgende Beispiel ClassA , wie ClassB die Funktionalität von ClassA indem ClassA wird, was durch die Druckmethode gesendet wird:

Beispiel:

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

Ausgabe:

EIN

B



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow