Modern Java Rehberi: Streams, Records, Pattern Matching & Virtual Threads
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
- Java Streams - Temeller
- Stream Oluşturma
- Ara (Intermediate) Operasyonlar
- Terminal Operasyonlar
- Collectors - Detaylı İnceleme
- Primitive Streamler
- Parallel Streamler
- Records
- Pattern Matching
- Sealed Classes + Kapsamlı (Exhaustive) Pattern Matching
- Virtual Threads
- Structured Concurrency
- 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:
- Lazy (tembel): Ara operasyonlar, terminal bir operasyon çağrılana kadar çalışmaz.
- Tekrar kullanılamaz: Bir stream tüketildikten sonra tekrar kullanılamaz.
- Veri yapısı değildir: Elemanları saklamaz, talep üzerine hesaplar.
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:
- Virtual thread’ler ucuzdur - belleği tüketmeden milyonlarcasını oluşturabilirsiniz.
- CPU-yoğun işler için UYGUN DEĞİLDİR - bunun için
parallelStream()veyaForkJoinPoolkullanın. - Bloklayan çağrılar (
Thread.sleep, bloklayan I/O, bazı durumlardasynchronized) otomatik olarak carrier (taşıyıcı) platform thread’inden “unmount” edilir ve o thread başka virtual thread’lere serbest kalır. - Pinning’den (sabitlenmeden) kaçının:
synchronizedbloklarının yoğun kullanımı virtual thread’i carrier thread’ine sabitleyebilir - yüksek verimli kod yollarındaReentrantLocktercih edin.
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
| Özellik | Java Sürümü | Amaç |
|---|---|---|
| Streams API | 8 | Fonksiyonel tarzda veri işleme pipeline’ları |
takeWhile/dropWhile/iterate (sınırlı) | 9 | Geliştirilmiş stream oluşturma/kısa devre |
teeing collector | 12 | İki collector’ı tek sonuçta birleştirme |
| Switch expressions | 14 | Kısa, ifade tabanlı dallanma |
| Records | 16 | Immutable veri taşıyıcıları |
instanceof pattern matching | 16 | Tip kontrolü + cast tek adımda |
| Sealed classes | 17 | Kısıtlanmış sınıf hiyerarşileri |
| Virtual Threads | 21 | I/O-yoğun işler için hafif eşzamanlılık |
| Switch için pattern matching | 21 | Switch içinde tip + record dekonstrüksiyonu |
| Record pattern’leri | 21 | Record’ları doğrudan dekonstrükte etme |
| Structured Concurrency | 21 (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.