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

Polimorfizmus

Method overloading, method overriding, dynamic dispatch, covariant return types, vtable/itable és JIT optimalizáció

Polimorfizmus

A polimorfizmus (polymorphism) az OOP egyik legfontosabb alapelve: lehetővé teszi, hogy egyetlen interfészen vagy referencia-típuson keresztül különböző viselkedéseket érjünk el. Két fő formája a compile-time (statikus) és a runtime (dinamikus) polimorfizmus.

1. Definíció

Mi ez?

  • Polimorfizmus (görög: „sokféle alak") — egy objektum több típusként is viselkedhet, és ugyanaz a metódushívás a futásidejű típustól függően más-más implementációt hajthat végre.
  • Compile-time polymorphism (statikus): A fordító dönti el, melyik metódust hívja — method overloading.
  • Runtime polymorphism (dinamikus): Futásidőben dől el, melyik implementáció fut le — method overriding + dynamic dispatch.

Miért fontos?

  • Open/Closed Principle: Új viselkedés hozzáadása a meglévő kód módosítása nélkül.
  • Karbantarthatóság: A hívó kód nem függ a konkrét implementációtól.
  • Tesztelhetőség: Mock objektumok könnyedén helyettesíthetők a valódi implementáció helyett.

Hol helyezkedik el?

A polimorfizmus az OOP négy alapelvéből (encapsulation, inheritance, polymorphism, abstraction) a leggyakorlatibb — szinte minden design pattern és framework erre épül.


2. Alapfogalmak

2.1 Method Overloading (compile-time / statikus polimorfizmus)

Ugyanazon osztályon belül több metódus létezhet azonos névvel, de eltérő paraméterlistával (típus, szám, sorrend):

public class Calculator {
    public int add(int a, int b)           { return a + b; }
    public double add(double a, double b)  { return a + b; }
    public int add(int a, int b, int c)    { return a + b + c; }
}

A fordító a metódus szignáturája alapján dönti el fordítási időben, melyik változatot hívja. A return type önmagában nem elegendő a megkülönböztetéshez.

2.2 Method Overriding (runtime / dinamikus polimorfizmus)

Az alosztály felülírja az ősosztály metódusát, és futásidőben az objektum tényleges típusa határozza meg, melyik implementáció fut:

public class Animal {
    public String speak() { return "..."; }
}

public class Dog extends Animal {
    @Override
    public String speak() { return "Woof!"; }
}

public class Cat extends Animal {
    @Override
    public String speak() { return "Meow!"; }
}

2.3 Dynamic Dispatch

A JVM futásidőben a referencia tényleges típusa alapján keresi meg a megfelelő metódust:

Animal animal = new Dog();  // compile-time type: Animal, runtime type: Dog
animal.speak();             // → "Woof!" (dynamic dispatch)

A fordító ellenőrzi, hogy a metódus létezik-e a compile-time type-ban (Animal), de a runtime type (Dog) implementációja fut le.

2.4 Covariant Return Types

Java 5 óta az overriding metódus return type-ja lehet az eredeti return type alosztálya:

public class AnimalFactory {
    public Animal create() { return new Animal(); }
}

public class DogFactory extends AnimalFactory {
    @Override
    public Dog create() { return new Dog(); }  // Dog az Animal alosztálya — OK
}

2.5 Polimorf referenciák

Egy szülő típusú referencia bármely alosztály objektumra mutathat:

List<Animal> animals = List.of(new Dog(), new Cat());
for (Animal a : animals) {
    System.out.println(a.speak());  // mindegyik a saját implementációját futtatja
}

2.6 Varargs Overloading

A varargs (...) overloading részeként speciálisan kezelt — a compiler a legspecifikusabb metódust preferálja:

public void print(int a, int b)   { /* specifikus */ }
public void print(int... numbers) { /* varargs  */ }

print(1, 2);  // → az első hívódik, mert specifikusabb

2.7 Type Erasure hatása az overloadingra

Generikus típusok a compilation során törlődnek (type erasure), ezért nem lehetséges:

// ❌ COMPILE ERROR — a két metódus törlés után azonos szignáturájú: process(List)
public void process(List<String> list) { }
public void process(List<Integer> list) { }

3. Gyakorlati alkalmazás

3.1 Strategy Pattern

A polimorfizmus a Strategy pattern alapja — a viselkedés futásidőben cserélhető:

public interface SortStrategy {
    <T extends Comparable<T>> void sort(List<T> list);
}

public class QuickSort implements SortStrategy {
    @Override
    public <T extends Comparable<T>> void sort(List<T> list) { /* quicksort impl */ }
}

public class MergeSort implements SortStrategy {
    @Override
    public <T extends Comparable<T>> void sort(List<T> list) { /* mergesort impl */ }
}

// Használat
SortStrategy strategy = useLargeDataset ? new MergeSort() : new QuickSort();
strategy.sort(data);

3.2 Plugin architektúrák

Interfészeken keresztül definiált bővítési pontok, ahol a plugin-ok futásidőben töltődnek be:

public interface PaymentProcessor {
    boolean process(Payment payment);
    String getProviderName();
}

// ServiceLoader vagy Spring IoC automatikusan betölti az implementációkat
ServiceLoader<PaymentProcessor> processors = ServiceLoader.load(PaymentProcessor.class);

3.3 API Design

Metódus paramétereknél és return type-oknál mindig a legáltalánosabb típust használjuk (program to an interface):

// ✅ JÓ — polimorf, bármely List implementáció elfogadott
public List<String> filterNames(List<String> input) { ... }

// ❌ ROSSZ — konkrét implementációhoz kötött
public ArrayList<String> filterNames(ArrayList<String> input) { ... }

4. Kódpéldák

4.1 Alapszintű — Overloading + Overriding

public class Shape {
    // Overloading (compile-time polymorphism)
    public double area(double radius) {
        return Math.PI * radius * radius;
    }

    public double area(double width, double height) {
        return width * height;
    }

    // Overriding-ra szánt metódus
    public String describe() {
        return "I am a generic shape";
    }
}

public class Circle extends Shape {
    private final double radius;

    public Circle(double radius) { this.radius = radius; }

    @Override
    public String describe() {
        return "I am a circle with radius " + radius;
    }
}

public class Rectangle extends Shape {
    private final double w, h;

    public Rectangle(double w, double h) { this.w = w; this.h = h; }

    @Override
    public String describe() {
        return "I am a " + w + "×" + h + " rectangle";
    }
}

4.2 Haladó — Dynamic Dispatch és Covariant Returns

public abstract class Document {
    public abstract Document clone();
    public abstract String render();
}

public class PdfDocument extends Document {
    @Override
    public PdfDocument clone() {               // covariant return type
        return new PdfDocument(/* copy state */);
    }

    @Override
    public String render() {
        return "Rendering PDF...";
    }
}

public class HtmlDocument extends Document {
    @Override
    public HtmlDocument clone() {              // covariant return type
        return new HtmlDocument(/* copy state */);
    }

    @Override
    public String render() {
        return "Rendering HTML...";
    }
}

// Dynamic dispatch in action
List<Document> docs = List.of(new PdfDocument(), new HtmlDocument());
for (Document doc : docs) {
    System.out.println(doc.render());  // runtime type határozza meg
    Document copy = doc.clone();       // covariant típus visszaadva
}

4.3 Haladó — Polimorfizmus interfészekkel

public interface Drawable {
    void draw(Graphics2D g);
}

public interface Resizable {
    void resize(double factor);
}

public class CanvasShape implements Drawable, Resizable {
    private double scale = 1.0;

    @Override
    public void draw(Graphics2D g) {
        g.scale(scale, scale);
        // rajzolás...
    }

    @Override
    public void resize(double factor) {
        this.scale *= factor;
    }
}

// Bármelyik interfész-referencia használható:
Drawable d = new CanvasShape();
d.draw(graphics);

Resizable r = (Resizable) d;
r.resize(2.0);

5. Trade-off-ok

Szempont Statikus polimorfizmus (overloading) Dinamikus polimorfizmus (overriding)
Mikor dől el Compile-time Runtime
Teljesítmény Gyorsabb — nincs runtime lookup Kis overhead — vtable lookup
Rugalmasság Korlátozott — fordításkor kötött Nagy — futásidőben cserélhető
Olvashatóság Sok overloaded metódus zavaró lehet Tisztább, ha jó az absztrakció
Tesztelhetőség Egyszerű — determinisztikus Mockokkal kiválóan tesztelhető
Karbantartás Overload-ok szaporodása problémás Interface-ek stabilak maradhatnak
Használat Kényelmi metódusok, builder-ek Strategy, plugin, framework kód

Mikor melyiket?

  • Overloading: Azonos logika, különböző input-típusok (pl. valueOf(int), valueOf(String))
  • Overriding: Különböző viselkedés, közös kontraktus (pl. Collection.size() minden implementációban más)

6. Gyakori hibák

6.1 ❌ Autoboxing/widening zavar overloading esetén

public void process(long value)    { System.out.println("long"); }
public void process(Integer value) { System.out.println("Integer"); }

process(5);  // → "long" — widening (int→long) előnyt élvez az autoboxinggal (int→Integer) szemben!

A feloldási sorrend: exact match → widening → autoboxing → varargs.

6.2 ❌ Static method hiding vs overriding

public class Parent {
    public static void greet() { System.out.println("Parent"); }
}

public class Child extends Parent {
    public static void greet() { System.out.println("Child"); }  // HIDING, nem overriding!
}

Parent ref = new Child();
ref.greet();  // → "Parent" — static metódusoknál a COMPILE-TIME type dönt!

Statikus metódusok NEM polimorfak. Nincs dynamic dispatch — a referencia deklarált típusa alapján dől el, melyik hívódik.

6.3 ❌ Hiányzó `@Override` annotáció

public class Dog extends Animal {
    // Elgépelés! Ez nem overriding, hanem egy ÚJ metódus
    public String speek() { return "Woof!"; }

    // ✅ @Override használatával a fordító hibát jelezne:
    // @Override
    // public String speek() { }  // ❌ COMPILE ERROR
}

6.4 ❌ Overloading az overriding helyett

public class Parent {
    public void execute(Object obj) { /* ... */ }
}

public class Child extends Parent {
    // ❌ Ez overloading, NEM overriding! Más a paraméter típus.
    public void execute(String str) { /* ... */ }
}

Parent ref = new Child();
ref.execute("hello");  // → Parent.execute(Object) hívódik, nem a Child verziója!

7. Senior szintű tudás

7.1 vtable és itable a JVM-ben

A JVM a virtual method table (vtable) segítségével oldja meg a dynamic dispatch-et:

Fogalom Leírás
vtable Minden osztályhoz egy tábla — az index a metódus, az érték a konkrét implementáció pointer-je. Öröklődésnél az alosztály táblája az ősosztály tábláját bővíti.
itable Interface method table — interfész metódusok dispatch-jéhez. Lassabb, mint a vtable, mert a JVM-nek az interfész-hierarchiában is keresnie kell.
invokevirtual Bytecode utasítás — vtable-en keresztüli dispatch (osztály metódusok).
invokeinterface Bytecode utasítás — itable-en keresztüli dispatch (interfész metódusok).

7.2 Monomorphic, Bimorphic és Megamorphic call site-ok

A JIT compiler optimalizálja a polimorf hívásokat:

Call site típus Leírás JIT optimalizáció
Monomorphic Mindig ugyanaz az 1 típus Inline cache → teljes inlining
Bimorphic 2 különböző típus Conditional inlining
Megamorphic 3+ különböző típus Nincs inlining → vtable lookup minden hívásnál
// Monomorphic — optimális
Animal a = new Dog();
a.speak();  // JIT inline-olja a Dog.speak()-et

// Megamorphic — lassabb
for (Animal a : mixedAnimals) {  // Dog, Cat, Bird, Fish...
    a.speak();  // nincs inlining, vtable lookup minden alkalommal
}

7.3 `invokedynamic` és Method Handles

A Java 7-ben bevezetett invokedynamic utasítás és a MethodHandle API rugalmasabb dispatch-et tesz lehetővé:

  • Lambda expressions (Java 8+) invokedynamic-ot használnak — nem anonymous inner class-t generálnak.
  • MethodHandle: Alacsony szintű, JVM-optimalizált metódus-referencia, amely bypass-olja a hagyományos vtable dispatch-et.
  • Performance: A MethodHandle.invoke() a JIT compiler számára átlátszó, optimalizálható.
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findVirtual(Animal.class, "speak",
    MethodType.methodType(String.class));

String result = (String) handle.invoke(new Dog());  // → "Woof!"

7.4 Sealed Classes és pattern matching (Java 17+)

A sealed class-ok a polimorfizmus egy kontrollált formáját adják — a compiler ellenőrzi, hogy minden eset le van kezelve:

public sealed interface Shape permits Circle, Rectangle, Triangle {}

// Java 21+ pattern matching switch
String describe(Shape s) {
    return switch (s) {
        case Circle c    -> "Circle r=" + c.radius();
        case Rectangle r -> "Rect " + r.w() + "×" + r.h();
        case Triangle t  -> "Triangle";
        // nincs default szükséges — a compiler tudja, hogy kimerítő
    };
}

8. Szójegyzék

Fogalom Definíció
Polymorphism Egy objektum több típusként való viselkedésének képessége
Method Overloading Azonos nevű metódusok eltérő paraméterlistával egy osztályon belül
Method Overriding Alosztályban az ősosztály metódusának felülírása azonos szignáturával
Dynamic Dispatch Futásidőben a tényleges típus alapján történő metóduskiválasztás
Covariant Return Type Az overriding metódus return type-ja az eredeti alosztálya lehet
vtable Virtual method table — a JVM dispatch mechanizmusa osztály metódusokhoz
itable Interface method table — a JVM dispatch mechanizmusa interfész metódusokhoz
Megamorphic 3 vagy több különböző típust fogadó call site, amit a JIT nem tud inlining-olni
invokedynamic JVM bytecode utasítás rugalmas, bootstrap-alapú metódushíváshoz
Method Handle Alacsony szintű, JVM-optimalizált metódus-referencia
Type Erasure Generikus típusinformáció törlése compile-time után
Static Binding Fordítási időben eldőlő metódushívás (overloading, static, private, final metódusok)

9. Gyorsreferencia

  • Statikus polimorfizmus — method overloading; a fordító a szignatúra alapján dönt.
  • Dinamikus polimorfizmus — method overriding; a JVM futásidőben, a runtime type alapján dönt.
  • Overload feloldási sorrend — exact match → widening → autoboxing → varargs.
  • Overriding szabályok — azonos szignatúra, ugyanaz vagy covariant return type, ugyanaz vagy tágabb access modifier, ugyanaz vagy szűkebb checked exception.
  • Nem overriding-olható a static, final és private metódus; érdemes mindig @Override-ot használni.
  • JVM dispatchinvokevirtual az osztálymetódusokra, invokeinterface az interfészmetódusokra, invokestatic a statikus hívásokra, invokedynamic például lambdákhoz.
  • JIT optimalizáció — a monomorphic call site optimalizálható a legjobban, a bimorphic még kezelhető, a megamorphic a legnehezebb.

🎮 Játékok

8 kérdés