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 legyenekpublic— 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:
- Allokáció —
new→ memória a heap-en - Inicializáció — instance initializer block → constructor
- Használat — referencia eléri
- Elérhetetlen — nincs rá referencia
- 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 finalmezőket- All-args konstruktort (canonical constructor)
- Getter metódusokat (
x(),y()— nemgetX()) 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 ajava.lang.Enumosztá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:
- Ha kell mutable állapot, válassz class-t.
- Ha nem, de véges és előre ismert értékhalmazod van, válassz enum-ot.
- Ha egy egyszerű immutable adathordozó kell, válassz record-ot.
- Ha zárt hierarchiára van szükség, válassz sealed class-t.
- 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
HashMapkey-ként vagyHashSetelemként használunk, kötelező konzisztensequals()+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 (
LocalDateaDatehelyett), é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
privatemezőket +publicgetter/setter-t (encapsulation) - ✅ Mindig írj felül
equals()ÉShashCode()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