Ricerca…


introduzione

L'ereditarietà è una caratteristica di base orientata agli oggetti in cui una classe acquisisce e si estende alle proprietà di un'altra classe, utilizzando la parola chiave extends . Per Interfacce e le implements parole chiave, vedere le interfacce .

Sintassi

  • class ClassB estende ClassA {...}
  • classe ClassB implementa InterfaceA {...}
  • interfaccia InterfaceB estende InterfaceA {...}
  • class ClassB estende le implementazioni di ClassA InterfaceC, InterfaceD {...}
  • abstract class AbstractClassB estende ClassA {...}
  • abstract class AbstractClassB estende AbstractClassA {...}
  • abstract class AbstractClassB estende le implementazioni di ClassA InterfaceC, InterfaceD {...}

Osservazioni

L'ereditarietà è spesso combinata con i generici in modo che la classe base abbia uno o più parametri di tipo. Vedi Creazione di una classe generica .

Classi astratte

Una classe astratta è una classe contrassegnata con la parola chiave abstract . Esso, contrariamente alla classe non astratta, può contenere metodi astratti - implementazione-meno. È tuttavia valido creare una classe astratta senza metodi astratti.

Una classe astratta non può essere istanziata. Può essere sottoclassato (esteso) finché la sottoclasse è anch'essa astratta, o implementa tutti i metodi contrassegnati come astratti da super classi.

Un esempio di una classe astratta:

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

    public abstract void render();
}

La classe deve essere contrassegnata come astratta, quando ha almeno un metodo astratto. Un metodo astratto è un metodo che non ha implementazione. Altri metodi possono essere dichiarati all'interno di una classe astratta che ha implementazione al fine di fornire codice comune per qualsiasi sottoclasse.

Il tentativo di creare un'istanza di questa classe fornirà un errore di compilazione:

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

Tuttavia una classe che estende Component e fornisce un'implementazione per tutti i suoi metodi astratti e può essere istanziata.

public class Button extends Component {

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

public class TextBox extends Component {

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

Anche le istanze di classi ereditanti possono essere espresse come classe genitore (normale ereditarietà) e forniscono un effetto polimorfico quando viene chiamato il metodo astratto.

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

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

Classi astratte vs interfacce

Le classi e le interfacce astratte forniscono entrambi un modo per definire le firme dei metodi, mentre richiedono la classe di estensione / implementazione per fornire l'implementazione.

Ci sono due differenze chiave tra classi astratte e interfacce:

  • Una classe può estendere solo una singola classe, ma può implementare molte interfacce.
  • Una classe astratta può contenere campi di istanza (non static ), ma le interfacce possono contenere solo campi static .
Java SE 8

I metodi dichiarati nelle interfacce non potevano contenere implementazioni, quindi sono state utilizzate classi astratte quando era utile fornire metodi aggiuntivi quali implementazioni chiamate metodi astratti.

Java SE 8

Java 8 consente alle interfacce di contenere metodi predefiniti , solitamente implementati utilizzando gli altri metodi dell'interfaccia , rendendo le interfacce e le classi astratte ugualmente potenti in questo senso.

Sottoclassi anonime di classi astratte

Per comodità java consente l'istanziazione di istanze anonime di sottoclassi di classi astratte, che forniscono implementazioni per i metodi astratti sulla creazione del nuovo oggetto. Usando l'esempio precedente questo potrebbe assomigliare a questo:

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

Eredità statica

Il metodo statico può essere ereditato in modo simile ai metodi normali, tuttavia a differenza dei metodi normali è impossibile creare metodi " astratti " per forzare l'override del metodo statico. Scrivere un metodo con la stessa firma di un metodo statico in una super classe sembra essere una forma di sovrascrittura, ma in realtà ciò crea semplicemente una nuova funzione che nasconde l'altro.

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

}

L'esecuzione di una di queste classi produce l'output:

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

Si noti che, a differenza dell'ereditarietà normale, i metodi di ereditarietà statica non sono nascosti. Puoi sempre chiamare il metodo sayHello base usando BaseClass.sayHello() . Ma le classi ereditano metodi statici se nella sottoclasse non vengono trovati metodi con la stessa firma. Se le firme di due metodi variano, entrambi i metodi possono essere eseguiti dalla sottoclasse, anche se il nome è lo stesso.

I campi statici si nascondono l'un l'altro in modo simile.

Usare 'final' per limitare l'ereditarietà e la sovrascrittura

Classi finali

Se utilizzato in una dichiarazione di class , il modificatore final impedisce che vengano dichiarate altre classi che extend la classe. Una classe final è una classe "leaf" nella gerarchia delle classi di ereditarietà.

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

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

Casi d'uso per le classi finali

Le classi finali possono essere combinate con un costruttore private per controllare o impedire l'istanziazione di una classe. Questo può essere usato per creare una cosiddetta "utility class" che definisce solo membri statici; cioè costanti e metodi statici.

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

}

Anche le classi immutabili dovrebbero essere dichiarate final . (Una classe immutabile è quella le cui istanze non possono essere modificate dopo che sono state create, vedere l'argomento I mmutable Objects .) Facendo ciò, è impossibile creare una sottoclasse mutabile di una classe immutabile. Ciò violerebbe il Principio di sostituzione di Liskov che richiede che un sottotipo obbedisca al "contratto comportamentale" dei suoi supertipi.

Dal punto di vista pratico, dichiarare che una classe immutabile è final rende più facile ragionare sul comportamento del programma. Affronta anche i problemi di sicurezza nello scenario in cui un codice non affidabile viene eseguito in una sandbox di sicurezza. (Ad esempio, dal momento che String è dichiarata final , una classe fidata non ha bisogno di preoccuparsi che possa essere ingannato ad accettare sottoclassi mutabili, che il chiamante non sicuro potrebbe quindi cambiare surrettiziamente).

Uno svantaggio delle classi final è che non funzionano con alcuni framework di derisione come Mockito. Aggiornamento: la versione 2 di Mockito ora supporta il mocking delle classi finali.

Metodi finali

Il modificatore final può anche essere applicato ai metodi per impedire che vengano sovrascritti nelle sottoclassi:

public class MyClassWithFinalMethod {

    public final void someMethod() {
    }
}

public class MySubClass extends MyClassWithFinalMethod {

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

I metodi finali vengono in genere utilizzati quando si desidera limitare ciò che una sottoclasse può modificare in una classe senza proibire completamente le sottoclassi.


Il modificatore final può essere applicato anche alle variabili, ma il significato di final per le variabili non è correlato all'ereditarietà.

Il principio di sostituzione di Liskov

La sostituibilità è un principio nella programmazione orientata agli oggetti introdotta da Barbara Liskov in una conferenza del 1987 che afferma che, se la classe B è una sottoclasse di classe A , allora dovunque A è previsto, B può essere usato invece:

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

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

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

Ciò si applica anche quando il tipo è un'interfaccia, in cui non è necessario alcun rapporto gerarchico tra gli oggetti:

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

Ora l'elenco contiene oggetti che non appartengono alla stessa gerarchia di classi.

Eredità

Con l'uso della parola chiave extends tra classi, tutte le proprietà della superclasse (anche note come Parent Class o Base Class ) sono presenti nella sottoclasse (anche conosciuta come Child Class o Derived Class )

public class BaseClass {

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

public class SubClass extends BaseClass {

}

Le istanze di SubClass hanno ereditato il metodo baseMethod() :

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

Contenuti aggiuntivi possono essere aggiunti a una sottoclasse. Ciò consente funzionalità aggiuntive nella sottoclasse senza alcuna modifica alla classe base o alle altre sottoclassi della stessa classe base:

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" 

I campi sono anche ereditati:

public class BaseClassWithField {

    public int x;

}

public class SubClassWithField extends BaseClassWithField {

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

campi e metodi private esistono ancora all'interno della sottoclasse, ma non sono accessibili:

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, ogni classe può estendere al massimo una classe.

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

Questo è noto come ereditarietà multipla, e mentre è legale in alcune lingue, Java non lo consente con le classi.

Come risultato di ciò, ogni classe ha una catena ancestrale di classi che conduce ad Object , da cui tutte le classi discendono.

Ereditarietà e metodi statici

In Java, entrambe le classi genitore e figlio possono avere metodi statici con lo stesso nome. Ma in questi casi l'implementazione del metodo statico in child sta nascondendo l'implementazione della classe genitore, non è un metodo prioritario. Per esempio:

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

I metodi statici sono associati a una classe non a un'istanza e questo binding di metodo avviene in fase di compilazione. Dal momento che nella prima chiamata a staticMethod() , genitore di riferimento di classe p è stato utilizzato, Parent versione s' di staticMethod() viene richiamato. Nel secondo caso, si gettò p in Child di classe, Child 's staticMethod() eseguito.

Ombreggiamento variabile

Le variabili sono SHADOWED e i metodi sono OVERRIDDEN. Quale variabile verrà utilizzata dipende dalla classe di dichiarata della variabile. Quale metodo verrà utilizzato dipende dalla classe effettiva dell'oggetto a cui fa riferimento la variabile.

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

Restringimento e ampliamento dei riferimenti a oggetti

Trasmettere un'istanza di una classe base ad una sottoclasse come in: b = (B) a; è chiamato restringimento (come si sta tentando di restringere l'oggetto della classe base a un oggetto di classe più specifico) e ha bisogno di un cast di tipo esplicito.

Trasmettere un'istanza di una sottoclasse a una classe base come in: A a = b; si chiama allargamento e non ha bisogno di un cast di tipo.

Per illustrare, considera le seguenti dichiarazioni di classe e il codice di prova:

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

La dichiarazione Vehicle vehicle = new Car(); è una dichiarazione Java valida. Ogni istanza di Car è anche un Vehicle . Pertanto, l'assegnazione è legale senza la necessità di un cast esplicito del tipo.

D'altra parte, Car c = vehicle; non è valido. Il tipo statico della variabile del vehicle è Vehicle che significa che potrebbe fare riferimento a un'istanza di Car , Truck , MotorCycle , or any other current or future subclass of veicolo . (Or indeed, an instance of Veicolo itself, since we did not declare it as an class.) The assignment cannot be allowed, since that might lead to astratta class.) The assignment cannot be allowed, since that might lead to all'automobile che si referring to a un'istanza di Truck`.

Per evitare questa situazione, dobbiamo aggiungere un cast di tipo esplicito:

Car c = (Car) vehicle;

Il cast del tipo dice al compilatore che ci aspettiamo che il valore del vehicle sia Car o una sottoclasse di Car . Se necessario, il compilatore inserirà il codice per eseguire un controllo del tipo di esecuzione. Se il controllo fallisce, verrà generata una ClassCastException momento dell'esecuzione del codice.

Nota che non tutti i cast di tipi sono validi. Per esempio:

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

Il compilatore Java sa che un'istanza di tipo compatibile con Vehicle non può mai essere compatibile con String . Il cast del tipo non potrebbe mai avere successo, e il JLS impone che questo dia in un errore di compilazione.

Programmazione su un'interfaccia

L'idea alla base della programmazione di un'interfaccia è di basare il codice principalmente sulle interfacce e utilizzare solo classi concrete al momento dell'istanziazione. In questo contesto, un buon codice che riguarda, ad esempio, le raccolte Java avrà un aspetto simile a questo (non che il metodo stesso sia di qualche utilità, solo illustrazione):

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

mentre il codice errato potrebbe apparire come questo:

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

Non solo il primo può essere applicato a una più ampia scelta di argomenti, i suoi risultati saranno più compatibili con il codice fornito da altri sviluppatori che generalmente aderiscono al concetto di programmazione di un'interfaccia. Tuttavia, i motivi più importanti per utilizzare il primo sono:

  • il più delle volte il contesto in cui viene utilizzato il risultato non richiede e non dovrebbe richiedere molti dettagli come prevede l'implementazione concreta;
  • aderire a un'interfaccia costringe un codice più pulito e meno hacks come un altro metodo pubblico viene aggiunto a una classe che serve uno scenario specifico;
  • il codice è più testabile in quanto le interfacce sono facilmente raggirabili;
  • infine, il concetto aiuta anche se è prevista solo un'implementazione (almeno per testabilità).

Quindi, come si può facilmente applicare il concetto di programmazione a un'interfaccia quando si scrive un nuovo codice avendo in mente una particolare implementazione? Un'opzione che usiamo comunemente è una combinazione dei seguenti modelli:

  • programmazione su un'interfaccia
  • fabbrica
  • costruttore

L'esempio seguente basato su questi principi è una versione semplificata e troncata di un'implementazione RPC scritta per un numero di protocolli diversi:

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

L'interfaccia di cui sopra non dovrebbe essere istanziata direttamente tramite una factory, ma deriviamo ulteriori interfacce concrete, una per l'invocazione HTTP e una per AMQP, ognuna delle quali ha una factory e un builder per costruire istanze, che a loro volta sono anche esempi di l'interfaccia di cui sopra:

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

Le istanze di RemoteInvoker per l'uso con AMQP possono ora essere costruite con la stessa facilità con cui (o sono più coinvolte a seconda del costruttore):

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

E l'invocazione di una richiesta è facile come:

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

A causa di Java 8 che consente il posizionamento di metodi statici direttamente nelle interfacce, lo stabilimento intermedio è diventato implicito nel codice sopra sostituito con AmqpInvoker.with() . In Java precedente alla versione 8, lo stesso effetto può essere ottenuto con una classe Factory interna:

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

L'istanziazione corrispondente si trasformerebbe quindi in:

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

Il builder usato sopra potrebbe apparire come questo (sebbene si tratti di una semplificazione in quanto quello attuale consente di definire fino a 15 parametri che si discostano dai valori predefiniti). Nota che il costrutto non è pubblico, quindi è specificamente utilizzabile solo dalla precedente interfaccia di AmqpInvoker :

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

Generalmente, un builder può anche essere generato usando uno strumento come FreeBuilder.

Infine, l'implementazione standard (e l'unica prevista) di questa interfaccia è definita come una classe package-local per imporre l'uso dell'interfaccia, della factory e del builder:

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

Nel frattempo, questo schema si è dimostrato molto efficace nello sviluppo di tutto il nostro nuovo codice, non importa quanto semplice o complessa sia la funzionalità.

Uso astratto della classe e dell'interfaccia: capacità "Is-a" rispetto a "Has-a"

Quando utilizzare le classi astratte: per implementare lo stesso comportamento o un comportamento diverso tra più oggetti correlati

Quando utilizzare le interfacce: per implementare un contratto con più oggetti non correlati

Le classi astratte creano relazioni "è un" mentre le interfacce forniscono "ha" una capacità.

Questo può essere visto nel codice qui sotto:

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

produzione:

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

Note chiave:

  1. Animal è una classe astratta con attributi condivisi: name e lifeExpectancy Metodi lifeExpectancy e astratti: remember() e protectOwner() . Dog e Cat sono Animals che hanno implementato i metodi remember() e protectOwner() .

  2. Cat può climb() ma Dog non può. Dog può think() ma Cat non può. Queste funzionalità specifiche vengono aggiunte a Cat and Dog mediante l'implementazione.

  3. Man non è un Animal ma può Think , Learn , Apply e Climb .

  4. Cat non è un Man ma può Climb .

  5. Dog non è un Man ma può Learn

  6. Man non è né un Cat né un Dog ma può avere alcune delle capacità degli ultimi due senza estendere Animal , Cat o Dog . Questo è fatto con le interfacce.

  7. Anche se Animal è una classe astratta, ha un costruttore, a differenza di un'interfaccia.

TL; DR:

Le classi non correlate possono avere capacità attraverso le interfacce, ma le classi correlate cambiano il comportamento attraverso l'estensione delle classi di base.

Fare riferimento alla pagina della documentazione Java per capire quale utilizzare in un caso d'uso specifico.

Prendi in considerazione l'utilizzo di classi astratte se ...

  1. Vuoi condividere il codice tra diverse classi strettamente correlate.
  2. Ti aspetti che le classi che estendono la tua classe astratta abbiano molti metodi o campi comuni o richiedano modificatori di accesso diversi da quelli pubblici (come protetti e privati).
  3. Si desidera dichiarare campi non statici o non finali.

Prendi in considerazione l'utilizzo di interfacce se ...

  1. Ti aspetti che le classi non correlate implementino la tua interfaccia. Ad esempio, molti oggetti non collegati possono implementare l'interfaccia Serializable .
  2. Si desidera specificare il comportamento di un particolare tipo di dati ma non si preoccupano di chi implementa il suo comportamento.
  3. Vuoi sfruttare l'ereditarietà multipla del tipo.

Overriding in Inheritance

Overriding in Inheritance viene utilizzato quando si utilizza un metodo già definito da una super classe in una sottoclasse, ma in un modo diverso rispetto al modo in cui il metodo è stato originariamente progettato nella super classe. L'override consente all'utente di riutilizzare il codice utilizzando il materiale esistente e modificandolo in base alle esigenze dell'utente.


L'esempio seguente mostra come ClassB sovrascrive la funzionalità di ClassA modificando ciò che viene inviato tramite il metodo di stampa:

Esempio:

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

Produzione:

UN

B



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow