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

Osztályok és objektumok

Konstruktorok, inicializációs blokkok, immutabilitás és recordok

Osztályok és objektumok

Az objektum-orientált programozás két alapköve: az osztály (class) mint tervrajz, és az objektum (object) mint annak élő példánya.

1. Definíció

Mi ez?

A class egy sablon (blueprint), amely meghatározza, milyen adatokat (fields) és viselkedést (methods) tartalmaz egy adott típusú objektum. Az object ennek a sablonnak egy konkrét, memóriában létező példánya (instance).

Az osztály és az objektum röviden:

  • Class — tervrajz: mezőket, metódusokat és konstruktorokat definiál.
  • Object — konkrét példány: valós értékeket tárol, például name = "Alice", age = 30, és a heapen él.

Miért létezik?

  • Absztrakció: a valós világ fogalmait modellezi kódban
  • Kapszuláció (encapsulation): az adatok és a hozzájuk tartozó műveletek egységbe zárása
  • Újrafelhasználhatóság: egyszer megírt osztály → végtelen példány belőle
  • Típusbiztonság: a compiler ellenőrzi, hogy az objektum valóban az, aminek deklaráltuk

Hol helyezkedik el?

Az OOP paradigma alapja — minden más koncepció (inheritance, polymorphism, design patterns) az osztályokra és objektumokra épül. Java-ban szinte minden osztály (kivéve a primitívek).


2. Alapfogalmak

2.1 Class anatomy — az osztály felépítése

Egy Java osztály a következő elemekből állhat:

public class Employee {
    // 1. Fields (mezők / állapot)
    private String name;
    private int age;
    private static int employeeCount = 0;  // osztályszintű

    // 2. Static initializer block
    static {
        System.out.println("Class loaded");
    }

    // 3. Instance initializer block
    {
        employeeCount++;
    }

    // 4. Constructor (konstruktor)
    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 5. Methods (metódusok)
    public String getName() { return name; }

    // 6. Static method
    public static int getEmployeeCount() { return employeeCount; }

    // 7. Inner class
    public class Badge {
        private String badgeId;
    }

    // 8. Enum
    public enum Department { ENGINEERING, HR, FINANCE }
}

2.2 Access modifiers — hozzáférés-szabályozók

Modifier Osztályon belül Package-en belül Leszármazott Mindenhol
private
(default)
protected
public

Ökölszabály: a mezők (fields) legyenek private, a getter/setter metódusok legyenek public — ez az encapsulation alapja.

2.3 Object creation — objektum létrehozása

// 1. new kulcsszó + konstruktor (leggyakoribb)
Employee emp = new Employee("Alice", 30);

// 2. Reflection
Employee emp2 = Employee.class.getDeclaredConstructor(String.class, int.class)
                               .newInstance("Bob", 25);

// 3. Clone (ha implementálja a Cloneable-t)
Employee emp3 = (Employee) emp.clone();

// 4. Deserialization
// ObjectInputStream → readObject()

// 5. Factory method (recommended pattern)
Employee emp4 = Employee.of("Charlie", 28);

Objektum életciklusa:

  1. Allokációnew → memória a heap-en
  2. Inicializáció — instance initializer block → constructor
  3. Használat — referencia eléri
  4. Elérhetetlen — nincs rá referencia
  5. Garbage Collection — a GC felszabadítja a memóriát

2.4 A `this` keyword

A this az aktuális objektumra mutat:

public class Account {
    private double balance;

    public Account(double balance) {
        this.balance = balance;          // field vs parameter disambiguáció
    }

    public Account deposit(double amount) {
        this.balance += amount;
        return this;                     // fluent API / method chaining
    }

    public Account() {
        this(0.0);                       // constructor chaining
    }
}

// Használat: fluent API
Account acc = new Account(100).deposit(50).deposit(25);

2.5 Static vs instance

Jellemző static Instance
Kinek tartozik? Az osztálynak Az objektumnak
Memória 1 db a Method Area-ban Objektumonként 1 db a heap-en
Hozzáférés ClassName.method() object.method()
this elérhető?
Tipikus use-case Utility metódusok, counter-ek, factory-k Állapotfüggő viselkedés
// Static: utility method — nincs állapot, nincs side-effect
public static double celsiusToFahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32;
}

// Instance: az objektum állapotától függ
public double getBalance() {
    return this.balance;
}

2.6 Records (Java 16+)

A record egy immutable data carrier — a Java válasza a boilerplate problémára:

// Hagyományos class: ~40 sor (fields, constructor, getters, equals, hashCode, toString)
// Record: 1 sor
public record Point(int x, int y) { }

A compiler automatikusan generálja:

  • private final mezőket
  • All-args konstruktort (canonical constructor)
  • Getter metódusokat (x(), y() — nem getX())
  • equals(), hashCode(), toString()
// Custom validation compact constructor-ral
public record Email(String value) {
    public Email {                          // compact constructor — nincs paraméter lista
        if (!value.contains("@")) {
            throw new IllegalArgumentException("Invalid email: " + value);
        }
        value = value.toLowerCase();        // normalizáció
    }
}

Korlátozások:

  • Nem lehet extend-elni (implicit final)
  • Nem lehet mutable fields (implicit final)
  • Nem lehet abstract
  • Lehet interface-t implementálni

2.7 Sealed classes (Java 17+)

A sealed class korlátozza, mely osztályok örökölhetnek belőle:

public sealed class Shape permits Circle, Rectangle, Triangle {
    // közös logika
}

public final class Circle extends Shape {       // final: nem örökíthető tovább
    private final double radius;
    public Circle(double radius) { this.radius = radius; }
}

public non-sealed class Rectangle extends Shape { // non-sealed: bárki örökölhet innen
    protected double width, height;
}

public sealed class Triangle extends Shape permits EquilateralTriangle {
    // sealed: tovább korlátozhatja
}

Miért hasznos?

  • Pattern matching-gel (switch) exhaustive check lehetséges
  • Zárt típushierarchia → biztonságosabb domain modeling
  • Algebraic data types (ADT) Java-ban

2.8 Inner és nested classes

Típus Deklaráció static? Hozzáfér outer state-hez?
Static nested class static class Inner {} Igen Csak static tagokhoz
Inner class class Inner {} Nem Igen, mindenhez
Local class Metóduson belül Nem Igen + effectively final lokális változók
Anonymous class new Interface() { } Nem Igen + effectively final lokális változók
// Static nested class — gyakori pattern (Builder)
public class Pizza {
    private final String dough;
    private final String topping;

    public static class Builder {
        private String dough;
        private String topping;

        public Builder dough(String d) { this.dough = d; return this; }
        public Builder topping(String t) { this.topping = t; return this; }
        public Pizza build() { return new Pizza(this); }
    }

    private Pizza(Builder b) {
        this.dough = b.dough;
        this.topping = b.topping;
    }
}

2.9 Enums

Az enum egy speciális class, amelynek fix számú, előre definiált instance-a van:

public enum Status {
    ACTIVE("Aktív"),
    INACTIVE("Inaktív"),
    SUSPENDED("Felfüggesztett");

    private final String displayName;

    Status(String displayName) {         // konstruktor implicit private
        this.displayName = displayName;
    }

    public String getDisplayName() { return displayName; }
}

// Használat
Status s = Status.ACTIVE;
String name = s.getDisplayName();  // "Aktív"

Az enum implicit final, extend-álja a java.lang.Enum osztályt, és lehet abstract method-ja (ha minden constant implementálja).


3. Gyakorlati használat

Mikor melyiket válasszam?

Szituáció Választás Miért?
Állapotot tartó, módosítható entitás (pl. User, Order) Class Mutable state szükséges
Egyszerű adat hordozó, immutable (pl. DTO, Event) Record Boilerplate eliminálás, immutabilitás
Fix, zárt értékhalmaz (pl. Status, Direction) Enum Type-safe, singleton-szerű
Zárt típushierarchia, pattern matching Sealed class Exhaustive switch, domain safety
Utility metódusok, állapot nélkül Static methods (utility class) Nincs instance szükséges

Döntési fa

Gyors döntési segédlet:

  1. Ha kell mutable állapot, válassz class-t.
  2. Ha nem, de véges és előre ismert értékhalmazod van, válassz enum-ot.
  3. Ha egy egyszerű immutable adathordozó kell, válassz record-ot.
  4. Ha zárt hierarchiára van szükség, válassz sealed class-t.
  5. Ha egyik sem illik, marad a hagyományos class vagy interface.

4. Kód példák

4.1 Basic — egyszerű class konstruktorral

public class BankAccount {
    private final String iban;
    private double balance;

    public BankAccount(String iban, double initialBalance) {
        if (iban == null || iban.isBlank()) {
            throw new IllegalArgumentException("IBAN cannot be blank");
        }
        this.iban = iban;
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        this.balance += amount;
    }

    public void withdraw(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        if (amount > balance) throw new IllegalStateException("Insufficient funds");
        this.balance -= amount;
    }

    // Getters — no setter for iban (immutable field)
    public String getIban() { return iban; }
    public double getBalance() { return balance; }
}

4.2 Advanced — Record, sealed class, Builder pattern

// 1. Record mint Value Object
public record Money(BigDecimal amount, Currency currency) {
    public Money {
        Objects.requireNonNull(amount, "amount must not be null");
        Objects.requireNonNull(currency, "currency must not be null");
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
    }

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

// 2. Sealed class hierarchia
public sealed interface PaymentMethod permits CreditCard, BankTransfer, Wallet {
    Money maxLimit();
}

public record CreditCard(String number, Money maxLimit) implements PaymentMethod { }
public record BankTransfer(String iban, Money maxLimit) implements PaymentMethod { }
public record Wallet(String walletId, Money maxLimit) implements PaymentMethod { }

// 3. Pattern matching switch (Java 21+)
public String describe(PaymentMethod pm) {
    return switch (pm) {
        case CreditCard cc   -> "Card ending in " + cc.number().substring(cc.number().length() - 4);
        case BankTransfer bt -> "Bank transfer to " + bt.iban();
        case Wallet w        -> "Wallet " + w.walletId();
    };
}

// 4. Builder pattern (static nested class)
public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Map.copyOf(builder.headers);  // defensive copy
        this.body = builder.body;
    }

    public static class Builder {
        private final String url;                              // required
        private String method = "GET";                         // optional, default
        private final Map<String, String> headers = new HashMap<>();
        private String body;

        public Builder(String url) { this.url = url; }
        public Builder method(String m) { this.method = m; return this; }
        public Builder header(String k, String v) { headers.put(k, v); return this; }
        public Builder body(String b) { this.body = b; return this; }
        public HttpRequest build() { return new HttpRequest(this); }
    }
}

// Használat
HttpRequest req = new HttpRequest.Builder("https://api.example.com")
    .method("POST")
    .header("Content-Type", "application/json")
    .body("{\"key\":\"value\"}")
    .build();

5. Trade-offok

Szempont Opció A Opció B Mikor melyik?
Mutability Mutable class Immutable class / Record Immutable, ha nincs szükség állapotváltozásra (thread-safe, egyszerűbb reasoning)
Class vs Record Hagyományos class Record Record ha pure data carrier, class ha viselkedés-gazdag entitás
Inheritance vs Composition extends Field injection / delegation Favor composition — az öröklés szoros csatolást hoz
Inner class vs top-level Inner class Külön fájl Inner ha csak az outer class használja (pl. Builder); top-level ha önállóan is értelmes
Enum vs sealed class Enum Sealed class Enum ha fix instance-ok, sealed ha típusonként eltérő struktúra kell

6. Gyakori hibák

❌ 1. `equals()` és `hashCode()` hiánya / inkonzisztenciája

// ROSSZ: csak equals() override, hashCode() nem
public class UserId {
    private final String value;
    @Override
    public boolean equals(Object o) { /* ... */ }
    // hashCode() hiányzik → HashMap-ben elveszhet!
}

// JÓ: mindkettőt felülírjuk, konzisztensen
@Override
public int hashCode() {
    return Objects.hash(value);
}

❌ 2. Mutable field record-ban

// ROSSZ: List átadva referenciával — kívülről módosítható!
public record Team(String name, List<String> members) { }

Team t = new Team("Dev", new ArrayList<>(List.of("Alice")));
t.members().add("Hacker");  // 😱 módosítható!

// JÓ: defensive copy a compact constructor-ban
public record Team(String name, List<String> members) {
    public Team {
        members = List.copyOf(members);  // unmodifiable copy
    }
}

❌ 3. `static` mezők helytelen használata shared state-ként

// ROSSZ: mutable static field → thread-safety probléma
public class Counter {
    public static int count = 0;  // data race!
}

// JÓ: AtomicInteger vagy proper synchronization
public class Counter {
    private static final AtomicInteger count = new AtomicInteger(0);
}

❌ 4. Belső állapot kiszolgáltatása (exposing internal state)

// ROSSZ: mutable list visszaadása
public List<String> getItems() { return items; }

// JÓ: unmodifiable view vagy defensive copy
public List<String> getItems() { return Collections.unmodifiableList(items); }

7. Senior szintű meglátások

7.1 Object identity vs equality

String a = new String("hello");
String b = new String("hello");

a == b       // false — különböző referenciák (identity)
a.equals(b)  // true  — ugyanaz az érték (equality)
  • == : referencia-összehasonlítás (ugyanaz az objektum a heap-en?)
  • .equals() : érték-összehasonlítás (az objektum tartalmát hasonlítjuk)

Interjútipp: minden class-nak, amit HashMap key-ként vagy HashSet elemként használunk, kötelező konzisztens equals() + hashCode() implementáció.

7.2 Defensive copying

Immutable objektumok belső mutable referenciáit be- és kimenetnél is másolni kell:

public class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        // Defensive copy a bemenetről
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
        if (this.start.after(this.end)) {
            throw new IllegalArgumentException("start after end");
        }
    }

    public Date getStart() {
        return new Date(start.getTime());  // defensive copy a kimeneten
    }
}

Best practice: használj immutable típusokat (LocalDate a Date helyett), és nem kell defensive copy.

7.3 Value Objects (DDD)

A Domain-Driven Design (DDD) megkülönbözteti:

Fogalom Identity Mutable? Példa
Entity Van (id) Igen User, Order
Value Object Nincs — az értéke azonosítja Nem Money, Address, Email
Aggregate Root entity + child entities Vegyes Order + OrderLine-ok
// Value Object record-dal — természetes illeszkedés
public record Address(String city, String street, String zip) { }

// Két Address egyenlő, ha a tartalma megegyezik — nem kell külön id
Address a1 = new Address("Budapest", "Fő utca 1", "1011");
Address a2 = new Address("Budapest", "Fő utca 1", "1011");
a1.equals(a2); // true — record automatikusan generálja

7.4 `Class` objektum és reflection

Minden Java osztályhoz tartozik egy java.lang.Class<?> objektum:

Class<?> clazz = Employee.class;                    // compile-time
Class<?> clazz2 = emp.getClass();                   // runtime
Class<?> clazz3 = Class.forName("com.example.Employee"); // dinamikus

// Reflection: mezők, metódusok, annotációk lekérdezése
Field[] fields = clazz.getDeclaredFields();

Figyelem: a reflection lassú és feltöri az encapsulation-t — production kódban ritkán közvetlenül használjuk (framework-ök viszont intenzíven).


8. Szószedet

Kifejezés Jelentés
Class Sablon (blueprint), amely definiálja az adatstruktúrát és viselkedést
Object Egy class konkrét, memóriában élő példánya (instance)
Instance = Object; az osztály egy megvalósítása
Constructor Speciális metódus, ami az objektum inicializálásáért felelős
Field Az osztály adattagja (member variable)
Method Az osztály viselkedését definiáló függvény
Access modifier Hozzáférés-szabályozó: private, default, protected, public
this Az aktuális objektumra mutató referencia
static Osztályszintű tag — nem az objektumhoz, hanem az osztályhoz tartozik
Record Immutable data carrier class, automatic equals/hashCode/toString (Java 16+)
Sealed class Zárt öröklési hierarchiát definiáló class (Java 17+)
Inner class Más osztályon belül definiált, nem-static osztály
Enum Fix instance-halmazú speciális class
Value Object DDD fogalom — identitás nélküli, értéke alapján összehasonlított objektum

9. Gyorsreferencia

  • ✅ Az osztály (class) sablon, az objektum annak egy konkrét instance-a a heap-en
  • ✅ Használj private mezőket + public getter/setter-t (encapsulation)
  • ✅ Mindig írj felül equals() ÉS hashCode() metódust, ha az objektumot Collection-ben használod
  • ✅ Használj record-ot egyszerű, immutable adathordozóknak (Java 16+)
  • ✅ Használj sealed class-t zárt típushierarchiák modellezésére (Java 17+)
  • Enum: fix, véges értékkészlet → type-safe, singleton-szerű
  • this — aktuális objektum referencia; használd fluent API és constructor chaining-hez
  • static — osztályszintű tag; utility metódusokhoz, factory-khoz, constant-okhoz
  • ✅ Részesítsd előnyben a composition-t az inheritance-szel szemben
  • ✅ Defensive copy: mutable referenciákat másold be- és kimenetnél
  • ✅ Value Objects (DDD): record = természetes választás identity nélküli fogalmakhoz
  • Builder pattern: használj static nested class-t, ha sok opcionális paraméter van
  • ⚠️ == referenciát hasonlít, .equals() értéket — ne keverd össze!
  • ⚠️ Record-ban mutable collection-t mindig List.copyOf() / Map.copyOf() -val védd

🎮 Játékok

8 kérdés