KözéphaladóOlvasási idő: ~18 perc

Öröklődés és interfészek

Extends vs implements, default metódusok, sealed osztályok és absztrakt osztályok

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 az extends kulcsszó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 @Override annotá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?

  1. Valódi is-a kapcsolatDog tényleg Animal
  2. Framework-ökHttpServlet, AbstractController kiterjesztése
  3. Template Method pattern — absztrakt osztály definiálja a vázat
  4. Sealed hierarchiák (Java 17+) — zárt domain modell

Mikor jobb a kompozíció?

  1. Viselkedés futásidőben kell változzon (Strategy pattern)
  2. Több különböző viselkedés kombinálása
  3. Az „is-a" kapcsolat nem természetes
  4. 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. Mélymerülés

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 switch expression-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. Interjúkérdések

Mi a legfontosabb különbség az öröklődés és a kompozíció között?

Az öröklődés is-a kapcsolatot modellez közös ősviselkedéssel, a kompozíció pedig has-a kapcsolatot úgy, hogy az együttműködő objektum mezőként jelenik meg.

Miért enged a Java több interfészt, de nem több ősosztályt?

Mert a több konkrét ősosztály könnyen kétértelmű viselkedést és állapotot okozna, míg az interfészek biztonságosabban fejeznek ki több szerződést.

Mi az a fragile base class probléma?

Az, amikor a szülőosztály egy változtatása váratlanul eltöri a leszármazottakat, mert azok implementációs részletekre támaszkodtak.

Mikor hasznosak az interface `default` metódusok?

API-fejlesztésnél és kisebb közös viselkedések megosztásánál hasznosak, de nem szabad velük rejtett absztrakt osztályt építeni.


9. 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

10. 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
  • @Override mindig legyen ott — compile-time védelem.
  • super() hívás konstruktorban csak első utasítás lehet.
  • final class é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.

🎮 Játékok

10 kérdés