Stream API Yazılar 3 – Mapping Methods

Merhabalar.

Önceki yazımızda findFirst vs findAny konusuna değinmiştik. Bu yazımızda da Stream API içerisinde yer alan mapping methotlara değineceğiz.

map:

map() metodu Stream içerisindeki verileri manipüle edip mevcut verilerden yeni bir veri kümesi elde etmemizi sağlar. İsmindeki map ifadesi gibi aslında mevcut veriyi bizim söylediğimiz bir pattern’i kullanarak başka bir veriye map’liyor.

Şimdi örnekler ile yukarıdaki cümleleri daha anlaşılır hale getirelim.

public static void main(String[] args) {
    List<String> namesList = Arrays.asList("Sebastian Vettel", "Michael Schumacher", "Charles Leclerc");
    namesList.stream().map("Driver Name : "::concat).forEach(System.out::println);
}

Örneğimizde elimizdeki nameList üzerinden stream elde edip ondan da map metodunu çağırdık ve map metoduna Stream içerisindeki her bir verinin önüne Driver Name : eklemesini söyledik. Yani bir text’i Stream içerisindeki veriler ile birleştirmiş olduk. Konsol çıktımız da şöyle:

Şimdi bu konu için başka bir örnek daha yapalım. Diyelim ki elimizde bir Integer list var ve bizimde listedeki verilerin 2 katını içeren bir veri kümesine ihtiyacımız oldu. map() metodu ile nasıl yaparız hemen örnekleyelim:

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(2, 4, 6, 8);
    list.stream().map(number -> number * 2).forEach(System.out::println);
}

Bu örneğimizde de elimizdeki verileri 2 katı ile map’ledik ve mevcut veriden yeni bir veri kümesi elde ettik.

Konsol çıktımız da şöyle:

mapToDouble:

map() metodumuz kısıtlayıcılık açısından oldukça rahat bir metot. String bir veri kümesinden Double bir veri kümesi ya da int bir veri kümesinden String bir veri kümesine mapleme yapabiliriz. Stream API içerisinde bazı türlerden diğer türlere mapping işlemi için özelleşmiş metotlar da var ve bunlardan birisi mapToDouble(). Bu metot ile verdiğimiz veri kümesindeki veriler üzerinde Double tipinde veriler elde edebiliyoruz. Hemen örneğimize bakalım.

public static void main(String[] args) {
    List<String> list = Arrays.asList("2", "4.3", "6.51", "8.795");
    list.stream().mapToDouble(Double::parseDouble).forEach(System.out::println);
    /* Aynı işlem map metodu ile de yapılabilirdi. */
    list.stream().map(Double::parseDouble).forEach(System.out::println);
}

Örneğimizde String tipinde bir listemiz var ve tutular veriler aslında sayısal. Bir Stream elde edip onun üzerinden de mapToDouble çağırıyoruz. mapToDouble metoduna da her bir verirnin parseDouble metodu ile elde edilmiş Double halini kullanmasını söylüyoruz. Böylece String tipindeki her bir veri Double hali ile maplenmiş oldu. Biz mapToDouble yerine map metodu ile de aynısını yapabilirdik ama dediğim gibi map metodu dönüş tipi için kısıtlayıcı değilken mapToDouble sadece Double tipinde dönüşler için müsait.

Konsol çıktımız da şu şekilde:

mapToInt & mapToLong:

mapToInt’in ve mapToLong’un mantığı mapToDouble ile tamamen aynı. Tek farkı double yerine Integer ve Long tipinde veri kümeleri sunmaları. Aralarında akış açısından fark olmadığından burada değineceğimiz ekstra bir şey yok. Örnek kodlarımız da şöyle:

List<String> list = Arrays.asList("2", "4", "6", "8");
list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
/* Aynı işlem map metodu ile de yapılabilirdi. */list.stream().map(Integer::parseInt).forEach(System.out::println);
List<String> list = Arrays.asList("2", "4", "6", "8");
list.stream().mapToLong(Long::parseLong).forEach(System.out::println);
/* Aynı işlem map metodu ile de yapılabilirdi. */
list.stream().map(Long::parseLong).forEach(System.out::println);

flatMap:

flatMap dediğimiz kavram aslında doğrudan tekil seviyede işlem yapamadığımız veri kümesini tek tek kullanabilir hale getirmemizi sağlayan bir yapı. Bizim şimdiye kadarki örneklerimiz hep teke tek verilerdi. Örneğin elimizdeki bir veriyi başka bir veriye map ettik. Yani bir input’a karşılık bir output elde ettik. Oysa listeler gibi birden çok veri tutan Stream yapılarıyla da çalışmamız gerekecek ve liste içindeki verileri de map etmemiz gereken durumlar olacak. Bu durumda map’i doğrudan kullanamayız. Önce flatMap ile map kullanabilir hale getirip sonrasında map kullanımına geçiyoruz. flatMap’de bir input verip birden fazla output alabiliyoruz.

Şimdi örnekle durumu daha iyi anlamaya çalışalım.

public static void main(String[] args) {
    Stream<List<Integer>> stream = Stream.of(Arrays.asList(1453,1517));
    List<Integer> newlyCreatedList = stream
            .flatMap(List::stream)
            .map(integer -> integer + 1)
            .collect(Collectors.toList());
    newlyCreatedList.forEach(System.out::println);
}

Örneğimizde içerisinde List<Integer> tipinde veri tutan bir stream’imiz var. Bu stream içerisinde Integer, String vs. gibi tekil veri tutmadığı için map’i doğrudan kullanamıyoruz. Eğer flatMap kullanmadan doğrudan map kullanmaya çalışsaydık doğrudan stream içindeki liste üzerinden map etmeye çalışacağı için compiler bize Operator ‘+’ cannot be applied to ‘java.util.List’, ‘int’ hatasını verecekti fakat bizim liste ile değil listenin içindeki veriler ile çalışmamız gerekiyor. İşte bu nedenle stream içerisinde yer alan her bir liste’den flatMap ile stream elde edip akabinde map kullanıyoruz ve collect ile bir listeye map’lediğimiz verileri topluyoruz.

Şöyle bir mantık da kursak zannedersem yanlış olmaz. Elimizde içerisinde liste gibi birden çok veri tutan bir yapıya sahip bir stream var. Bizim bu listenin içindeki verilerle çalışmamız gerekiyor. İşte flatMap de bu noktada bize stream’in içindeki listenin stream’ini veren bir yapı.

flatMapToDouble:

flatMapToDouble ile yukarıda incelediğimiz flatMap ve mapToDouble’ın birleşimini görmüş olacağız aslında. flatMap ile gördük ki çok boyutlu stream’leri tek boyuta indirebiliyoruz. mapToDouble ile de veri kümememizdeki verileri double’a map’liyebiliyorduk. Şimdi yapacağımız örnek ile de hem çok boyutlu Stream’i işleyeceğiz hem de Double’a mapleyeceğiz. flatMapToDouble ile DoubleStream tipinde bir Stream elde ediyoruz. Örneğimize bakalım.

import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;

public class Main {
    public static void main(String[] args) {
        List<List<String>> listOfStringLists = Arrays.asList(
                Arrays.asList("2", "4"),
                Arrays.asList("3", "6"),
                Arrays.asList("4", "8")
        );

        DoubleStream doubleStream =
                listOfStringLists.stream().flatMapToDouble(childList ->
                        childList.stream()
                                .mapToDouble(Double::parseDouble));
        doubleStream.forEach(System.out::println);
        
    }
}

Örneğimizde listOfStringLists objemiz bir List ve bundan dolayı bir Stream yapısına sahip. İçerisinde de listeler tutuyor ve bundan dolayı da tutulan her bir List objesinden gelen Stream de var. flatMap örneğimizdeki gibi çok boyutlu bir Stream yapısı ile karşı karşıyayız yani.

listOfStringLists üzerinden stream’i elde ediyoruz ve onun üzerinden de flatMapToDouble’ı çağırıyoruz. flatMapToDouble ile listOfStringLists içerisinde tutulan her bir liste içerisindeki veriler double karşılığına map’leniyor ve en nihayetinde tek seviyeli bir Stream elde etmiş oluyoruz ve bu Stream de DoubleStream tipinde oluyor. Konsol çıktımız da şöyle:

flatMapToInt & flatMapToLong:

flatMapToInt ve flatMapToLong’un flatMapToDouble ile kullanım şekli açısında hiçbir farkı bulunmuyor. flatMapToInt ile IntStream tipinde bir Stream elde ediyoruz ve flatMapToLong ile de LongStream tipinde bir Stream elde ediyoruz. Bunun dışında belirgin bir farkları yok. Yine de ikisini de örnekleyelim.

import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;

public class Main {
    public static void main(String[] args) {
        List<List<String>> listOfStringLists = Arrays.asList(
                Arrays.asList("2", "4"),
                Arrays.asList("3", "6"),
                Arrays.asList("4", "8")
        );

        LongStream intStream =
                listOfStringLists.stream().flatMapToLong(childList ->
                        childList.stream()
                                .mapToLong(Long::parseLong));
        longStream.forEach(System.out::println);
        
    }
}

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<List<String>> listOfStringLists = Arrays.asList(
                Arrays.asList("2", "4"),
                Arrays.asList("3", "6"),
                Arrays.asList("4", "8")
        );

        IntStream intStream =
                listOfStringLists.stream().flatMapToInt(childList ->
                        childList.stream()
                                .mapToInt(Integer::parseInt));

        //let's peek and find average of the elements
        /*OptionalDouble opt = doubleStream.peek(System.out::println)
                .average();
        if (opt.isPresent()) {
            System.out.println("average: " + opt.getAsDouble());
        }*/
        intStream.forEach(System.out::println);
        
    }
}

SONUÇ

Bu yazı ile birlikte 6 adet map metodunun kullanımlarını öğrenmiş olduk. Verileri başka bir veriye map etmeyi ve birden çok boyutlu olan Stream’i tek boyutta işlemeyi öğrenmiş olduk.

Gelecek yazıda terminate operations konusuna değineceğiz.

Sağlıcakla kalın…

Latest posts by İlkay Günel (see all)

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir