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()); }
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:
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()); } } );
@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); }; } }
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:
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);
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);
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:
Bu hatanın Eclipse'in ilerleyen sürümlerinde düzeltilmesini umuyoruz. Kodun tamamına bu bağlantıdan NetBeans projesi olarak erişebilirsiniz.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
Java 8 ile ilgili daha yoğun ve detay bilgi edinmek isterseniz bu bağlantıdaki eğitimleri incelemenizi öneririm.