Showing posts with label Optional. Show all posts
Showing posts with label Optional. Show all posts

Tuesday, August 23, 2016

Java 8'de Optional Kullanımı


Java'da iki tür tip bulunuyor: basit tipler ve sınıflar. Değişkeni bellekte bir kutu olarak düşünebiliriz. Basit tipten bir değişken için bu kutunun içinde tanımlandığı basit tipten bir değer yer alır. Java'daki basit tipler fazla değil: byte, short, int, long, float, double, char ve boolean. Bunun dışındaki her tip bir sınıftır. Sınıf tipinden bir değişken ise referans değişkeni olarak adlandırılır. Referans tipinden bir değişken için ise kutunun içinde bir değer değil, bir adres yer alır. Bu adres ilgili sınıftan bir nesnenin adresidir. Java'da tüm nesneler Heap'de olmak zorundadır. Bu nedenle nesnelere bazen Heap nesnesi dendiği olur. Ancak referans tipinden bir değişken için kutunun içi boş olabilir. Kutunun içinin boş olduğunu, geçerli bir nesneyi göstermediğini tanımlamak için Java dilindeki bir sözcükten yararlanıyoruz: null. Eğer kutunun içinde null değeri var ise bu değişkeni kullanmasanız iyi olur. Eğer referans değişkenimiz null değeri taşıyorsa, bu referansı kullanarak bir metot çağrısı yapmanız, yürütme zamanında NullPointerException (NPE) tipinde bir hata almanıza neden olur. 


NPE bir RuntimeException olduğu için en azından derleyici sizi bu tür bir hatayı ele almak zorunda bırakmaz. Ele almadığınız durumda ise uygulamanız istenmeyen bir şekilde sona erecektir. Kutunun içinin boş olması, referans değişkeninin değerinin null olması, bazen mantıksal bir hatanın sonucudur. Bu hatayı mutlaka gidermeniz gerekir. Kutunun içinin boş olması, bazen de işin doğası gereği olabilir. İşin doğası gereği null değeri taşıyacak ise bu referans üzerinden erişimden önce mutlaka önlem alınması gerekir:
public class MovieSearchService {
. . .
    Collection<Movie> search(Criteria criteria){ . . . }
. . .
}

MovieSearchService içindeki search() metodu verilen bir kritere göre sorgu sonucu dönüyor ve sorgu sonucu boş olabilir. Bu durumda metot null dönüyorsa sorgu sonucuna erişimden önce mutlaka önlem almak gerekir:
MovieSearchService serchSrv= InMemeoryMovieSearchService.getInstance();
Criteria someCriteria= new Criteria(/* irrelavent */); 
Collection<Movie> searchResult= searchSrv.search(someCriteria);
if (searchResult != null){
   System.out.println(String.format("There are %d movies found:",searchResult.size()));
   searchResult.stream().forEach(System.out::println)    
} 

Eğer bir metot torba dönüyorsa, uygulama programcısını NPE'ye neden olmayacak ancak yukarıdaki gibi bir önlem almaya zorlamayacak bir çözüm sunabiliriz. Bunun için eğer arama kriterine uygun hiç bir film yoksa basitçe içi boş bir liste dönebiliriz: Collections.emptyList(). Bunun yanında, Java 8 ile birlikte doğrudan torba dönmek yerine Stream<> ya da CompletableFuture<> dönülebilir:
public interface ManagedServerRepository extends Repository<ManagedServer,Long> {
     Optional<S extends ManagedServer> findOne(Long id);
     <S extends ManagedServer> S save(S server);
     <S extends ManagedServer> S delete(S server);
     <S extends ManagedServer> S delete(Long id);
     @Query("select m from ManagedServer m")
     Stream<ManagedServer> streamAllServers();
     Stream<ManagedServer> findByServerType(ServerType serverType);
     @Async
     CompletableFuture<List<ManagedServer>> findAll();
}

Stream<> ile uyuşuk işleme (=lazy processing) ve CompletableFuture<> ile reaktif fonksiyonel programlama (=reactive functional programming) yapılabilir. Yukarıdaki kodda Spring Data JPA kullanılmaktadır. Spring Data JPA tabanlı kalıcılık çözümünde, sadece kalıcılık işlemlerinin tanımlandığı bir -arayüz tanımlanır, gerçekleme verilmez. Tüm kalıcılık detayları ile Spring Data JPA çatısı ilgilenecektir. Spring Data JPA çatısı Java Persistence API (JPA) çatısı üzerine kuruludur.
Burada tartışılabilecek diğer bir çözüm, arama kriterine uyan hiç bir film bulunamadığında, bunu bir hata durumu olarak kabul edip hatayı açıklayan bir Exception nesnesi fırlatmak olabilir. Bu durumda metot kritere uyan en az bir film bulduğunda bir torba döner ya da sorgu sonucu boş ise Exception nesnesi fırlatır. Her iki çözüm de herhangi bir önlem alınmış olunsun ya da olunmasın NPE'ye neden olmaz. Bu çözümden hangisinin tercih edileceği işin sahibinin vermesi gereken bir karardır. Eğer genel bir API tasarlanıyor ise API'nin her iki çözümü de içermesi tercih edilir. Kararı bırakın çağıran taraf kendi ihtiyacına göre versin.

Nesneyi Optional ile Sarmallamak

Java 8 ile birlikte üçüncü bir çözüm daha geldi: Optional. Optional sınıfı bir nesneyi saklama ve erişmek için bir çözüm sunuyor. Eğer saklayacağı nesne null olabiliyor ise Optional.ofNullable metodu ile yaratıyoruz:
@Override
public Optional<Country> findCountryByCode(String code) {
 Country country = countries.get(code);
 return Optional.ofNullable(country);
}

Eğer nesnenin null olmaması gerekiyorsa değeri Optional.of() metodu ile sarmallıyoruz:
Optional<Double> average= Optional.of(Double.NaN);

Optional.of() metodu null kontrolü yapar ve parametre null ise NPE fırlatır:
public final class Optional<T> {

   . . .

   public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
   }

    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    . . .

}

 
public final class Objects {

    . . .

   public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }   

    . . .

}

Eğer sarmallanacak değer null ise bunu doğrudan Optional.empty() çağrısı ile oluşturabiliriz:
Optional<Double> average= Optional.empty(); 

Optional ile Nesneye Ulaşmak

Optional içinde sakladığımız nesneye ulaşmak için yukarıda tartıştığımız şekillerde tasarlanmış farklı davranışlara sahip metotlar bulunuyor. get() metodu ile Optional'da saklanan nesnenin referansına ulaşırsınız. Ancak Optional içinde nesne yoksa NoSuchElementException() alırsınız:
public final class Optional<T> {

    . . .

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    . . .

}

NoSuchElementException yine bir RuntimeException türünde bir hatadır. Collection API içindeki Iterator arayüzündeki next() metodu da eğer torbada ziyaret edilecek başka bir eleman yoksa NoSuchElementException fırlatır. Bu hatayı almamak için Optional içinde bir değer olup olmadığını sorgulayabilirsiniz:
Optional<Country> country= countryDao.findCountryByCode("KLM");
if (country.isPresent())
    System.out.println(country.get().getName()); 

Optional içinde yukarıdaki çözümü daha yalın bir şekilde karşılayacak  başka bir metot yer alıyor:
Consumer<Country> countryNameConsumer = c -> System.out.println(c.getName());
country.ifPresent(countryNameConsumer);

Yukarıdaki çözümü daha da yalın ifade edebiliriz:
country.map(Country::getName).ifPresent(System.out::println);

Eğer Optional içinde değer yoksa alternatif bir değerin kullanılmasını isteyebiliriz:
String countryName= country.map(Country::getName).orElse("Not found!");

Şimdi orElse() metodunun Optional sınıfındaki tanımına bakalım:
public final class Optional<T> {
    . . .
    public T orElse(T other) {
        return value != null ? value : other;
    }
    . . .
}

Eğer aradığımız kodda bir ülke yoksa yeni bir ülke yaratmak için Optional sınıfındaki orElseGet() çağrısını kullanıyoruz:
CountryDao countryDao= InMemoryWorldDao.getInstance();
Optional<Country> country= countryDao.findCountryByCode("KLM");
Country found= country.orElseGet(Country::new);

Eğer aradığımız kodda bir ülke yoksa Exception fırlatmak istersek Optional sınıfındaki orElseThrow() çağrısını kullanabilirsiniz:
CountryDao countryDao= InMemoryWorldDao.getInstance();
Optional<Country> country= countryDao.findCountryByCode("KLM");
Country existing= country.orElseThrow(NoSuchElementException::new);

Son olarak Optional içindeki fonksiyonel programlamaya yönelik flatMap() metodunu çalışacağız:
CountryDao countryDao= InMemoryWorldDao.getInstance();
Optional<Country> country= countryDao.findCountryByCode("TUR");
country.flatMap(Country::getCities).ifPresent(System.out::println);

Yukarıdaki örnekte ülke kodu ile aradığımız ülkenin şehirlerini listelemek istiyoruz. Yukarıdaki kodu anlamak için Country sınıfındaki getCities() metodunun tanımına göz atmamız gerekir:
public class Country {

     . . .

     public Optional<List<City>> getCities() {
         return Optional.ofNullable(cities);
     }

     . . .

}

Dikkat ederseniz getCities() metodu Optional<List<City>> tipinden bir değer dönüyor. Böylelikle flatMap() metodu Optional<Country>'yi Optional<List<City>>'ye dönüştürüyor.

Modellemede Optional Kullanımı

Nesneye dayalı tasarımda Optional<> ile neleri sarmallamalıyız? Sınıf özniteliklerini (=attributes, fields) ve metod parametrelerini olduğu gibi bırakın, Optional<> içinde sarmallamaya çalışmayın. Optional sınıfının en doğru kullanımı, yukarıdaki örneklerde olduğu gibi metod dönüş değerleri olacaktır. Çağıran tarafta ise Optional ile sarmallanan değerlere get() yerine orElse() ile ulaşmaya çalışın.

Java 9'da Optional'daki Yenilikler

Önümüzdeki yıl Mart ayında Java SE'nin yeni sürümü 9'un çıkması planlanıyor. Java 9Optional sınıfına yeni üç metot tanıştırıyor. Şimdi bu yenilikleri örneklerle çalışalım.  TUR, USA, ITA ve FRA kodlu ülkelerin bilgilerini ekrana yazmaya çalışalım. Java 9 öncesinde bunu aşağıdaki kodla gerçekleştirebiliriz:
Stream.of("TUR","USA","ITA","FRA")
        .map(countryDao::findCountryByCode)
        .map(c -> c.isPresent() ? Stream.of(c.get()) : Stream.empty())
        .forEach(System.out::println);

Java 9'daki ilk yenilik yukarıdaki c -> c.isPresent() ? Stream.of(c.get()) : Stream.empty() ifadesinin kısa yolunu oluşturan stream() çağrısı:
Stream.of("TUR","USA","ITA","FA")
        .map(countryDao::findCountryByCode)
        .map(Optional::stream)
        .forEach(System.out::println);

Gerçekten de Optional sınıfında stream çağrısının gerçeklemesine baktığımızda bu kısa yolu görüyoruz:
/**
 * If a value is present return a sequential {@link Stream} containing only
 * that value, otherwise return an empty {@code Stream}.
 *
 * @apiNote This method can be used to transform a {@code Stream} of
 * optional elements to a {@code Stream} of present value elements:
 *
 * <pre>{@code
 *     Stream<Optional<T>> os = ..
 *     Stream<T> s = os.flatMap(Optional::stream)
 * }</pre>
 *
 * @return the optional value as a {@code Stream}
 * @since 1.9
 */
public Stream<T> stream() {
 if (!isPresent()) {
  return Stream.empty();
 } else {
  return Stream.of(value);
 }
}

Diğer yeni gelen metot ise ifPresentOrElse(). Aşağıdaki örnekte hatalı ülke kodlarını saymak, hatasız ülke kodlarına karşı gelen ülke bilgilerini ise ekrana yazmak istiyoruz:
AtomicInteger counter= new AtomicInteger(0);
Stream.of("TUR","USA","ITA","FA")
      .map(countryDao::findCountryByCode)
      .forEach(
           c -> c.ifPresentOrElse(
                   System.out::println,
                   () -> { counter.incrementAndGet(); } 
                )
       );
System.out.println(counter.get());

Bu uygulama çalıştırıldığında aşağıdaki ekran ekran görüntüsü ile karşılaşılır:
Country{gnp=210721.0, code='TUR', name='Turkey', continent='Asia', surfaceArea=774815.0, population=66591000}
Country{gnp=8510700.0, code='USA', name='United States', continent='North America', surfaceArea=9363520.0, population=278357000}
Country{gnp=1161755.0, code='ITA', name='Italy', continent='Europe', surfaceArea=301316.0, population=57680000}
1

Sonuncu ve daha çok ihtiyaç duyulan metot ise or(). Şimdi, TUR kodlu ülkeyi önce cep (=cache) servisinde , orada yoksa InMemoryWorldDao servisinde arayalım, orada da yoksa en son JdbcWorldDao servisinde arayalım:
Optional<Country> turkey= cache.findCountryByCode("TUR")
                                .or(() -> countryDao.findCountryByCode("TUR"))
                                .or(() -> jdbcDao.findCountryByCode("TUR"));

Wednesday, September 24, 2014

Java 8'de Torbalar Üzerinde "Paralel Yapılar" Kurulması

   
Verilerin işlenebilmesi için bellekte organize edilmeleri gerekir. Bunun için çeşitli organizasyon biçimleri bulunuyor: Dizi, Bağlantılı Liste, Yığın, Kuyruk. Java'da bu organizasyon biçimlerinin çok daha fazlası Collection API içinde bulunur ve uygulama geliştirilirken pek çoğu kullanılır. Torba seçimi, veriye nasıl ve hangi amaçla erişileceğine bağlıdır. Eğer çoğu zaman rastgele ve daha çok okumak amacıyla, konumuna göre rasgele erişiliyorsa ArrayList, ama rasgele silme/ekleme yapılacak ve daha çok ardışık olarak erişilecekse LinkedList kullanılmalıdır. Eğer çift değerlere izin verilmiyorsa, Set arayüzüne sahip bir torba kullanılmalıdır. Bu noktada, ekleme sırası önemliyse, LinkedHashSet’in, her zaman sıralı erişilecekse TreeSet’in ve daha çok verilerin torbada var olup olmadığı sorgulanacaksa HashSet’in kullanımı uygulama başarımı için tercih edilir. Eğer veriler (Anahtar ve Değer) ikililerinden oluşuyorsa Map arayüzüne sahip bir torbanın kullanılması gerekir. Map verilere anahtar üzerinden erişilmesini sağlar, verilen anahtar için o anahtar değerli değer döndürülür. Eğer anahtarların eklenme sırası önemliyse, LinkedHashMap, her zaman sıralı erişilecekse TreeMap ve daha çok verilerin torbada var olup olmadığı sorgulanacaksa HashMap tercih edilir.  Collection API içinde bunlar dışında pek çok sayıda daha torba bulunur. Java geliştiricilerinden torbaların detaylarını iyi derecede biliyor olması beklenir.
   Torba içindeki verilere ardışık olarak erişim için iki yöntem bulunmaktadır:
1. for-each Kullanımı
for(:) gösterimi Java programlama diline Java SE 5 ile gelmiştir:
List<City> cities;
for (City city: cities){
     System.out.println(city.getName());
}
Burada city değişkeni sırayla cities torbasındaki tüm nesneleri ziyaret eder. for-each gösterimi salt okunurdur, gözlerini ziyaret ettiği torba üzerinde herhangi bir değişikliğe neden olamaz. Torbadaki tüm elemanlar taranır, eleman atlamak mümkün değildir. Eğer torbada değişiklik yapılacak ise Iterator<T> kullanılmalıdır.
2. Iterator<T> Kullanımı
List<City> cities;
Iterator<City> iter= cities.iterator();
while (iter.hasNext()){
    City city= iter.next();
    System.out.println(city.getName());       
}            
   Torbalar veriye erişim için List<T>, Set<T>, SortedSet<T>, Map<K,V> gibi arayüzler üzerinden iyi bir soyutlama sunar. Hangi torba kullanılırsa kullanılsın veriye aynı şekilde erişilir: eleman eklemek için add()/put() metodu, silmek için ise remove() metodu çağırılır. Bazı işlemler, torbadaki tüm verilere erişimi gerektirir. Örneğin, torbadaki en büyük ve en küçük değerlerin bulunması ya da ortalama hesabı gibi işlemler için torbadaki tüm verilere erişmek gerekir. Eğer bu işlem, yukarıda (1)’de ve (2)’de tanıtılan for-each ve Iterator<T> kullanılarak gerçekleştirilirse, seri olarak yürütülür. (1) ve (2) ile gerçekleştirilen döngü, döngü kurma işlemi torbanın dışında gerçekleştiği için dış döngü olarak adlandırılmaktadır. Dış döngünün bazı yitimleri vardır. En önemli yitim, çok şekilli (=polymorphic) çözümler kuramıyor olmamızdır. Seri çözümde işlem süresi, özellikle çok elemanlı torbalarda uzar. Daha hızlı sonuç almak için paralel bir çözüm tasarlamak gerekir. Java'da Callable iplikler ve ExecutorService kullanarak ve torbadaki verileri bu ipliklere (=Thread) dağıtarak paralel programlama yapmak mümkündür. Ancak yarış durumu yaratmadan ve ölümcül kilitlenme oluşturmadan yüksek başarımlı bir çözüme ulaşmak her zaman kolay değildir. Üstelik bu çözümün testini yapmak oldukça zordur. Java 8, torbalar üzerinde seri ya da paralel olarak gerçekleştirilecek işlemler için kodlama ve test süresini çok kısaltacak yenilikler sunuyor. Bu yeniliklerin bir kısmı dil seviyesinde, bir kısmı API seviyesinde ve bir kısmı da Java Sanal Makinası seviyesindedir.
   Java 8 ile birlikte yeni bir anlayışa (=Paradigm) kavuştuk: Fonksiyonel programlama. Java’da bir fonksiyona parametre olarak bir değer geçilebilir. Bu değer bir temel tipten olabileceği gibi bir nesne referansı da olabilir:
List<City> findAllCitiesByCountry(Country country){
 . . .
}
Yukarıdaki örnekte findAllCitiesByCountry fonksiyonu Country tipinden bir nesnenin referansını parametre olarak almaktadır. Benzer şekilde List<City> tipinden de bir değer dönmektedir. Fonksiyonel programlama ile birlikte fonksiyonlar parametre olarak, değer dışında fonksiyon da alabilirler veya fonksiyon da döndürebilirler. Bu şekilde parametre olarak fonksiyon alan ya da fonksiyon döndüren fonksiyonları yüksek dereceden fonksiyon (high-order functions,  bazen functional ya da functor) olarak adlandırıyoruz.
   Java 8’de torbalar üzerinde iç döngü oluşturabiliriz:
List < City > allCities = worldDao.findAllCities();
allCities.forEach(
  new Consumer < City > () {
      @Override
      public void accept(City city) {
        System.out.println(city.getName());
      }
  }
);
Dikkat edilirse Collection arayüzünde artık yeni bir metod yer alıyor: forEach(). forEach() ile iç döngü oluşturabilir. Fonksiyona parametre olarak Consumer<City> tipinden anonim sınıftan bir nesne gönderdik. forEach torbadaki her bir nesne için parametre olarak verdiğimiz anonim nesnenin accept() fonksiyonunu çağırır. Şimdi önce Consumer<T> arayüzünün tanımına bir bakalım:
@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
Bu tanımda bizim için Java 8 ile birlikte gelen iki önemli yenilik yer alıyor:
1.  Fonksiyonel Arayüz (=Functional Interface)
Bir arayüz içinde sadece tek bir fonksiyon bulunuyorsa bu tür arayüzler fonksiyonel arayüz olarak isimlendirilir. Java 8’de bu tür arayüzleri betimlemek üzere @FunctionaInterface isimli damga kullanılır. Bu tür arayüzlere örnek olarak Runnable, Callable, Comparator, Comparable arayüzleri verilebilir. @FunctionaInterface damgasının tanımı ise aşağıda verilmiştir:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
2    2. default tanımlı varsayılan metod
default anahtar kelimesi yeni değil, ancak buradaki kullanımı yeni. Java 8’de API seviyesinde yenilik yapılırken aynı zamanda geriye doğru uyumluluğu da sağlayabilmek için arayüzlerde gövdesi tanımlanan somut fonksiyon yazılabilmesine olanak sağlanmıştır. Arayüzler, hizmet alan ve hizmet veren sınıf arasında birer sözleşme olarak düşünebilir. Sözleşmeye yeni bir metod eklendiğinde kod kırılacaktır. Varsayılan metodlar, arayüze yeni bir metod eklenirken, kodun kırılmasını engeller. Arayüzü gerçekleyen sınıf, eklenen yeni metoda işlev yüklememiş ise varsayılan metod tanımı kullanılır. Ancak bunun C++’dan bildiğimiz yan etkileri bulunmaktadır. Bu nedenle, varsayılan metodların yukarıda anlatılan amaca yönelik olarak kullanımına dikkat edilmelidir.
Fonksiyonel programlamada kısa süreli kullanıma sahip anonim fonksiyonlar l ifadesi olarak adlandırılır. Java 8’da l ifadesi oluşturulabilir. Yukarıda torba üzerinde oluşturduğumuz iç döngüyü l ifadesi olarak da yazabiliriz:
List<City> allCities=worldDao.findAllCities();
allCities.forEach( 
     (final City city) -> {      
        System.out.println(city); 
     }
);

Burada (final City city) ile l ifadesinin parametre listesini veriyoruz. l ifadesinin gövdesini ise metod gövdelerini tanımlarken kullandığımız kıvırcık parantezlerden yararlanıyoruz. Her l ifadesi mutlaka bir fonksiyonel arayüze bağlıdır, bu arayüz üzerinden tanımlanır. Ancak bizim doğrudan arayüzü belirtmemize gerek yoktur. Bu örnekte fonksiyonel arayüz Consumer<T> arayüzüdür. l ifadesinin gövdesine ise fonksiyonel arayüzün biricik metodu üzerinden erişilecektir. Fonksiyonel programlama dillerinde l ifadelerinin uygulamanın durumunu değiştirmezler. for-each ile oluşturulan dış döngüde olduğu gibi torbada değişikliğe neden olamazlar. Yukarıdaki l ifadesinde parametrenin final tanımlanmasındaki amaç budur. Ancak Java 8’deki  l ifadelerinde final kullanımı zorunlu değildir.  
Java 7 ile birlikte derleyici daha akıllı davranmaya başladı. Burada l ifadesini tanımlarken parametrenin tipini söylememize gerek yok, derleyici çıkarsama yapabilir:
List<City> allCities=worldDao.findAllCities();
allCities.forEach( 
     (city) -> {      
        System.out.println(city); 
     }
);
Ancak burada tipi çıkarsama işini derleyiciye bıraktığımızda, artık parametre final tanımlı değildir.
Eğer l ifadesi tek bir parametre alıyor ise parantez çiftini de kullanmaya gerek yoktur:
List<City> allCities=worldDao.findAllCities();
allCities.forEach( 
     city -> {      
        System.out.println(city); 
     }
);
Eğer l ifadesinin gövdesi tek bir ifadeden oluşuyor ise kıvırcık parantez çifti kullanılmayabilir:
List<City> allCities=worldDao.findAllCities();
allCities.forEach( city -> System.out.println(city) );
Eğer l ifadesi sadece bir metod çağrısından oluşuyorsa Java 8 ile birlikte gelen metod tutacağı ile ifade edilebilir:
allCities.forEach( System.out::println );
l ifadeleri ve metod tutacağı Java 8’de fonksiyonel programlama yapmak için temel yapı taşlarını oluşturur. 
Java 8 ile birlikte yukarıda tanıtıldığı gibi iç döngüler oluşturabiliyoruz. Bunu sağlayan Stream API’dır ve bunun ilk uygulamasını da Collection API’de görüyoruz. Ancak Collection API’deki kullanımına geçmeden önce Stream API’nin ne işe yaradığını gösterir bir örnek inceleyelim:
int lost[]={42,23,16,15,8,4};
IntStream.of(lost).sorted().forEach(System.out::println);
OptionalDouble average= IntStream.of(lost).average();
average.ifPresent(System.out::println);
System.out.println("Sum: "+IntStream.of(lost).sum());
Yukarıdaki kodu çalıştırdığımızda ekranda aşağıdaki çıktıyı elde ediyoruz:
4
8
15
16
23
42
18.0
Sum: 108
Stream bir iş hattı gibi düşünülebilir. Yukarıdaki kodda kullanılan IntStream sınıfı ise özel bir Stream sınıfı: iş hattından sadece tam sayılar akıyor. Bu tam sayılar akarken yapılacak işleri bir metod zinciri olarak verebiliyoruz: sorted().forEach(System.out::println). Önce sıralamasını ve sonra ekrana yazmasını istiyoruz. IntStream.of(lost).average() satırında ise akan sayıların ortalamasını hesaplamak istiyoruz. average metodu doğrudan double bir değer dönmek yerine OptionalDouble sınıfından bir değer dönüyor. Java 8’de ile gelen Optional<T> sınıflarının amacı NullPointerException oluşmasını engellemektir. average.ifPresent(System.out::println) ifadesi hiçbir zaman NullPointerException’a neden olmaz.
Şimdi torbalar üzerinde Stream API’yi kullanımını çeşitli örnekler üzerinden çalışalım.
1.  Elimizde filmlerin olduğu bir liste var ve bu liste içinden 70’li yıllara ait Drama türünde filmlere ulaşmak istiyoruz:
MovieService movieService= InMemoryMovieService.getInstance();
Collection<Movie> allMovies= movieService.findAllMovies();
Predicate<Movie> _70s= m -> m.getYear()>=1970 && m.getYear()<1980;
Predicate<Movie> drama= 
    movie -> movie.getGenres().stream()
             .filter(genre -> genre.getName().equals("Drama"))
             .count()==1;
 
// 70’li yıllara ait drama türünde filmlerin listesi
allMovies.stream().filter(_70s.and(drama)).forEach(System.err::println);
// 70’li yıllara ait sadece drama türünden filmlerin listesi
Predicate<Movie> drama_only= 
   movie -> movie.getGenres().size()==1 
   &&   
   movie.getGenres().get(0).getName().equals("Drama");
allMovies.stream().filter(_70s.and(drama_only))
                  .forEach(System.err::println);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
70's movies whose genre is Drama
Movie [title=Dog Day Afternoon, year=1975]
Movie [title=Network, year=1976]
Movie [title=The Little Girl Who Lives Down the Lane, year=1976]
Movie [title=Der amerikanische Freund, year=1977]
Movie [title=The Last Wave, year=1977]
70's movies whose genre is only Drama
Movie [title=Network, year=1976]

2. Filmleri yıllara göre ve daha sonra aynı yıllara ait filmleri kendi içinde sözlük sırasına göre listelemek istiyoruz:
MovieService movieService= InMemoryMovieService.getInstance();
Collection<Movie> allMovies= movieService.findAllMovies();
Comparator<Movie> byYear= (left,right) -> left.getYear() - right.getYear() ;
Comparator<Movie> byTitle= 
             (left,right) -> left.getTitle().compareTo(right.getTitle()) ;
allMovies.stream().sorted(byYear.thenComparing(byTitle))
                  .forEach(System.err::println);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Movie [title=The Return of Frank James, year=1940]
Movie [title=Double Indemnity, year=1944]
Movie [title=The Treasure of the Sierra Madre, year=1948]
Movie [title=Sunset Blvd., year=1950]
Movie [title=A Streetcar Named Desire, year=1951]
Movie [title=Stalag 17, year=1953]
Movie [title=Dial M for Murder, year=1954]
Movie [title=Shichinin no samurai, year=1954]
Movie [title=Them!, year=1954]
Movie [title=Vertigo, year=1958]
. . .
Movie [title=The Book of Eli, year=2010]
Movie [title=The Bounty Hunter, year=2010]
Movie [title=Unthinkable, year=2010]
Movie [title=Veda, year=2010]
Movie [title=Yip Man 2: Chung si chuen kei, year=2010]
Movie [title=You Dont Know Jack, year=2010]
3. Birden fazla yönetmeni olan filmleri listelemek istiyoruz:
Consumer<Movie> printNamesAndNumberOfDirectors= 
 movie -> System.err.println(String.format("Title: %-36s # of directors: %d", 
          movie.getTitle(),
          movie.getDirectors().size())) ;
Predicate<Movie> multiDirectedMovies= 
       movie -> movie.getDirectors().size()>1;
allMovies.stream().filter(multiDirectedMovies)
                  .forEach(printNamesAndNumberOfDirectors);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Title: Gamer                                # of directors: 2
Title: A Serious Man                        # of directors: 2
Title: Rembetiko                            # of directors: 2
Title: New York, I Love You                 # of directors: 2
Title: Daybreakers                          # of directors: 2
Title: The Imaginarium of Doctor Parnassus  # of directors: 2
Title: Cloudy with a Chance of Meatballs    # of directors: 2
Title: The Princess and the Frog            # of directors: 2
Title: Karamazovi                           # of directors: 2
Title: The Missing Person                   # of directors: 2
Title: Sonbahar                             # of directors: 2
Title: I Love You Phillip Morris            # of directors: 2
Title: The Book of Eli                      # of directors: 2
4.  Birden fazla yönetmeni olan filmlerin sadece yönetmenlerinin adlarını listelemek istiyoruz:
Consumer<Director> printDirectorName= 
       director -> System.err.println(director.getName()); 
Consumer<List<Director>> printDirectorNames= 
       directors -> directors.stream().forEach(printDirectorName); 
Function<Movie,List<Director>> directorMapper= 
       movie -> movie.getDirectors() ;
allMovies.stream().filter(multiDirectedMovies).map(directorMapper).forEach(printDirectorNames);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Mark Neveldine
Brian Taylor
Ethan Coen
Joel Coen
. . .
Glenn Ficarra
John Requa
Albert Hughes
Allen Hughes
5.  Elimizde tüm dünya ülkelerinin olduğu bir liste var ve kıtaların listesini ekrana sözlük sırasına göre sıralı yazmak istiyoruz:
WorldDao worldDao= InMemoryWorldDao.getInstance();
List<String> continents= 
        worldDao.findAllCountries()
                .stream()
                .map(Country::getContinent)
                .distinct()
                .sorted(String::compareTo)
                .collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
continents.forEach(System.out::println);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Africa
Antarctica
Asia
Europe
North America
Oceania
South America
6. Nüfusu en kalabalık olan ülkeyi bulmak istiyoruz:
Comparator<Country> byPopulation=
        (left,right) -> left.getPopulation()>=right.getPopulation() ? +1 : -1 ;
Optional<Country> overPopulatedCountry= 
        worldDao.findAllCountries()
                .stream()
                .max(byPopulation);
overPopulatedCountry.ifPresent(System.out::println);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Country [code=CHN, name=China, continent=Asia, surfaceArea=9572900.0, population=1277558000, . . . ] 
7. Nüfusu en kalabalık ilk 10 ülkeyi bulmak istiyoruz:
Comparator<Country> byPopulation = 
        (left, right) -> left.getPopulation() >= right.getPopulation() ? -1 : +1;
List<Country> overPopulatedCountries
        = worldDao.findAllCountries()
        .stream()
        .sorted(byPopulation)
        .limit(10)
        .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
overPopulatedCountries.forEach(System.out::println);
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Country [ name=China, population=1277558000]
Country [ name=India, population=1013662000]
Country [ name=United States, population=278357000]
Country [ name=Indonesia, population=212107000]
Country [ name=Brazil, population=170115000]
Country [ name=Pakistan, population=156483000]
Country [ name=Russian Federation, population=146934000]
Country [ name=Bangladesh, population=129155000]
Country [ name=Japan, population=126714000]
Country [ name=Nigeria, population=111506000]

8. Ülkeleri kıtalara göre gruplamak ve her kıtada kaç ülke olduğunu ekrana yazmak istiyoruz:
WorldDao worldDao = InMemoryWorldDao.getInstance();
Map<String,List<Country>> countriesByContinent= worldDao.findAllCountries()
        .stream()
        .collect(Collectors.groupingBy(Country::getContinent));
countriesByContinent.forEach( (key , value) ->  
System.out.println(key + ": " + value.size()));
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
South America: 14
Asia: 51
Europe: 46
Africa: 58
Antarctica: 5
North America: 37
Oceania: 28
9. Film türlerinin her birinden kaç film olduğunu ekrana yazdırmak istiyoruz:
public static void main(String[] args) {
    MovieService movieService = InMemoryMovieService.getInstance();
    Collection<Movie> allMovies = movieService.findAllMovies();
    Map<String, List<Movie>> moviesByGenre
            = allMovies.stream()
            .flatMap(movie -> movie.getGenres()
                    .stream()
                    .map(genre -> pair(genre.getName(), movie)))
            .collect(groupingBy(Entry::getKey,mapping(Entry::getValue,toList())));
    moviesByGenre.forEach( (key,value) -> System.out.println(key+": "+value.size() ));
}

private static <T, U> AbstractMap.SimpleEntry<T, U> pair(T t, U u) {
    return new AbstractMap.SimpleEntry<T, U>(t, u);
}
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki gibi gerçekleşmektedir:
Film-Noir: 2
Action: 36
Adventure: 26
Horror: 19
War: 18
Romance: 56
History: 12
Western: 7
Documentary: 1
Sport: 3
Sci-Fi: 15
Drama: 180
Thriller: 70
Music: 7
Crime: 41
Fantasy: 14
Biography: 20
Family: 12
Animation: 9
Mystery: 33
Comedy: 60
Musical: 2
Yukarıdaki tüm çözümleri kolaylıkla paralelleştirebiliriz. İlk olarak yapılması gereken paralel Stream yaratmaktır. Bunun için Collection arayüzündeki parallelStream() çağrısı kullanılır:
Comparator<Country> byPopulation
        = (left, right) -> left.getPopulation() >= right.getPopulation() ? -1 : +1;
List<Country> overPopulatedCountries
        = worldDao.findAllCountries()
        .parallelStream()
.sorted(byPopulation)
        .limit(10)
        .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Burada dikkat edilmesi gerek en önemli nokta collect çağrısının üçüncü parametresidir. Paralel Stream kullanıldığı durumda çekirdek sayısı kadar iş hattı yaratılır ve her bir çekirdek torbada kendisine düşen kısımda çalışır ve sonuçlar en son birleştirilir. Birleştirilirken ArrayList::addAll metodu kullanılır. Bu teker teker eklemeye göre daha yüksek başarım verir.
Eğer torba üzerinde kümeleme işlemi yapılıyorsa, bu durumda kümeleme işleminin paralel olan versiyonu kullanılmalıdır. Örneğin, dokuzuncu problemde kümeleme olarak Collectors.groupingBy() metodu kullanılmıştır. Aynı problem, paralel olarak çözülmek istenirse, groupingBy metodu yerine groupingByConcurrent() metodu kullanılmalıdır:

private static <T, U> AbstractMap.SimpleEntry<T, U> pair(T t, U u) {
    return new AbstractMap.SimpleEntry<T, U>(t, u);
}

public static void main(String[] args) {
    MovieService movieService = InMemoryMovieService.getInstance();
    Collection<Movie> allMovies = movieService.findAllMovies();
    Map<String, List<Movie>> moviesByGenre
            = allMovies.parallelStream()
            .flatMap(movie -> movie.getGenres()
                             .stream().map(genre -> pair(genre.getName(), movie)))
            .collect(groupingByConcurrent(Entry::getKey, 
mapping(Entry::getValue, toList())));
    moviesByGenre.forEach((key, value) -> System.out.println(key + ": " + value.size()));
}

Eğer Eclipse Luna SR1 geliştirme aracını kullanıyorsanız, 8 ve 9 ile verilen kod için derleme hatası verecektir. Eclipse kendi derleyicisini kullanır: Eclipse Compiler for Java (ECJ). Ne yazık ki javac'ın derlediği kod için ECJ hata veriyor:
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
Type mismatch: cannot convert from Map<Object,List<Object>> to Map<String,List<Movie>>
 The type Map.Entry does not define getKey(Object) that is applicable here
 The type Map.Entry does not define getValue(Object) that is applicable here
Bu hatanın Eclipse'in ilerleyen sürümlerinde düzeltilmesini umuyoruz. Kodun tamamına bu bağlantıdan NetBeans projesi olarak erişebilirsiniz.
Java 8 ile ilgili daha yoğun ve detay bilgi edinmek isterseniz bu bağlantıdaki eğitimleri incelemenizi öneririm. 
View Binnur Kurt's profile on LinkedIn