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ésprivatemetódus; érdemes mindig@Override-ot használni. - JVM dispatch —
invokevirtualaz osztálymetódusokra,invokeinterfaceaz interfészmetódusokra,invokestatica statikus hívásokra,invokedynamicpé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