Java'da Stream API: Pratik Kullanım Rehberi
Stream API Nedir ve Neden Kullanmalısınız?
Java 8 ile tanıtılan Stream API, koleksiyonlar üzerinde fonksiyonel programlama tarzında işlemler yapmanızı sağlar. Bir Stream, veri kaynağından (collection, array, I/O channel) gelen elemanlar üzerinde pipeline tarzında işlemler (filter, map, reduce, collect) uygulamanıza olanak tanır.
Geleneksel for döngüleri ile yapılan işlemleri düşünün: döngü değişkeni tanımla, koşul yaz, sayacı artır, geçici listeler oluştur. Stream API ile aynı işlemi çok daha okunabilir ve deklaratif şekilde yapabilirsiniz.
// Geleneksel yaklaşım
List<String> activeUserNames = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
activeUserNames.add(user.getName().toUpperCase());
}
}
Collections.sort(activeUserNames);
// Stream API ile
List<String> activeUserNames = users.stream()
.filter(User::isActive)
.map(User::getName)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());İkinci yaklaşım ne yaptığını ilk bakışta anlatıyor: aktif kullanıcıları filtrele, isimlerini al, büyük harfe çevir, sırala, listeye topla.
Temel Stream Operasyonları
Stream operasyonları iki kategoriye ayrılır:
Intermediate (Ara) operasyonlar:
filter,map,flatMap,sorted,distinct,peek— Bir stream dönerler, lazy (tembel) çalışırlarTerminal (Son) operasyonlar:
collect,forEach,reduce,count,findFirst,anyMatch— Sonuç üretir, stream'i tüketirler
Önemli: Bir Stream yalnızca bir kez tüketilebilir. Terminal operasyondan sonra aynı stream'i tekrar kullanamazsınız. Yeni bir stream oluşturmanız gerekir.
filter — Elemanları Filtreleme
filter, bir Predicate alır ve koşulu sağlayan elemanları geçirir:
List<Product> expensiveProducts = products.stream()
.filter(p -> p.getPrice() > 1000)
.filter(p -> p.getCategory().equals("Electronics"))
.collect(Collectors.toList());
// Method reference ile
List<Product> inStockProducts = products.stream()
.filter(Product::isInStock)
.collect(Collectors.toList());map — Dönüştürme
map, her elemanı bir fonksiyonla dönüştürür:
// Kullanıcı isimlerini al
List<String> names = users.stream()
.map(User::getName)
.collect(Collectors.toList());
// Fiyatlara %18 KDV ekle
List<Double> pricesWithVat = products.stream()
.map(Product::getPrice)
.map(price -> price * 1.18)
.collect(Collectors.toList());
// DTO dönüşümü — çok yaygın kullanım
List<UserDto> userDtos = users.stream()
.map(user -> new UserDto(user.getId(), user.getName(), user.getEmail()))
.collect(Collectors.toList());reduce — İndirgeme
reduce, stream elemanlarını tek bir değere indirger:
// Toplam fiyat hesapla
double totalPrice = products.stream()
.map(Product::getPrice)
.reduce(0.0, Double::sum);
// En uzun ismi bul
Optional<String> longestName = names.stream()
.reduce((a, b) -> a.length() >= b.length() ? a : b);
// Tüm isimleri birleştir
String allNames = names.stream()
.reduce("", (a, b) -> a.isEmpty() ? b : a + ", " + b);
reducekullanırken ilk parametre (identity) önemlidir. Toplama için 0, çarpma için 1, string birleştirme için "" kullanın.
collect ve Collectors
collect, stream sonuçlarını bir veri yapısına toplar. Collectors utility sınıfı birçok hazır collector sunar:
// List'e topla
List<String> list = stream.collect(Collectors.toList());
// Set'e topla (unique elemanlar)
Set<String> set = stream.collect(Collectors.toSet());
// Map'e dönüştür
Map<Long, String> userMap = users.stream()
.collect(Collectors.toMap(User::getId, User::getName));
// String birleştirme
String csv = names.stream()
.collect(Collectors.joining(", "));
// Ortalama hesapla
Double avgPrice = products.stream()
.collect(Collectors.averagingDouble(Product::getPrice));
// İstatistik özeti
DoubleSummaryStatistics stats = products.stream()
.collect(Collectors.summarizingDouble(Product::getPrice));
// stats.getAverage(), stats.getMax(), stats.getMin(), stats.getSum(), stats.getCount()flatMap — İç İçe Yapıları Düzleştirme
flatMap, her elemanı bir stream'e dönüştürüp sonuçları tek bir stream'de birleştirir. Nested koleksiyonlarda çok kullanışlıdır:
// Her siparişin ürünlerini tek listede topla
List<Product> allProducts = orders.stream()
.flatMap(order -> order.getProducts().stream())
.collect(Collectors.toList());
// Cümleleri kelimelere ayır
List<String> words = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.distinct()
.collect(Collectors.toList());
// Optional ile flatMap
Optional<String> city = user
.flatMap(User::getAddress)
.flatMap(Address::getCity)
.map(String::toUpperCase);groupingBy ve partitioningBy
groupingBy, elemanları bir key fonksiyonuna göre gruplar. SQL'deki GROUP BY ifadesinin Java karşılığıdır:
// Kategoriye göre grupla
Map<String, List<Product>> byCategory = products.stream()
.collect(Collectors.groupingBy(Product::getCategory));
// Kategoriye göre grupla ve sayısını hesapla
Map<String, Long> countByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.counting()
));
// Kategoriye göre grupla ve ortalama fiyatı hesapla
Map<String, Double> avgPriceByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.averagingDouble(Product::getPrice)
));
// Çok seviyeli gruplama: kategori -> marka -> ürünler
Map<String, Map<String, List<Product>>> nested = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.groupingBy(Product::getBrand)
));partitioningBy, elemanları bir predicate'e göre iki gruba ayırır (true/false):
Map<Boolean, List<Product>> partition = products.stream()
.collect(Collectors.partitioningBy(p -> p.getPrice() > 500));
List<Product> expensive = partition.get(true);
List<Product> affordable = partition.get(false);sorted, distinct, limit ve skip
// Fiyata göre sırala
List<Product> sorted = products.stream()
.sorted(Comparator.comparing(Product::getPrice))
.collect(Collectors.toList());
// Fiyata göre azalan sırala
List<Product> sortedDesc = products.stream()
.sorted(Comparator.comparing(Product::getPrice).reversed())
.collect(Collectors.toList());
// Çoklu sıralama: önce kategori, sonra fiyat
List<Product> multiSorted = products.stream()
.sorted(Comparator.comparing(Product::getCategory)
.thenComparing(Product::getPrice))
.collect(Collectors.toList());
// İlk 10 pahalı ürün
List<Product> top10 = products.stream()
.sorted(Comparator.comparing(Product::getPrice).reversed())
.limit(10)
.collect(Collectors.toList());
// Pagination: sayfa 3, sayfa boyutu 20
List<Product> page3 = products.stream()
.skip(40) // (3 - 1) * 20
.limit(20)
.collect(Collectors.toList());Parallel Streams
parallelStream() veya stream().parallel() ile stream işlemlerini birden fazla thread'de çalıştırabilirsiniz:
// Parallel stream
long count = products.parallelStream()
.filter(p -> p.getPrice() > 100)
.count();
// Büyük veri setlerinde faydalı
List<ProcessedData> results = largeDataSet.parallelStream()
.map(this::heavyComputation)
.collect(Collectors.toList());Dikkat edilmesi gerekenler:
Parallel stream, ForkJoinPool.commonPool() kullanır (varsayılan thread sayısı: CPU core - 1)
Küçük koleksiyonlarda (< 10.000 eleman) parallel stream daha yavaş olabilir — thread oluşturma maliyeti
Stateful operasyonlardan kaçının (shared mutable state)
Sıralama (order) önemliyse
forEachOrdered()kullanınI/O bound işlemlerde parallel stream yerine CompletableFuture tercih edin
// YANLIS: Shared mutable state
List<String> results = new ArrayList<>(); // Thread-safe degil!
stream.parallel().forEach(item -> results.add(process(item)));
// DOGRU: Collect kullanın
List<String> results = stream.parallel()
.map(this::process)
.collect(Collectors.toList());Performans İpuçları
Stream'i tekrar kullanmayın. Her kullanımda yeni stream oluşturun.
Gereksiz boxing'den kaçının.
IntStream,LongStream,DoubleStreamkullanın:
// Yavaş: Integer boxing/unboxing
int sum = numbers.stream()
.map(n -> n * 2)
.reduce(0, Integer::sum);
// Hızlı: Primitive stream
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.map(n -> n * 2)
.sum();Kısa devre (short-circuit) operasyonları kullanın:
findFirst(),anyMatch(),limit()stream'i erken sonlandırır.filter'ı map'ten önce koyun. Önce filtreleme yaparak dönüştürülecek eleman sayısını azaltın.
Büyük veri setleri için
Stream.of()yerineCollection.stream()kullanın.
Gerçek Dünya Örneği: Sipariş Raporu
Birden fazla Stream operasyonunu birleştiren bir rapor oluşturalım:
public OrderReport generateReport(List<Order> orders) {
// Son 30 günün siparişleri
LocalDate thirtyDaysAgo = LocalDate.now().minusDays(30);
List<Order> recentOrders = orders.stream()
.filter(o -> o.getOrderDate().isAfter(thirtyDaysAgo))
.filter(o -> o.getStatus() != OrderStatus.CANCELLED)
.collect(Collectors.toList());
// Toplam ciro
double totalRevenue = recentOrders.stream()
.flatMap(o -> o.getItems().stream())
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
// Kategoriye göre ciro
Map<String, Double> revenueByCategory = recentOrders.stream()
.flatMap(o -> o.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getCategory,
Collectors.summingDouble(item -> item.getPrice() * item.getQuantity())
));
// En çok satan 5 ürün
List<String> topProducts = recentOrders.stream()
.flatMap(o -> o.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getProductName,
Collectors.summingInt(OrderItem::getQuantity)
))
.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.limit(5)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
return new OrderReport(totalRevenue, revenueByCategory, topProducts);
}Özet
Stream API koleksiyonlar üzerinde fonksiyonel tarzda, okunabilir ve deklaratif işlemler sağlar
filter/map/reduce üçlüsü en temel operasyonlardır — çoğu işi bunlarla çözersiniz
flatMap iç içe yapıları düzleştirirken, groupingBy gruplama ve aggregation için kullanılır
Parallel streams büyük veri setlerinde faydalıdır ama shared mutable state'den kaçının
Primitive stream'ler (IntStream, LongStream) boxing maliyetini ortadan kaldırır
filter'ı map'ten önce koyarak performansı artırın
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
Bu konuyu derinlemesine öğrenmek ister misin?
Java Programlama: Sıfırdan İleri Seviyeye
İlgili Yazılar
Java Optional: NullPointerException'a Kesin Çözüm ve Doğru Kullanım Rehberi
Java Optional sınıfını sıfırdan ileri seviyeye öğrenin. NullPointerException'a kesin çözüm, doğru kullanım kalıpları, an...
Java Nedir? Kapsamlı Başlangıç Rehberi 2026
Java nedir, ne işe yarar, nasıl çalışır? JVM, JDK, JRE farkları, Java ile neler yapılır, kariyer fırsatları ve öğrenme y...
Java Stream API: Koleksiyonları Fonksiyonel Programlama ile Yönetmenin Tam Rehberi
Java Stream API'yi sıfırdan ileri seviyeye kadar öğrenin. Gerçek dünya örnekleri, yaygın hatalar, performans ipuçları ve...