Öröklődés és interfészek
Extends vs implements, default metódusok, sealed osztályok és absztrakt osztályok
📖 Objektumorientált programozás 5 témakör
📑 Tartalomjegyzék 9
Öröklődés és kompozíció
Az öröklődés (inheritance) és a kompozíció (composition) a két alapvető mechanizmus, amellyel osztályok közötti kapcsolatokat építhetünk Java-ban. A helyes választás kulcsfontosságú a karbantartható, rugalmas szoftverarchitektúrához.
1. Definíció
Mi ez?
- Inheritance (öröklődés): Egy „is-a" kapcsolat, ahol egy osztály (
subclass) egy másik osztály (superclass) összes publikus és protected tagját megörökli azextendskulcsszóval. - Composition (kompozíció): Egy „has-a" kapcsolat, ahol egy osztály egy másik osztály példányát mezőként tartalmazza, és delegálja neki a feladatokat.
Miért létezik mindkettő?
- Az öröklődés lehetővé teszi a kód újrafelhasználást és a polimorfizmust.
- A kompozíció lazább csatolást (loose coupling) és nagyobb rugalmasságot biztosít.
- A kettő együtt teszi lehetővé, hogy a megfelelő absztrakciós szintet válasszuk az adott problémára.
Hol helyezkedik el?
Ez az OOP paradigma két pillére — a legtöbb design pattern (Strategy, Decorator, Template Method stb.) ezekre épül.
2. Alapfogalmak
2.1 `extends` kulcsszó
A Java-ban egy osztály pontosan egy másik osztályt terjeszthet ki (single inheritance):
public class Animal {
protected String name;
public void eat() {
System.out.println(name + " is eating");
}
}
public class Dog extends Animal {
public void bark() {
System.out.println(name + " says Woof!");
}
}
2.2 `super` kulcsszó
A super két fő célt szolgál:
| Használat | Szintaxis | Leírás |
|---|---|---|
| Konstruktor hívás | super(args) |
Az ősosztály konstruktorát hívja — kötelezően az első utasítás |
| Metódus hívás | super.method() |
Az ősosztály metódusát hívja (felülírt metódus esetén) |
| Mező hozzáférés | super.field |
Az ősosztály mezőjét éri el (ha a gyermekosztály elfedi) |
public class Dog extends Animal {
public Dog(String name) {
super(); // Animal() konstruktor hívás
this.name = name;
}
@Override
public void eat() {
super.eat(); // Animal.eat() meghívása
System.out.println("...and wants more!");
}
}
2.3 Method overriding és `@Override`
Felülírás szabályai:
| Szabály | Leírás |
|---|---|
| Szignátura | Pontosan egyező metódusnév és paraméterek |
| Return type | Ugyanaz vagy covariant (alosztály) return type |
| Access modifier | Ugyanaz vagy tágabb (pl. protected → public) |
| Exception | Nem dobhat tágabb checked exception-t |
final metódus |
Nem írható felül |
static metódus |
Nem overriding, hanem hiding |
⚠️ Az
@Overrideannotáció használata nem kötelező, de erősen ajánlott — compile-time hibát ad, ha nem tényleges felülírás.
2.4 Constructor chaining
A konstruktorhívás sorrendje mindig fentről lefelé halad az öröklődési hierarchiában:
public class Animal {
public Animal() { System.out.println("Animal()"); }
}
public class Dog extends Animal {
public Dog() { System.out.println("Dog()"); } // ← implicit super()
}
public class Puppy extends Dog {
public Puppy() { System.out.println("Puppy()"); } // ← implicit super()
}
// new Puppy() kimenete:
// Animal()
// Dog()
// Puppy()
2.5 `final` osztályok és metódusok
| Módosító | Hatás |
|---|---|
final class |
Az osztály nem terjeszthető ki (pl. String, Integer) |
final method |
A metódus nem írható felül az alosztályban |
2.6 Kompozíció (has-a) és delegálás
A kompozíció nem nyelvi szintű kulcsszó — egy tervezési minta, ahol egy osztály egy másik példányát tartalmazza:
public class Engine {
public void start() { System.out.println("Engine started"); }
}
public class Car {
private final Engine engine; // has-a kapcsolat
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start(); // delegálás
}
}
2.7 Diamond problem — miért nincs multiple inheritance?
A Java nem támogatja a többszörös osztály-öröklést:
Animal
/ \
Flyer Swimmer
\ /
FlyingFish ← NEM LEHETSÉGES extends Flyer, Swimmer
Megoldás: interfészek (implements) és Java 8+ default metódusok.
public interface Flyer { default void move() { System.out.println("Fly"); } }
public interface Swimmer { default void move() { System.out.println("Swim"); } }
public class FlyingFish implements Flyer, Swimmer {
@Override
public void move() {
Flyer.super.move(); // explicit feloldás
}
}
2.8 Liskov Substitution Principle (LSP)
„Ha S a T alosztálya, akkor T típusú objektumok helyettesíthetők S típusú objektumokkal a program viselkedésének megváltozása nélkül."
Egyszerűen: az alosztály minden helyen működjön, ahol az ősosztályt használják.
3. Gyakorlati használat
„Favor composition over inheritance" (GoF)
Ez a Gang of Four Design Patterns könyv egyik legfontosabb tanácsa.
| Szempont | Inheritance | Composition |
|---|---|---|
| Kapcsolat típusa | is-a | has-a |
| Csatolás | Szoros (tight coupling) | Laza (loose coupling) |
| Rugalmasság | Compile-time eldöntött | Runtime cserélhető |
| Újrafelhasználás | Vertikális (hierarchia) | Horizontális (delegálás) |
| Kapszulázás | Gyengíti (subclass ismeri a parent-et) | Erősíti |
Mikor jó az öröklődés?
- Valódi is-a kapcsolat —
DogténylegAnimal - Framework-ök —
HttpServlet,AbstractControllerkiterjesztése - Template Method pattern — absztrakt osztály definiálja a vázat
- Sealed hierarchiák (Java 17+) — zárt domain modell
Mikor jobb a kompozíció?
- Viselkedés futásidőben kell változzon (Strategy pattern)
- Több különböző viselkedés kombinálása
- Az „is-a" kapcsolat nem természetes
- A szülőosztály nem a mi kontrolunk alatt áll
4. Kód példák
4.1 Basic — Klasszikus öröklődés felülírással
public abstract class Shape {
public abstract double area();
@Override
public String toString() {
return getClass().getSimpleName() + " [area=" + area() + "]";
}
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
@Override
public double area() { return Math.PI * radius * radius; }
}
public class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() { return width * height; }
}
4.2 Advanced — Kompozícióval az öröklődés helyett
Probléma: InstrumentedHashSet örökölne HashSet-ből, de a addAll() belül add()-ot hív → dupla számlálás (Effective Java Item 18).
// ❌ ROSSZ — fragile base class problem
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c); // ← ez meghívja this.add()-ot! → dupla számlálás
}
}
// ✅ JÓ — composition + delegation (forwarding wrapper)
public class InstrumentedSet<E> implements Set<E> {
private final Set<E> delegate; // kompozíció
private int addCount = 0;
public InstrumentedSet(Set<E> delegate) {
this.delegate = delegate;
}
@Override
public boolean add(E e) {
addCount++;
return delegate.add(e); // delegálás
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return delegate.addAll(c); // nincs dupla számlálás!
}
// ... többi Set metódus delegálása ...
@Override public int size() { return delegate.size(); }
@Override public boolean isEmpty() { return delegate.isEmpty(); }
// stb.
}
4.3 Decorator pattern teaser — kompozíció a gyakorlatban
public interface Logger {
void log(String message);
}
public class ConsoleLogger implements Logger {
@Override
public void log(String message) { System.out.println(message); }
}
public class TimestampLogger implements Logger {
private final Logger delegate; // kompozíció
public TimestampLogger(Logger delegate) {
this.delegate = delegate;
}
@Override
public void log(String message) {
delegate.log("[" + Instant.now() + "] " + message); // dekoráció
}
}
// Használat — futásidőben kombinálható:
Logger logger = new TimestampLogger(new ConsoleLogger());
logger.log("Hello!"); // → [2026-04-04T10:00:00Z] Hello!
5. Trade-offok
Öröklődés előnyei és hátrányai
| ✅ Előny | ❌ Hátrány |
|---|---|
| Természetes hierarchia kifejezése | Tight coupling az ős- és gyermekosztály között |
| Polimorfizmus natívan elérhető | Fragile base class problem |
| Kevesebb boilerplate kód | Egyetlen öröklődési lánc (single inheritance) |
| Framework kiterjesztés egyszerű | Hierarchia mélysége kezelhetetlenné válhat |
Kompozíció előnyei és hátrányai
| ✅ Előny | ❌ Hátrány |
|---|---|
| Loose coupling — komponensek függetlenek | Több kód (delegáló metódusok) |
| Runtime cserélhetőség | Nincs natív polimorfizmus (interface kell) |
| Több viselkedés kombinálható | Néha bonyolultabb a struktúra |
| Jobban tesztelhető (mock-olható) |
6. Gyakori hibák
❌ 1. LSP megsértése
// Rectangle és Square probléma
public class Rectangle {
protected int width, height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int area() { return width * height; }
}
public class Square extends Rectangle {
@Override public void setWidth(int w) { this.width = this.height = w; } // ← LSP sérül!
@Override public void setHeight(int h) { this.width = this.height = h; }
}
// A kliens kód eltörik:
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(3);
assert r.area() == 15; // FAIL! area() == 9
❌ 2. Túl mély öröklődési hierarchiák
Példa egy túlnőtt hierarchiára: Object → BaseEntity → AuditableEntity → SoftDeletableEntity → User → AdminUser
🔴 5+ szintű hierarchia karbantarthatatlan. Max 2-3 szint ajánlott.
❌ 3. Overridable metódus hívása konstruktorból
public class Parent {
public Parent() {
init(); // ← VESZÉLYES! A gyermekosztály init()-je hívódik, de az még nincs inicializálva
}
protected void init() { /* ... */ }
}
public class Child extends Parent {
private final String value = "hello";
@Override
protected void init() {
System.out.println(value.length()); // NullPointerException! value még null
}
}
❌ 4. `@Override` annotáció elhagyása
// Elgépelés és nincs @Override → nem felülírás, hanem új metódus!
public class MyList extends ArrayList<String> {
public boolean Add(String s) { ... } // ← nagy 'A' — nem override!
}
7. Senior szintű meglátások
7.1 Effective Java Item 18
„Favor composition over inheritance" — Josh Bloch
Az öröklődés megtöri a kapszulázást, mert a subclass függ a superclass implementációs részleteitől (nem csak az API-tól). Ha a superclass belső működése megváltozik, a subclass eltörhet.
7.2 Template Method vs Strategy pattern
| Szempont | Template Method | Strategy |
|---|---|---|
| Mechanizmus | Inheritance — abstract metódus | Composition — interface referencia |
| Mikor dől el? | Compile-time | Runtime |
| Rugalmasság | Egy viselkedés variáció | Több, cserélhető viselkedés |
| Példa | AbstractList.get() |
Comparator átadása sort()-nak |
7.3 Sealed hierarchiák domain modellezéshez (Java 17+)
A sealed osztályok és interfészek zárt halmazra korlátozzák az alosztályokat:
public sealed interface PaymentMethod
permits CreditCard, BankTransfer, DigitalWallet {
}
public record CreditCard(String number, String expiry) implements PaymentMethod {}
public record BankTransfer(String iban) implements PaymentMethod {}
public record DigitalWallet(String provider) implements PaymentMethod {}
Előnyök:
- A fordító exhaustive check-et végez
switchexpression-ben - Biztonságos domain modell — senki nem adhat hozzá új implementációt
- Pattern matching-gel kombinálva nagyon kifejező kódot kapunk
String describe(PaymentMethod pm) {
return switch (pm) {
case CreditCard cc -> "Card ending in " + cc.number().substring(12);
case BankTransfer bt -> "IBAN: " + bt.iban();
case DigitalWallet dw -> "Wallet: " + dw.provider();
};
}
8. Szószedet
| Fogalom | Definíció |
|---|---|
| Inheritance | Egy osztály megörökli egy másik osztály tagjait (extends) |
| Composition | Egy osztály egy másik példányát mezőként tartalmazza (has-a) |
| Delegation | A feladat továbbadása a tartalmazott objektumnak |
| Method overriding | Az ősosztály metódusának felülírása az alosztályban |
| Constructor chaining | A konstruktorhívások láncolata az öröklődési hierarchiában |
| Fragile base class | Ha az ősosztály belső változtatása eltöri az alosztályt |
| LSP | Liskov Substitution Principle — az alosztály helyettesíthesse az őst |
| Diamond problem | Többszörös öröklődés okozta kétértelműség |
| Sealed class | Korlátozott alosztály-halmazzal rendelkező osztály (Java 17+) |
| Forwarding | Delegálás wrapper osztályon keresztül |
9. Gyorsreferencia
| Szempont | Öröklődés (is-a) |
Kompozíció (has-a) |
|---|---|---|
| Példa | class Dog extends Animal |
class Car { private Engine engine; } |
| Előny | Természetes hierarchia | Laza csatolás |
| Előny | Polimorfizmus | Runtime cserélhetőség |
| Hátrány | Tight coupling | Több boilerplate |
| Hátrány | Single inheritance | Nincs automatikus alaposztály-viselkedés |
@Overridemindig legyen ott — compile-time védelem.super()hívás konstruktorban csak első utasítás lehet.finalclass és method nem terjeszthető ki / írható felül.- Törekedj legfeljebb 2-3 szint mélységű hierarchiára.
- Az LSP szerint az alosztály helyettesíthesse az ősosztályt.
- Alapértelmezett döntésként inkább kompozíciót válassz.