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

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

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

Ö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 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. 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 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. 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
  • @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.