Java Language
Erfenis
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 alleenstatic
velden bevatten.
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.
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:
Animal
is een abstracte klasse met gedeelde kenmerken:name
enlifeExpectancy
en abstracte methoden:remember()
enprotectOwner()
.Dog
enCat
zijnAnimals
die de methodenremember()
enprotectOwner()
hebben geïmplementeerd.Cat
kanclimb()
maarDog
niet.Dog
kanthink()
maarCat
niet. Deze specifieke mogelijkheden worden door implementatie aanCat
enDog
toegevoegd.Man
is geenAnimal
maar hij kanThink
,Learn
,Apply
enClimb
.Cat
is geenMan
maar hij kan welClimb
.Dog
is geenMan
maar hij kanLearn
Man
is noch eenCat
noch eenDog
maar kan sommige van de laatste twee hebben zonderAnimal
,Cat
ofDog
te breiden. Dit gebeurt met interfaces.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 ...
- U wilt code delen tussen verschillende nauw verwante klassen.
- 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é).
- U wilt niet-statische of niet-definitieve velden aangeven.
Overweeg het gebruik van interfaces als ...
- U verwacht dat niet-gerelateerde klassen uw interface zouden implementeren. Veel niet-gerelateerde objecten kunnen bijvoorbeeld de
Serializable
interface implementeren. - U wilt het gedrag van een bepaald gegevenstype opgeven, maar maakt u zich geen zorgen over wie het gedrag implementeert.
- 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