Modern Java Rehberi: Streams, Records, Pattern Matching & Virtual Threads

20 Haziran 2026 · netologist · 11 dakika, 2147 kelime ·

Java Streams API’sini tüm kullanım senaryolarıyla ve Java 14–21+ ile gelen modern özelliklerle (Records, Pattern Matching, Virtual Threads, Sealed Classes) nasıl birleştirileceğini gösteren kapsamlı bir referans.


İçindekiler

  1. Java Streams - Temeller
  2. Stream Oluşturma
  3. Ara (Intermediate) Operasyonlar
  4. Terminal Operasyonlar
  5. Collectors - Detaylı İnceleme
  6. Primitive Streamler
  7. Parallel Streamler
  8. Records
  9. Pattern Matching
  10. Sealed Classes + Kapsamlı (Exhaustive) Pattern Matching
  11. Virtual Threads
  12. Structured Concurrency
  13. Hepsini Birleştirmek - Gerçek Dünya Pattern’leri

1. Java Streams - Temeller

Stream, fonksiyonel tarzda işlemleri destekleyen bir eleman dizisini temsil eder. Streamlerin özellikleri:

List<String> names = List.of("Ali", "Veli", "Ayşe", "Mert");
long count = names.stream()
                   .filter(n -> n.length() > 3)
                   .count();

2. Stream Oluşturma

// Collection'dan
List<Integer> list = List.of(1, 2, 3);
Stream<Integer> s1 = list.stream();

// Değerlerden
Stream<String> s2 = Stream.of("a", "b", "c");

// Boş stream
Stream<String> s3 = Stream.empty();

// Sonsuz stream (limit ile sınırlandırılmalı)
Stream<Integer> s4 = Stream.iterate(0, n -> n + 2).limit(5); // 0,2,4,6,8

// Java 9+: predicate ile iterate (sınırlı)
Stream<Integer> s5 = Stream.iterate(0, n -> n < 10, n -> n + 2);

// Generate (supplier tabanlı, sonsuz)
Stream<Double> s6 = Stream.generate(Math::random).limit(3);

// Array'den
Integer[] arr = {1, 2, 3};
Stream<Integer> s7 = Arrays.stream(arr);

// Dosyadan (I/O destekli stream, kapatılmalı)
try (Stream<String> lines = Files.lines(Path.of("data.txt"))) {
    lines.forEach(System.out::println);
}

// Builder
Stream<String> s8 = Stream.<String>builder().add("x").add("y").build();

3. Ara (Intermediate) Operasyonlar

Ara operasyonlar yeni bir stream döndürür ve tembel (lazy) olarak değerlendirilir.

List<String> words = List.of("apple", "banana", "kiwi", "fig", "apple");

// filter
words.stream().filter(w -> w.length() > 3).forEach(System.out::println);

// map
words.stream().map(String::toUpperCase).forEach(System.out::println);

// flatMap - iç içe yapıları düzleştirme
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
                            .flatMap(List::stream)
                            .toList();

// distinct
words.stream().distinct().forEach(System.out::println);

// sorted (doğal veya özel comparator ile)
words.stream().sorted().forEach(System.out::println);
words.stream().sorted(Comparator.comparingInt(String::length).reversed())
              .forEach(System.out::println);

// limit / skip
words.stream().skip(1).limit(2).forEach(System.out::println);

// peek - genellikle debug amaçlı yan etkiler için
words.stream()
     .peek(w -> System.out.println("İşleniyor: " + w))
     .map(String::toUpperCase)
     .toList();

// takeWhile / dropWhile (Java 9+)
List<Integer> nums = List.of(1, 2, 3, 10, 4, 5);
nums.stream().takeWhile(n -> n < 5).forEach(System.out::println); // 1,2,3
nums.stream().dropWhile(n -> n < 5).forEach(System.out::println); // 10,4,5

4. Terminal Operasyonlar

Terminal operasyonlar asıl işlemeyi tetikler ve bir sonuç veya yan etki üretir.

List<Integer> nums = List.of(1, 2, 3, 4, 5);

// forEach
nums.stream().forEach(System.out::println);

// collect (bkz. bölüm 5)
List<Integer> evens = nums.stream().filter(n -> n % 2 == 0).toList(); // Java 16+ kısayolu

// reduce
Optional<Integer> sum = nums.stream().reduce(Integer::sum);
int sumWithIdentity = nums.stream().reduce(0, Integer::sum);
int product = nums.stream().reduce(1, (a, b) -> a * b);

// count
long count = nums.stream().filter(n -> n > 2).count();

// anyMatch / allMatch / noneMatch
boolean anyEven = nums.stream().anyMatch(n -> n % 2 == 0);
boolean allPositive = nums.stream().allMatch(n -> n > 0);
boolean noneNegative = nums.stream().noneMatch(n -> n < 0);

// findFirst / findAny
Optional<Integer> first = nums.stream().filter(n -> n > 3).findFirst();
Optional<Integer> any = nums.stream().findAny(); // parallel streamlerde faydalı

// min / max
Optional<Integer> max = nums.stream().max(Integer::compareTo);

// toArray
Integer[] array = nums.stream().toArray(Integer[]::new);

5. Collectors - Detaylı İnceleme

record Person(String name, int age, String city) {}

List<Person> people = List.of(
    new Person("Ali", 25, "Istanbul"),
    new Person("Veli", 30, "Ankara"),
    new Person("Ayşe", 25, "Istanbul"),
    new Person("Mert", 35, "Izmir")
);

// toList / toSet / toUnmodifiableList
List<String> names = people.stream().map(Person::name).collect(Collectors.toList());
Set<String> cities = people.stream().map(Person::city).collect(Collectors.toSet());

// toMap
Map<String, Integer> nameToAge = people.stream()
        .collect(Collectors.toMap(Person::name, Person::age));

// toMap - çakışan key'ler için merge fonksiyonu
Map<String, Integer> cityMaxAge = people.stream()
        .collect(Collectors.toMap(Person::city, Person::age, Integer::max));

// groupingBy - klasik gruplama
Map<String, List<Person>> byCity = people.stream()
        .collect(Collectors.groupingBy(Person::city));

// groupingBy + downstream collector
Map<String, Long> countByCity = people.stream()
        .collect(Collectors.groupingBy(Person::city, Collectors.counting()));

Map<String, Double> avgAgeByCity = people.stream()
        .collect(Collectors.groupingBy(Person::city, Collectors.averagingInt(Person::age)));

Map<String, List<String>> namesByCity = people.stream()
        .collect(Collectors.groupingBy(Person::city, Collectors.mapping(Person::name, Collectors.toList())));

// partitioningBy - ikili bölme
Map<Boolean, List<Person>> partitioned = people.stream()
        .collect(Collectors.partitioningBy(p -> p.age() >= 30));

// joining
String joinedNames = people.stream().map(Person::name).collect(Collectors.joining(", ", "[", "]"));

// summarizing
IntSummaryStatistics stats = people.stream().collect(Collectors.summarizingInt(Person::age));
System.out.println(stats.getAverage() + " " + stats.getMax() + " " + stats.getMin());

// teeing (Java 12+) - iki collector'ı tek sonuçta birleştirme
record MinMax(int min, int max) {}
MinMax minMax = people.stream()
        .collect(Collectors.teeing(
                Collectors.minBy(Comparator.comparingInt(Person::age)),
                Collectors.maxBy(Comparator.comparingInt(Person::age)),
                (min, max) -> new MinMax(min.get().age(), max.get().age())
        ));

6. Primitive Streamler

IntStream, LongStream, DoubleStream ile boxing maliyetinden kaçının.

IntStream.range(1, 5).forEach(System.out::println);      // 1,2,3,4
IntStream.rangeClosed(1, 5).forEach(System.out::println); // 1,2,3,4,5

int sum = IntStream.rangeClosed(1, 100).sum();
OptionalDouble avg = IntStream.of(1, 2, 3, 4).average();
IntSummaryStatistics stats = IntStream.of(1, 5, 3).summaryStatistics();

// Boxing / unboxing dönüşümleri
Stream<Integer> boxed = IntStream.range(1, 5).boxed();
IntStream unboxed = boxed.mapToInt(Integer::intValue);

// mapToObj
List<String> labels = IntStream.range(0, 3)
        .mapToObj(i -> "item-" + i)
        .toList();

7. Parallel Streamler

List<Integer> bigList = IntStream.rangeClosed(1, 10_000_000).boxed().toList();

long sum = bigList.parallelStream()
                   .mapToLong(Integer::longValue)
                   .sum();

// Açık dönüşüm
Stream<Integer> parallel = bigList.stream().parallel();
Stream<Integer> sequential = parallel.sequential();

Ne zaman kullanılmalı: CPU-yoğun işlemler, büyük veri setleri, stateless ve ilişkisel (associative) operasyonlar. I/O-yoğun işlemler için kullanmayın - bunun yerine Virtual Threads kullanın (bkz. bölüm 11).


8. Records

Records (Java 16+), otomatik oluşturulan constructor, accessor’lar, equals(), hashCode(), toString() ile immutable veri taşıyıcılarıdır.

// Temel record
record Point(int x, int y) {}

Point p = new Point(3, 4);
System.out.println(p.x() + ", " + p.y()); // accessor: x(), getX() değil
System.out.println(p); // Point[x=3, y=4]

// Compact constructor - validasyon / normalizasyon
record Range(int min, int max) {
    Range {
        if (min > max) throw new IllegalArgumentException("min > max");
    }
}

// Özel metodlar
record Circle(double radius) {
    double area() {
        return Math.PI * radius * radius;
    }
}

// Static factory metodları
record User(String email) {
    static User of(String email) {
        return new User(email.toLowerCase());
    }
}

// Interface implementasyonu
interface Shape { double area(); }
record Square(double side) implements Shape {
    public double area() { return side * side; }
}

// Records + Streams - çok yaygın kombinasyon
record Employee(String name, String dept, double salary) {}

List<Employee> employees = List.of(
    new Employee("Ali", "IT", 15000),
    new Employee("Veli", "HR", 12000)
);

Map<String, Double> totalSalaryByDept = employees.stream()
        .collect(Collectors.groupingBy(Employee::dept, Collectors.summingDouble(Employee::salary)));

9. Pattern Matching

9.1 instanceof Pattern Matching (Java 16+)

Object obj = "Hello";

// Eski yöntem
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// Pattern matching yöntemi
if (obj instanceof String s) {
    System.out.println(s.length());
}

// Ek koşulla birlikte
if (obj instanceof String s && s.length() > 3) {
    System.out.println("Uzun string: " + s);
}

9.2 Switch Expressions (Java 14+)

int day = 3;
String name = switch (day) {
    case 1, 7 -> "Hafta sonu";
    case 2, 3, 4, 5, 6 -> "Hafta içi";
    default -> "Bilinmiyor";
};

// Blok gövdeler için yield
String result = switch (day) {
    case 1 -> "Pazartesi";
    default -> {
        String computed = "Gün-" + day;
        yield computed;
    }
};

9.3 switch için Pattern Matching (Java 21+)

sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}

static double area(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
    };
}

9.4 Record Pattern’leri (Java 21+) - Dekonstrüksiyon

record Point(int x, int y) {}
record Line(Point start, Point end) {}

static String describe(Object obj) {
    return switch (obj) {
        case Point(int x, int y) when x == y -> "Çapraz nokta (%d,%d)".formatted(x, y);
        case Point(int x, int y) -> "Nokta (%d,%d)".formatted(x, y);
        case Line(Point(var x1, var y1), Point(var x2, var y2)) ->
                "(%d,%d) noktasından (%d,%d) noktasına çizgi".formatted(x1, y1, x2, y2);
        case null -> "null değer";
        default -> "Bilinmiyor";
    };
}

9.5 Korumalı (Guarded) Pattern’ler (when ifadesi)

static String categorize(Object obj) {
    return switch (obj) {
        case Integer i when i < 0 -> "Negatif tam sayı";
        case Integer i when i == 0 -> "Sıfır";
        case Integer i -> "Pozitif tam sayı";
        case String s when s.isBlank() -> "Boş string";
        case String s -> "Dolu string: " + s;
        default -> "Diğer";
    };
}

10. Sealed Classes + Kapsamlı (Exhaustive) Pattern Matching

Sealed class’lar (Java 17+), hangi sınıfların implement/extend edebileceğini kısıtlar ve bu sayede default dalı olmadan kapsamlı (exhaustive) switch expression’ları mümkün kılar.

sealed interface PaymentMethod permits CreditCard, BankTransfer, Crypto {}
record CreditCard(String number, String cvv) implements PaymentMethod {}
record BankTransfer(String iban) implements PaymentMethod {}
record Crypto(String walletAddress) implements PaymentMethod {}

static String process(PaymentMethod method) {
    // default gerekmez - derleyici kapsamlılığı doğrular
    return switch (method) {
        case CreditCard cc -> "Şu numarayla biten kart tahsil ediliyor: " + cc.number().substring(cc.number().length() - 4);
        case BankTransfer bt -> "IBAN üzerinden transfer: " + bt.iban();
        case Crypto c -> "Şu cüzdana kripto gönderiliyor: " + c.walletAddress();
    };
}

Yeni bir ödeme yöntemi eklenirse (örn. record Wallet(...) implements PaymentMethod), derleyici her switch’i güncellemenizi zorunlu kılar - geleneksel kalıtıma göre büyük bir sürdürülebilirlik avantajı.


11. Virtual Threads

Virtual Threads (Java 21+, JEP 444), JVM tarafından yönetilen hafif thread’lerdir; I/O-yoğun, yüksek eşzamanlılık gerektiren iş yükleri (binlerce/milyonlarca eşzamanlı görev) için idealdir.

// Tek bir virtual thread oluşturma
Thread vThread = Thread.ofVirtual().start(() -> {
    System.out.println("Çalışıyor: " + Thread.currentThread());
});
vThread.join();

// Görev başına virtual thread executor'ı - önerilen pattern
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<String>> futures = IntStream.range(0, 10_000)
            .mapToObj(i -> executor.submit(() -> {
                Thread.sleep(Duration.ofMillis(100)); // I/O simülasyonu
                return "Görev " + i + " tamamlandı";
            }))
            .toList();

    futures.forEach(f -> {
        try {
            System.out.println(f.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
}

// İsimlendirilmiş virtual thread'ler (debug/log için faydalı)
Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);
Thread t1 = builder.start(() -> System.out.println("Merhaba: " + Thread.currentThread()));

Önemli noktalar:


12. Structured Concurrency

Structured Concurrency (Java 21+ preview, StructuredTaskScope), virtual thread’ler üzerinde çalışan ilişkili görev gruplarını tek bir iş birimi olarak ele alır.

import java.util.concurrent.StructuredTaskScope;

record UserData(String profile, String orders) {}

UserData fetchUserData(String userId) throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String> profileTask = scope.fork(() -> fetchProfile(userId))::get;
        Supplier<String> ordersTask = scope.fork(() -> fetchOrders(userId))::get;

        scope.join();           // her iki alt görevi de bekle
        scope.throwIfFailed();  // herhangi bir alt görev başarısız olursa hatayı yay

        return new UserData(profileTask.get(), ordersTask.get());
    }
}

Alt görevlerden biri başarısız olursa, ShutdownOnFailure diğerini otomatik olarak iptal eder - kimsesiz kalan thread yok, kaynak sızıntısı yok.


13. Hepsini Birleştirmek - Gerçek Dünya Pattern’leri

Pattern A: Virtual Threads ile eşzamanlı veri çekme, Records ile modelleme, Streams ile işleme

record Order(String id, String customerId, double amount, String status) {}

List<String> orderIds = List.of("O1", "O2", "O3", "O4", "O5");

List<Order> orders;
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    orders = orderIds.stream()
            .map(id -> executor.submit(() -> fetchOrderFromApi(id))) // I/O-yoğun çağrılar
            .toList()
            .stream()
            .map(future -> {
                try {
                    return future.get();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            })
            .toList();
}

// Stream + Pattern Matching + Records bir arada
Map<String, Double> totalByStatus = orders.stream()
        .collect(Collectors.groupingBy(Order::status, Collectors.summingDouble(Order::amount)));

String summary = orders.stream()
        .map(order -> switch (order) {
            case Order(var id, var cust, var amt, var status) when amt > 1000 ->
                    "Yüksek tutarlı sipariş %s (%s): %.2f".formatted(id, status, amt);
            case Order(var id, var cust, var amt, var status) ->
                    "Sipariş %s (%s): %.2f".formatted(id, status, amt);
        })
        .collect(Collectors.joining("\n"));

Pattern B: Sealed hiyerarşi + kapsamlı switch + Stream dönüşümü

sealed interface Event permits OrderPlaced, OrderCancelled, PaymentReceived {}
record OrderPlaced(String orderId, double amount) implements Event {}
record OrderCancelled(String orderId, String reason) implements Event {}
record PaymentReceived(String orderId, double amount) implements Event {}

List<Event> events = List.of(
    new OrderPlaced("O1", 250.0),
    new PaymentReceived("O1", 250.0),
    new OrderCancelled("O2", "Stokta yok")
);

List<String> log = events.stream()
        .map(e -> switch (e) {
            case OrderPlaced(var id, var amt) -> "Sipariş " + id + " oluşturuldu: " + amt;
            case OrderCancelled(var id, var reason) -> "Sipariş " + id + " iptal edildi: " + reason;
            case PaymentReceived(var id, var amt) -> id + " için ödeme alındı: " + amt;
        })
        .toList();

Pattern C: Structured Concurrency + Streams ile yüksek verimli eşzamanlı işleme

List<String> urls = List.of("https://a.com", "https://b.com", "https://c.com");

List<String> results = new ArrayList<>();
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    List<Supplier<String>> tasks = urls.stream()
            .map(url -> (Supplier<String>) scope.fork(() -> fetchUrl(url))::get)
            .toList();

    scope.join();
    scope.throwIfFailed();

    tasks.stream().map(Supplier::get).forEach(results::add);
}

Özet Tablo

ÖzellikJava SürümüAmaç
Streams API8Fonksiyonel tarzda veri işleme pipeline’ları
takeWhile/dropWhile/iterate (sınırlı)9Geliştirilmiş stream oluşturma/kısa devre
teeing collector12İki collector’ı tek sonuçta birleştirme
Switch expressions14Kısa, ifade tabanlı dallanma
Records16Immutable veri taşıyıcıları
instanceof pattern matching16Tip kontrolü + cast tek adımda
Sealed classes17Kısıtlanmış sınıf hiyerarşileri
Virtual Threads21I/O-yoğun işler için hafif eşzamanlılık
Switch için pattern matching21Switch içinde tip + record dekonstrüksiyonu
Record pattern’leri21Record’ları doğrudan dekonstrükte etme
Structured Concurrency21 (preview)İlişkili eşzamanlı görevleri tek birim olarak yönetme

Bu rehber Java 21 LTS’e kadar olan özellikleri kapsar. Bazı API’ler (Structured Concurrency, bazı pattern matching iyileştirmeleri) kullandığınız JDK sürümüne bağlı olarak hâlâ preview özellik olabilir - gerekirse --enable-preview bayrağını kullanın.