5 Ekim 2020 Pazartesi

Stream.mapMulti metodu

Giriş
Java 16 ile geliyor. İmzası şöyle
<R> Stream<R> mapMulti​(BiConsumer<? super T,​? super Consumer<R>> mapper)
Bu metodun mapMultiToDouble(), mapMultiToInt(), and mapMultiToLong() gibi kardeşleri de var

Bu metod
1. filter + MapToX yerine kullanılabilir
2. filter + flatMap yerine kullanılabilir

Bu metodu şu noktalarda tercih edilmeli deniliyor. Yani aslında mapMulti() metoduna geçilen consumer isimli Stream JVM tarafından oluşturuluyor. Elimizdeki tüm nesneler bu yeni Stream'e geçiliyor.
- When replacing each stream element with a small (possibly zero) number of elements. Using this method avoids the overhead of creating a new Stream instance for every group of result elements, as required by flatMap.

-When it is easier to use an imperative approach for generating result elements than it is to return them in the form of a Stream.
MapToX Yerine Kullanımı
Örnek - İki Tane Immutable Record Birleşimi
Elimizde iki tane record olsun. record nesnesini setter metodu yoktur. Dolayısıyla her employee için maaş artışı yani EmployerSalaryUpdateRecord isteğini işleyebilmek için yeni bir EmployeeRecord nesnesi oluşturmak gerekir.
record EmployerSalaryUpdateRecord(String employerId, double increment){}

public record EmployeeRecord(String id, String name, double salary, String employerId) {
  public EmployeeRecord{
    id = StringUtils.isEmpty(id) ? UUID.randomUUID().toString() : id;
  }
}
Şöyle yaparız. Burada elimizde LocalMapper isimli yeni bir record var. mapMulti() ile LocalMapper nesneleri oluşturuluyor. Daha sonra map() metoduyla LocalMapper nesnelerinden yeni EmployeeRecord nesneleri oluşturuluyor 
public static List<Employee> getEmployeesByEmployerId(String employerId) {
  ...
}

public List<EmployeeRecord> computeAndUpdateSalaryIncrement(
List<EmployerSalaryUpdateRecord> employerSalaryUpdates) {

  record LocalMapper(String employerId, double increment, Employee employee){}

  return employerSalaryUpdates
    .stream()
    .<LocalMapper>mapMulti(
      (employerSalaryUpdate, consumer) ->
        getEmployeesByEmployerId(employerSalaryUpdate.employerId())
        .forEach(employee ->
          consumer.accept(new LocalMapper(
            employerSalaryUpdate.employerId(),
            employerSalaryUpdate.increment(),
            employee)))
)//end of mapMulti
    .map(t-> new EmployeeRecord(t.employee().getId(), t.employee().getName(),
              t.increment() * t.employee().getSalary() + t.employee().getSalary(),
t.employerId()))
    //More processing steps
    .collect(Collectors.toList());
}

filter + MapToX Yerine Kullanım
Açıklaması şöyle
Java 16 introduced mapMulti which is similar to flatMap in usage and has performance characteristics very close to those of filter/map.
Performans
Birinci açıklamada performans dikkate alınıyor. Bu metod ile tek bir stream yaratılıyor.
Örnek
Elimizde şöyle bir kod olsun. Bu kod ile filter() ve mapToInt() için birer tane olmak üzere iki tane geçici stream yaratılır  
int sum = Stream.of(1, 2.0, 3.0, 4F, 5, 6L)
  .filter(number -> number instanceof Integer)
  .mapToInt(number -> (Integer) number)
  .sum();
Aynı kodu şöyle yaparsak tek bir geçici stream yaratılır
int sum = Stream.of(1, 2.0, 3.0, 4F, 5, 6L)
  .mapMultiToInt((number, consumer) -> {
    if (number instanceof Integer) {
      consumer.accept((Integer) number);
    }
  })
  .sum();
// 6

filter + flatMap Yerine Kullanım
Örnek
Elimizde şöyle bir kod olsun. Burada nesnenin alt nesnelerin belli bir koşula göre süzülüp başka bir nesneye dönüştürülüyor.
List<Product> products = ...
List<Offer> offers = products.stream()
  .filter(product -> "PRODUCT_CATEGORY".equals(product.getCategory()))
  .flatMap(product -> product.getVariations().stream()
    .filter(Variation::isAvailable)
    .map(v -> new Offer(
                product.getName() + "_" + v.getName(),
                product.getBasePrice() + v.getPrice()
        ))
    )
  .collect(Collectors.toList());
Aynı kodu şöyle yapabiliriz
List<Product> products = ...
List<Offer> offers = products.stream()
  .mapMulti((product, consumer) -> {
    if ("PRODUCT_CATEGORY".equals(product.getCategory())) {
      for (Variation v : product.getVariations()) {
        if (v.isAvailable()) {
          Offer offer = new Offer(
                          product.getName() + "_" + v.getName(),
                          product.getBasePrice() + v.getPrice());
          consumer.accept(offer);
        }
      }
    }
  })
  .collect(Collectors.toList());
Imperative Yaklaşım
Örnek
Şöyle yapabiliriz.
Stream.of("Java", "Python", "JavaScript", "C#", "Ruby", "")
  .mapMulti((str, consumer) -> {
    for (int i = 0; i < str.length(); i++) {
      consumer.accept(str.length());
    }
  })
  .forEach(i -> System.out.print(i + " "));

// 4 4 4 4 6 6 6 6 6 6 10 10 10 10 10 10 10 10 10 10 2 2 4 4 4 4 

mapMultiToInt metodu
Şöyle yaparız
int sum = Stream.of(1, 2.0, 3.0, 4F, 5, 6L)
  .mapMultiToInt((number, consumer) -> {
    if (number instanceof Integer) {
      consumer.accept((Integer) number);
    }
  })
  .sum();
// 6

Hiç yorum yok:

Yorum Gönder