21 Mayıs 2018 Pazartesi

Collectors Sınıfı

Giriş
Şu satırı dahil ederiz.
import java.util.stream.Collectors;
Sınıf hazır tanımlı Collector nesneleri için factory gibi çalışır. Açıklaması şöyle
Collect is an extremely useful terminal operation to transform the elements of the stream into a different kind of result, e.g. a List, Set or Map. Collect accepts a Collector which consists of four different operations: a supplier, an accumulator, a combiner and a finisher. This sounds super complicated at first, but the good part is Java 8 supports various built-in collectors via the Collectors class. So for the most common operations you don't have to implement a collector yourself.
averagingDouble
Şöyle yaparız.
Double[] x = {5.4, 5.56, 1.0};
double avg = Arrays.stream(x).collect(Collectors.averagingDouble(n -> n));
collectingAndThen metodu
Collectors collectingAndThen metodu yazısına taşıdım

counting metodu
Stream'in uzunluğunu verir.

Elimizde bir sözcük listesi olsun. Listedeki sözcüklerin frekans tablosunu çıkarıp belli bir değerin üzerinde olan sözcüklerin sayısını şöyle buluruz.
int countThreshold = 2;
long sum =
    words.stream()
          .collect(Collectors.groupingBy(Function.identity(),
                                         Collectors.counting()))
          .values()
          .stream()
          .filter(x -> x >= countThreshold)
          .reduce(0L, Long::sum);
İki setin kesişimi kaç tane elemandan oluşur diye bulmak isteyelim. Şöyle yaparız.
Set<String> sOne = ...; Set<String> sTwo = ...;
long numFound = sOne.parallelStream()
                    .filter(segment -> sTwo.contains(segment))
                    .collect(Collectors.counting());
flatMapping metodu
Java 9 ile geliyor. groupingBy() ile kullanılırken value tarafındaki List elemanlarının üzerinde dolaşabilmeyi sağlar.
Örnek
Elimizde şöyle bir kod olsun. Aynı yıla ait tüm Skill'leri gruplamak isteyelim.
public class WorkExperience {
  private int year;
  private List<Skills> skill;
  ...
}
Bu sınıfın listesi olsun.
List<WorkExperience> workExperienceList = ...;
Her yıla göre Skill nesnelerini gruplamak için şöyle yaparız.
Map<Integer, Set<Skills>> map = workExperienceList.stream()
  .collect(Collectors.groupingBy(
              WorkExperience::getYear, 
              Collectors.flatMapping(workexp -> workexp.getSkill().stream(), 
                                   Collectors.toSet())));
Örnek
Elimizde şöyle bir kod olsun. Aynı kişiye ait tüm adresleri gruplamak isteyelim.
class Person {
    String name;
    int age;
    List<Address> address;
}

class Address {
    String street;
    String city;
    String country;
}
Şöyle yaparız.
Map<Person, List<Address>> resultSet = findPerson.stream()
    .collect(
        Collectors.groupingBy(
            Function.identity(),
            Collectors.flatMapping(
                p -> p.getAddress().stream(), 
                Collectors.toList()
            )
        )
    )
;

joining metodu - delimeter
Şöyle yaparız.
var strings = stream.collect(Collectors.joining(", "));
joining metodu - delimeter + prefix + suffix
İmzası şöyle
Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
Örnek
Şöyle yaparız.
String s = stringList.stream().collect(Collectors.joining(" and ", "prefix_", "_suffix"))
Örnek
Şöyle yaparız.
String values = list.stream().collect(Collectors.joining("','", "'", "'"));
Çıktı olarak şunu alırız.
'rest','test','best'
groupingBy metodu - classifier
Sadece classifier verilen groupingBy() metodunu çağırırsak Map'in ikinci parametresi her zaman List olur.

Not : Collectors.groupingBy() ve Collectors.toMap() çok benziyorlar. Fark olarak toMap() sonucunda dönen Map nesnesinin Key tipi classifier'ın döndürdüğü tiptir. groupingBy'da ise nesnenin tipidir.

Örnek
groupingBy() ile şöyle yaparız.
Map<A> result = list.stream()
    .collect(Collectors.groupingBy(A::getId,...))
    .values();
toMap ile şöyle yaparız.
Map<Integer, A> result = listOfA.stream()
    .collect(Collectors.toMap(A::getId, ..., ...));
Örnek
Value değerlerine göre gruplamak için şöyle yaparız.
Map<String, Integer> map = new HashMap<>();

map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);

Map<Integer, List<String>> newMap = map.keySet()
                                  .stream()
                                  .collect(Collectors.groupingBy(map::get));

System.out.println(newMap);

> {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
groupingBy metodu - classifier + downstream
Şu satırı dahil ederiz.
import static java.util.stream.Collectors.groupingBy;
downstream map'in sağ tarafındaki nesnenin ne olacağını belirtir. Metodun imzası şöyledir.
groupingBy(classifier, downstream)
Metod bir Map döner. Map nesnesinin key alanı classifier'ın döndürdüğü tip, Value alanı ise downstream'in döndürdüğü tiptir.
Classifier key değerini döner. Böylece nesneler key değerlerine göre gruplanır. Aynı key değerine sahip nesneleri downStream'e verilir.
Örnek
Aynı departmanda çalışan kişi sayısı şöyle bulunur.
Map<Department, Long> map =
    list.stream()
    .collect(Collectors.groupingBy(Employee::getDep, Collectors.counting()));
Şöyle bir çıktı alabiliriz.
Personnel: 4
Accounting: 3 
Production: 3
Örnek
Elimizde şöyle bir kod olsun
List<Pair<A,B>> list = //some list of pairs ; 
Şöyle yaparız.
Map<A,Set<Pair<A,B>> m =   list.stream().collect(Collectors.groupingBy((Pair::getLeft),
  Collectors.toSet()));
Örnek
Elimizde şöyle bir kod olsun
public class A {
  private Integer id;
  private String name;
  private List<B> list;
  ...
}
Elimizde şöyle bir kod olsun
public class B {
  ...
}
Çıktı olarak şunu isteyelim
[
  A(1, a_one, [B(1, b_one), B(2, b_two), B(3, b_three)]),
  A(2, a_two, [B(2, b_two), B(4, b_four), B(5, b_five)]),
  A(3, a_three, [B(4, b_four), B(5, b_five)])
]

groupingBy metodu - classifier + mapFactory + downstream
Elimizde şöyle bir map olsun.
Map<Integer, Long> m = ...;
Bu map'i value değerleri anahtar olarak şekilde şu hale getirmek isteyelim.
TreeMap<Long, List<Integer>> tm = ...
Şöyle yaparız.
Map<Long, List<Integer>> tm = 
m.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue, // group the entries by the 
                                                    // value (the frequency)
   TreeMap::new, // generate a TreeMap
   Collectors.mapping (Map.Entry::getKey, 
   Collectors.toList()))); // the
                           // value of the output TreeMap 
                           // should be a List of the original keys          
groupingBy metodu groupingBy
İç içe gruplamayı sağlar. İlk groupingBy Map<String,...> yapısını oluşturur. İkinci groupingBy içteki Map<String,...> yapısını oluşturur
Map<String, Map<String, List<Student>>> map= studs.collect(Collectors.groupingBy(
Student::getName,Collectors.groupingBy(Student::getLocation)));
groupingBy metodu+ collectionAndThen
Stream'deki nesneleri bir Map içine gruplar. Daha sonra Map'teki her bir elemanı bir kere daha dönüştürme imkanı tanır.

Elimizde bir Task ve Task'lara ait Job nesneleri olsun.
public class Task { int taskId; List<Job> jobList; }
Her Job ise bir agent tarafından çalıştırılabiliyor olsun.
// in class Job
int getAgentId() { // return the "agent" who is responsible for @param job }
Her agent'a düşen task'ları şöyle gruplamak isteyelim
// in class Partition; `Integer` for "agent" id
Map<Integer, Task> partition(Task task) { }
Şöyle yaparız.
task.getJobList().stream()
    .collect(
        groupingBy(
             Job::getAgentId,//agentID -> Job map
             collectingAndThen(toList(), jobs -> new Task(id, jobs))));
mapping metodu
Şu satırı dahil ederiz.
import static java.util.stream.Collectors.mapping;
Örnek
Elimizde şöyle bir map olsun.
ImmutableListMultimap<String, Character> immutableMultiMap
Bu yapıyı şuna çevirmek isteyelim.
Map<String, List<Character>> result
Şöyle yaparız. mapping ile her eleman Map içindeki Entry<String,Character> yapısından kurtulup List<Character> yapısına dönüşür.
Map<String, List<Character>> result = immutableMultiMap.entries().stream()
  .collect(groupingBy(Entry::getKey, TreeMap::new,
           mapping(Entry::getValue, toList())));
partitioningBy metodu
Collectors partitioningBy metodu yazısına taşıdım.
reduce metodu
Stream Sınıfı Terminal Operations yazısına taşıdım.

reducing metodu
Tüm elemenaları bir işleme sokarak tek sonuç döner.
Örnek
Aynı değere sahip iki metod varsa null dönmesi için şöyle yaparız.
Optional<String> res = list.stream()
    .filter(item -> item.startsWith("A"))
    .limit(2)
    .collect(Collectors.reducing((s1, s2) -> null));
toCollection metodu
Döndürülen nesne List veya Map değil daha özel bir sınıf olsun istersek şöyle yaparız.
ArrayList<SomeClass> list =
  inputStrings.stream()
    .map(SomeClass::doSomthing)
    .filter((someClazz)->{return someClazz!=null;})
    .collect(Collectors.toCollection(ArrayList::new));
Şöyle yaparız.
CopyOnWriteArrayList<Foo> list =
  fields.stream()
    .distinct()
    .collect(toCollection(CopyOnWriteArrayList::new));
toList metodu
Collectors toList metodu yazısına taşıdım.

toMap metodu - keyMapper + valueMapper
Yeni bir Map döndürür. Nesne için çağrılacak key fonksiyonunu ve value fonksiyonunu kodlarız.
Örnek
Şöyle yaparız. Object sınıfının metodunu kullandık.
Set<Foo> set = ...;
Map<Integer, Foo> map = set.stream()
                           .collect( Collectors.toMap( Object::hashCode, 
                                                       element -> element )
                                   );
Örnek
Elimizde şöyle bir stream olsun
Map<String, String> input = new HashMap<>();
Bu map'i Map<String,Integer> yapmak isteyelim ve tek sayıları silelim istiyoruz. Şöyle yaparız. Mevcut değerleri kullanmak için Map.Entry sınıfının metodlarını getter olarak kullandık.
Map<String, Integer> output =
input.entrySet().
stream().
map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), Integer.valueOf(e.getValue())))
.filter(e -> e.getValue() % 2 == 0)
.collect(Collectors.toMap(
             Map.Entry::getKey,
             Map.Entry::getValue
         ));
toMap metodu - keyMapper + valueMapper + Merge Function
keyMapper key değerini döner.
valueMapper value değerini döner.
mergeFunction aynı key değerine sahip bir nesne bulununca çağrılır

Örnek
Elimizde şöyle bir liste olsun.
List<A>{
 name = "abc"
 List<B>{1,2}

 name= "xyz"
 List<B>{3,4}

 name = "abc"
 List<B>{3,5}
}
İsmi aynı olan numaraları birleştirmek isteylim. Yani şöyle bir sonuç istiyoruz.
List<A>{
 name = "abc"
 List<B>{1,2,3,5}

 name="xyz"
 List<B>{3,4}
}
Bunun için toMap metodunun 3 parametreli keyMapper, valueMapper, mergeFunction halini kullanırız. keyMapper name değerini döner. valueMapper nesnenin kendisini döner. mergeFunction aynı name değerine sahip bir nesne bulununca çağrılır. a , b  valueMapper'in döndürdüğü nesne tipindendir. a nesnesinin numaralarına b nesnesinin numaraları eklenir. Şöyle yaparız.
Collection<A> result = list.stream()
         .collect(Collectors.toMap(a -> a.name, a -> a, 
                      (a, b) -> {a.numbers.addAll(b.numbers); return a;}))
         .values();
Örnek
Elimizde şöyle iki sınıf olsun.
class Person {
    String name;
    int age;
    List<Address> address;
}

class Address {
    String street;
    String city;
    String country;
}
Şöyle yaparız.
List<Person> merged = findPerson.stream()
  .collect( 
    Collectors.collectingAndThen(
      Collectors.toMap( 
        (p) -> new AbstractMap.SimpleEntry<>( p.getName(), p.getAge() ), 
              Function.identity(), 
        (left, right) -> { 
          left.getAddress().addAll( right.getAddress() ); 
          return left; 
        }
      ),        
      ps -> new ArrayList<>( ps.values() ) //Map'i Listeye çevir
    ) 
);
Örnek
Elimizde şöyle bir kod olsun
public A(A another) {
    this.id = another.id;
    this.name = another.name;
    this.list = new ArrayList<>(another.list);
}

public A merge(A another) {
    list.addAll(another.list):
    return this;
}
Çıktı olarak şunu isteyelim
[
    A(1, a_one, [B(1, b_one), B(2, b_two), B(3, b_three)]),
    A(2, a_two, [B(2, b_two), B(4, b_four), B(5, b_five)]),
    A(3, a_three, [B(4, b_four), B(5, b_five)])
]
Şöyle yaparız.
Map<Integer, A> result = listOfA.stream()
    .collect(Collectors.toMap(A::getId, A::new, A::merge));

Collection<A> result = map.values();
Örnek
Şöyle yaparız.
Map<String, List<String>> map3 = Stream.of(map1, map2)
                .flatMap(map -> map.entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        e -> new ArrayList<>(e.getValue()),
                        (left, right) -> {left.addAll(right); return left;}
                ));
toSet metodu
Elimizde bir Map olsun
Map<Key, Value> map = ...;
Tüm value nesnelerini bir Set'e doldurmak istersek şöyle yaparız.
Set<Key> keys = map.entrySet().stream()
  .filter(entry -> entry.getValue() == value)
  .map(entry -> entry.getKey())
  .collect(Collectors.toSet());
toUnmofifiableList metodu
Java 10 ile geliyor. Açıklaması şöyle.
The Collectors.toUnmodifiableList would return a Collector that disallows null values and will throw NullPointerException if it is presented with a null value.
Şöyle yaparız.
List<Integer> result = Arrays.asList(1, 2, 3, 4)
            .stream()
            .collect(Collectors.toUnmodifiableList());



Hiç yorum yok:

Yorum Gönder