Showing posts with label Java 9. Show all posts
Showing posts with label Java 9. Show all posts

Saturday, October 21, 2017

Exercises to study Java Stream API


In this blog post, we will study Stream API of Java 9 with several exercise questions. In these exercises, we will use two different domains: 
  • IMDB Movies
  • World Countries
Before we dive into the exercise questions, let's take a look at these domains:

IMDB Movies: We have three domain classes: Movie, Director and Genre. Each movie has a title, a year and a unique id assigned by Internet Movie DataBase (IMDB). Each movie has been directed by at least one director, and each director has many movies. Each movie belongs to at least one genre.

package com.example.domain;

import java.util.ArrayList;
import java.util.List;

public class Movie {
private int id;
   private String title;
   private int year;
   private String imdb;
   private List<Genre> genres;
   private List<Director> directors;

   {
      genres = new ArrayList<>();
      directors = new ArrayList<>();
   }

   public Movie() {
   }

   public Movie(int id, String title, int year, String imdb) {
      this.id = id;
      this.title = title;
      this.year = year;
      this.imdb = imdb;
   }

   // getters and setters

   @Override   
   public String toString() {
      return "Movie [title=" + title + ", year=" + year + "]";
   }
}


package com.example.domain;
import java.util.ArrayList;
import java.util.List;

public class Director {
   private int id;
   private String name;
   private String imdb;
   private List<Movie> movies= new ArrayList<>();

   public Director() {
   }

   public Director(int id, String name, String imdb) {
      this.id = id;
      this.name = name;
      this.imdb = imdb;
   }

   // getters and setters
   @Override   
   public String toString() {
      return "Director [id=" + id + ", name=" + name + ", imdb=" + imdb + "]";
   }
}

package com.example.domain;
public class Genre {
   private int id;
   private String name;

   public Genre() {
   }

   public Genre(int id, String name) {
      this.id = id;
      this.name = name;
   }

   // getters and setters
@Override
   public String toString() {
      return "Genre [id=" + id + ", name=" + name + "]";
   }

}

World Countries: There are two domain classes: Country and City. Each city belongs to a country defined by the attribute, countryCode. Each country has a unique code and has many cities.

package com.example.domain;

import java.util.ArrayList;
import java.util.List;

public class Country {
   private String code;
   private String name;
   private String continent;
   private double surfaceArea;
   private int population;
   private double gnp;
   private int capital;
   private List<City> cities;
   {
      cities = new ArrayList<>();
   }

   public Country() {
   }

   public Country(String code, String name, String continent, int population,
         double surfaceArea, double gnp, int capital) {
      this.code = code;
      this.name = name;
      this.continent = continent;
      this.surfaceArea = surfaceArea;
      this.population = population;
      this.capital = capital;
      this.gnp = gnp;
   }

   // getters and setters
   @Override   
   public String toString() {
      return "Country [ name=" + name + ", population=" + population + "]";
   }

}

package com.example.domain;

public class City {
   private int id;
   private String name;
   private int population;
   private String countryCode;

   public City() {
   }

   public City(int id, String name, String countryCode, int population) {
      this.id = id;
      this.name = name;
      this.population = population;
      this.countryCode = countryCode;
   }

   // getters and setters
   
   @Override   
   public String toString() {
      return "City [id=" + id + ", name=" + name + ", population=" + population + ", countryCode=" + countryCode + "]";
   };

}

EXERCISE #1

Find the highest populated city of each country:

package com.example.exercise;

import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import com.example.dao.CountryDao;
import com.example.dao.InMemoryWorldDao;
import com.example.domain.City;

public class Exercise1 {

   public static void main(String[] args) {
      CountryDao countryDao= InMemoryWorldDao.getInstance();
      List<City> highPopulatedCitiesOfCountries = countryDao.findAllCountries()
                .stream()
                .map( country -> country.getCities().stream().max(Comparator.comparing(City::getPopulation)))
                .filter(Optional::isPresent)
                .map(Optional::get)
                  .collect(Collectors.toList());
      highPopulatedCitiesOfCountries.forEach(System.out::println);
   }

}

The code given above will produce the following output:

City [id=3494, name=Auckland, population=381800, countryCode=NZL]
City [id=764, name=Suva, population=77366, countryCode=FJI]
City [id=2884, name=Port Moresby, population=247000, countryCode=PNG]
City [id=918, name=Les Abymes, population=62947, countryCode=GLP]
.
.
.
City [id=4067, name=Charlotte Amalie, population=13000, countryCode=VIR]
City [id=712, name=Cape Town, population=2352121, countryCode=ZAF]
City [id=538, name=Bandar Seri Begawan, population=21484, countryCode=BRN]
City [id=933, name=Tegucigalpa, population=813900, countryCode=HND]

EXERCISE #2

Find the most populated city of each continent:

package com.example.exercise;

import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import com.example.dao.CountryDao;
import com.example.dao.InMemoryWorldDao;
import com.example.domain.City;

public class Exercise2 {

    public static void main(String[] args) {
        CountryDao countryDao = InMemoryWorldDao.getInstance();
        final Predicate<Entry<String, Optional<City>>> isPresent = entry -> entry.getValue().isPresent();
        final BiConsumer<String, Optional<City>> printEntry =
                (k,v) -> {
                    City city = v.get();
                    System.out.println(k + ": City [ name= " + city.getName() + ", population= " + city.getPopulation() + " ]");
                };
        Collector<City, ?, Map<String, Optional<City>>> groupingHighPopulatedCitiesByContinent = Collectors.groupingBy(city -> countryDao.findCountryByCode(city.getCountryCode()).getContinent(), Collectors.maxBy(Comparator.comparing(City::getPopulation)));
        Map<String, Optional<City>> highPopulatedCitiesByContinent = countryDao.findAllCountries()
                .stream()
                .flatMap(country -> country.getCities().stream())
                .collect(groupingHighPopulatedCitiesByContinent);
        highPopulatedCitiesByContinent.forEach(printEntry);

    }

}

The code given above will produce the following output:

South America: City [ name= SÆo Paulo, population= 9968485 ]
Asia: City [ name= Mumbai (Bombay), population= 10500000 ]
Europe: City [ name= Moscow, population= 8389200 ]
Africa: City [ name= Cairo, population= 6789479 ]
North America: City [ name= Ciudad de M‚xico, population= 8591309 ]
Oceania: City [ name= Sydney, population= 3276207 ]


EXERCISE #3

Find the number of movies of each director: Try to solve this problem by assuming that Director class has not the member movies

package com.example.exercise;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.example.domain.Director;
import com.example.domain.Movie;
import com.example.service.InMemoryMovieService;
import com.example.service.MovieService;

public class Exercise3 {

    public static void main(String[] args) {
        MovieService movieService = InMemoryMovieService.getInstance();
        Collection<Movie> movies = movieService.findAllMovies();
        Map<String, Long> directorMovieCounts =
                movies.stream()
                        .map(Movie::getDirectors)
                        .flatMap(List::stream)
                        .collect(Collectors.groupingBy(Director::getName, Collectors.counting()));
        directorMovieCounts.entrySet().forEach(System.out::println);
    }

}


The code given above will produce the following output:

Sam Taylor Wood=1
F. Gary Gray=1
Oliver Hirschbiegel=1
Scott Cooper=1
Katherine Dieckmann=1
Kevin Macdonald=1
Peter Jackson=2
Andy Tennant=1
.
.
.
Isabel Coixet=1
Jason Reitman=1
Louie Psihoyos=1
Brian De Palma=1
Jay DiPietro=1

EXERCISE #4


Find the number of genres of each director's movies:

package com.example.exercise;

import java.util.Collection;
import java.util.Map;
import java.util.stream.Stream;

import com.example.domain.Director;
import com.example.domain.Genre;
import com.example.domain.Movie;
import com.example.service.InMemoryMovieService;
import com.example.service.MovieService;

import static java.util.stream.Collectors.*;

public class Exercise4 {

    public static void main(String[] args) {
        MovieService movieService = InMemoryMovieService.getInstance();
        Collection<Director> directors = movieService.findAllDirectors();
        Stream<DirectorGenre> stream =
                directors.stream()
                        .flatMap(director -> director.getMovies()
                                .stream()
                                .map(Movie::getGenres)
                                .flatMap(Collection::stream)
                                .map(genre -> new DirectorGenre(director, genre))
                                .collect(toList()).stream()
                        );
        Map<Director, Map<Genre, Long>> directorGenreList =
                stream.collect(
                        groupingBy(
                                DirectorGenre::getKey,
                                groupingBy(DirectorGenre::getValue, counting())
                        )
                );
        directorGenreList.forEach(
                (k1,v1) -> {
                    System.out.println(k1.getName());
                    v1.forEach( (k2,v2) -> {
                                System.out.println(String.format("\t%-12s: %2d", k2.getName(), v2));
                            });
                    System.out.println();
                }
        );
    }

}

class DirectorGenre implements Map.Entry<Director, Genre> {
    private Director director;
    private Genre genre;

    public DirectorGenre(Director director, Genre genre) {
        this.director = director;
        this.genre = genre;
    }

    @Override    public Director getKey() {
        return director;
    }

    @Override    public Genre getValue() {
        return genre;
    }

    @Override    public Genre setValue(Genre genre) {
        this.genre = genre;
        return genre;
    }

}

The code given above will produce the following output:

Marc Webb
 Comedy      :  1
 Drama       :  1
 Romance     :  1

Peter Hyams
 Drama       :  1
 Mystery     :  1

Mark Neveldine
 Action      :  1
 Sci-Fi      :  1
 Thriller    :  1

Brian Taylor
 Action      :  1
 Sci-Fi      :  1
 Thriller    :  1

.
.
.

Ryûhei Kitamura
 Fantasy     :  1
 Drama       :  1
 Action      :  1
 Thriller    :  1
 Adventure   :  1

Shusuke Kaneko
 Action      :  1

Gregor Jordan
 Drama       :  1
 Thriller    :  1

EXERCISE #5


Find the highest populated capital city:

package com.example.exercise;

import com.example.dao.CityDao;
import com.example.dao.CountryDao;
import com.example.dao.InMemoryWorldDao;
import com.example.domain.City;
import com.example.domain.Country;

import java.util.Objects;
import java.util.Optional;

import static java.lang.System.out;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.maxBy;

public class Exercise5 {

    public static void main(String[] args) {
        CountryDao countryDao = InMemoryWorldDao.getInstance();
        CityDao cityDao = InMemoryWorldDao.getInstance();
        Optional<City> capital = countryDao.findAllCountries()
                .stream()
                .map(Country::getCapital)
                .map(cityDao::findCityById)
                .filter(Objects::nonNull)
                .collect(maxBy(comparing(City::getPopulation)));
        capital.ifPresent(out::println);
    }

}

The code given above will produce the following output:

City [id=2331, name=Seoul, population=9981619, countryCode=KOR]

EXERCISE #6


Find the highest populated capital city of each continent:

package com.example.exercise;

import com.example.dao.CityDao;
import com.example.dao.CountryDao;
import com.example.dao.InMemoryWorldDao;
import com.example.domain.City;

import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import static java.lang.System.out;
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;

public class Exercise6 {

    public static void main(String[] args) {
        CountryDao countryDao = InMemoryWorldDao.getInstance();
        CityDao cityDao = InMemoryWorldDao.getInstance();
        Map<String, Optional<ContinentPopulatedCity>> continentsCapitals = countryDao.findAllCountries()
                .stream()
                .filter(country -> country.getCapital() > 0)
                .map(country -> new ContinentPopulatedCity(country.getContinent(), cityDao.findCityById(country.getCapital())))
                .collect(groupingBy(ContinentPopulatedCity::getKey, maxBy(comparing(cpc -> cpc.getValue().getPopulation()))));
        BiConsumer<String, Optional<ContinentPopulatedCity>> print= (k, v) -> {
            Consumer<ContinentPopulatedCity> continentPopulatedCityConsumer = cpc -> out.println(cpc.getKey() + ": " + v.get().getValue());
            v.ifPresent(continentPopulatedCityConsumer);
        };
        continentsCapitals.forEach(print);
    }

}

class ContinentPopulatedCity implements Map.Entry<String, City> {
    private String continent;
    private City city;

    public ContinentPopulatedCity(String continent, City city) {
        this.continent = continent;
        this.city = city;
    }

    @Override    public String getKey() {
        return continent;
    }

    @Override    public City getValue() {
        return city;
    }

    @Override    public City setValue(City city) {
        this.city = city;
        return city;
    }

}

The code given above will produce the following output:

South America: City [id=2890, name=Lima, population=6464693, countryCode=PER]
Asia: City [id=2331, name=Seoul, population=9981619, countryCode=KOR]
Europe: City [id=3580, name=Moscow, population=8389200, countryCode=RUS]
Africa: City [id=608, name=Cairo, population=6789479, countryCode=EGY]
North America: City [id=2515, name=Ciudad de M‚xico, population=8591309, countryCode=MEX]
Oceania: City [id=135, name=Canberra, population=322723, countryCode=AUS]

EXERCISE #7


Sort the countries by number of their cities in desending order:

package com.example.exercise;

import com.example.dao.CountryDao;
import com.example.dao.InMemoryWorldDao;
import com.example.domain.Country;

import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;

import static java.lang.String.format;
import static java.lang.System.out;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

public class Exercise7 {

    public static void main(String[] args) {
        CountryDao countryDao = InMemoryWorldDao.getInstance();
        Comparator<Country> sortByNumOfCities = comparing(country -> country.getCities().size());
        Predicate<Country> countriesHavingNoCities = country -> country.getCities().isEmpty();
        List<Country> countries = countryDao.findAllCountries()
                .stream()
                .filter(countriesHavingNoCities.negate())
                .sorted(sortByNumOfCities.reversed())
                .collect(toList());
        countries.forEach(country -> out.println(format("%38s %3d", country.getName(), country.getCities().size())));
    }

}

The code given above will produce the following output:

                               China 363
                               India 341
                       United States 274
                              Brazil 250
                               Japan 248
                  Russian Federation 189
                              Mexico 173
                                       .
                                       .
                                       .
                            Barbados   1
                              Tuvalu   1
                                Niue   1
                Virgin Islands, U.S.   1
                              Brunei   1

EXERCISE #8

Find the list of movies having the genres "Drama" and "Comedy" only:

package com.example.exercise;

import com.example.domain.Genre;
import com.example.domain.Movie;
import com.example.service.InMemoryMovieService;
import com.example.service.MovieService;

import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import static java.lang.String.format;
import static java.lang.System.out;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

public class Exercise8 {

    public static void main(String[] args) {
        MovieService movieService = InMemoryMovieService.getInstance();
        Collection<Movie> movies = movieService.findAllMovies();
        Predicate<Movie> drama = movie -> movie.getGenres().stream().anyMatch(genre -> genre.getName().equals("Drama"));
        Predicate<Movie> comedy = movie -> movie.getGenres().stream().anyMatch(genre -> genre.getName().equals("Comedy"));
        Predicate<Movie> havingTwoGenresOnly = movie -> movie.getGenres().size() == 2;
        List<Movie> dramaAndComedyMovies = movies.stream()
                .filter(havingTwoGenresOnly.and(drama.and(comedy)))
                .collect(toList());
        dramaAndComedyMovies.forEach(movie -> out.println(format("%-32s: %12s", movie.getTitle(), movie.getGenres().stream().map(Genre::getName).collect(joining(",")))));
    }

}

The code given above will produce the following output:

Away We Go                      : Comedy,Drama
A Serious Man                   : Comedy,Drama
High Life                       : Comedy,Drama
Cold Souls                      : Comedy,Drama
Worlds Greatest Dad             : Comedy,Drama
My One and Only                 : Comedy,Drama
Sunshine Cleaning               : Comedy,Drama
The Vicious Kind                : Comedy,Drama
Defendor                        : Comedy,Drama
I Love You Phillip Morris       : Comedy,Drama

EXERCISE #9

Group the movies by the year and list them:

package com.example.exercise;

import com.example.domain.Movie;
import com.example.service.InMemoryMovieService;
import com.example.service.MovieService;

import java.util.Collection;
import java.util.Map;

import static java.lang.String.format;
import static java.lang.System.out;
import static java.util.Comparator.comparing;
import static java.util.Map.*;
import static java.util.stream.Collectors.*;

public class Exercise9 {

    public static void main(String[] args) {
        MovieService movieService = InMemoryMovieService.getInstance();
        Collection<Movie> movies = movieService.findAllMovies();
        Map<Integer, String> moviesByYear = movies.stream().collect(groupingBy(Movie::getYear, mapping(Movie::getTitle, joining(","))));
        moviesByYear.entrySet().stream().sorted(comparing(Entry::getKey)).forEach(entry -> out.println(format("%4d: %s", entry.getKey(), entry.getValue())));
    }

}

The code given above will produce the following output:

1940: The Return of Frank James
1944: Double Indemnity
1948: The Treasure of the Sierra Madre
1950: Sunset Blvd.
1951: A Streetcar Named Desire
1953: Stalag 17
1954: Them!,Shichinin no samurai,Dial M for Murder
1958: Vertigo
1960: Psycho
1963: The Great Escape
1968: The Party
1969: Butch Cassidy and the Sundance Kid,Easy Rider,The Wild Brunch
1973: Le magnifique
1975: Dog Day Afternoon
1976: Network,The Little Girl Who Lives Down the Lane
1977: Der amerikanische Freund,The Last Wave
1983: Rembetiko,Danton,The Outsiders,Scarface
1987: Empire of the Sun
1988: The Accused
1989: My Left Foot: The Story of Christy Brown
1991: My Own Private Idaho
1992: Of Mice and Men
1993: Tombstone,Germinal
1994: Before the Rain,Heavenly Creatures
1996: L appartement
1997: Bacheha-Ye aseman,The Rainmaker
2000: Almost Famous
2001: Yeopgijeogin geunyeo
2002: Ice Age
2003: Bom yeoreum gaeul gyeoul geurigo bom,Oldboy,Jeux Denfants,Keulraesik,Azumi
2004: Bin-jip,Nae meorisokui jiwoogae,Samaria,Wicker Park,2046,Voditel dlya Very
2005: Hiroshima,Hwal,Just Like Heaven,Azumi 2: Death or Love
2006: Deiji,Ice Age: The Meltdown,La Sconosciuta
2007: Paranormal Activity,Before the Devil Knows You are Dead,Broken English,Ex Drummer
2008: Adam Resurrected,Nothing But the Truth,100 Feet,Nordwand,The Other Man,God on Trial,Sunshine Cleaning,Oorlogswinter,Bin-mong,Pazar - Bir Ticaret Masalı,The Hurt Locker,My Only Sunshine,Karamazovi,Ghost Town,To Verdener,Sonbahar,Yip Man,Elegy,What Doesnt Kill You,Faubourg 36
2009: 500 Days Of Summer,Beyond a Reasonable Doubt,Gamer,Cheri,Dorian Gray,Inglourious Basterds,Invictus,Julie and Julia,Los abrazos rotos,Armored,Bornova Bornova,Coco avant Chanel,Nefes: Vatan sağolsun,Up,Whiteout,The Time Travelers Wife,Whatever Works,Anonyma - Eine Frau in Berlin,Zombieland,Weather Girl,Watchmen,Angels and Deamons,Away We Go,Last Ride,The Boys Are Back,The Tournament,A Serious Man,Saw VI,Ne te retourne pas,District 9,Extract,Five Minutes of Haven,High Life,The Proposal,Veronika Decides to Die,The Goods: Live Hard, Sell Hard,The Hangover,Public Enemies,Creation,Amelia,The Rebound,Powder Blue,The Men Who Stare at Goats,Bright Star,Case 39,Cold Souls,Moon,Worlds Greatest Dad,State of Play,The Brothers Bloom,My One and Only,Man Som Hatar Kvinnor,Mary and Max,The Limits of Control,A Perfect Getaway,My Sisters Keeper,Planet 51,I Love You, Man,Amelia,The Damned United,New York, I Love You,Fish Tank,The Informant!,The Courageous Heart of Irena Sendler,Storm,Triangle,2012,The Cry of the Owl,13B,El secreto de sus ojos,Surrogates,Kimssi pyoryugi,Uzak İhtimal,Daybreakers,Cairo Time,The Cove,Tenderness,Hachiko: A Dogs Story,The Box,Everybodys Fine,Peter and Vandy,Women in Trouble,Un prophete,The Vicious Kind,Bakjwi,Up in the air,Law Abiding Citizen,Nine,The Soloist,Agora,Motherhood,Neşeli Hayat,The Greatest,The Boondock Saints II: All Saints Day,The Private Lives of Pippa Lee,The Imaginarium of Doctor Parnassus,The Men Who Stare at Goats,Cloudy with a Chance of Meatballs,The Princess and the Frog,An Education,Avatar,Avatar 3D,Precious: Based on the Novel Push by Sapphire,The Blind Side,New Moon,Fantastic Mr. Fox,Sherlock Holmes,The Road,Man som hatar kvinnor,The Collector,Leaves of Grass,Brooklyns Finest,Alice,Duplicity,Harry Brown,Defendor,Brothers,Crazy Heart,Kıskanmak,Das weisse Band - Eine deutsche Kindergeschichte,The Lovely Bones,Eastern Plays,Cargo,Glorious 39,Fifty Dead Men Walking,Grey Gardens,Vavien,Lebanon,Harry Potter and the Half-Blood Prince,Slovenka,9:06,2081,The Electric Mist,Serious Moonlight,Ice Age: Dawn of the Dinosaurs,La doppia ora,A Single Man,Cracks,The Missing Person,Nowhere Boy,Chloe,Drag Me to Hell,Eloise's Lover,Başka Dilde Aşk,Air Doll
2010: From Paris with Love,Edge of Darkness,Shutter Island,The Bounty Hunter,Dear John,Extraordinary Measures,Leap Year,Yahşi Batı,I Love You Phillip Morris,You Dont Know Jack,Yip Man 2: Chung si chuen kei,Alice in Wonderland,Romantik Komedi,Veda,Sin Nombre,The Book of Eli,Unthinkable,Shrek Forever After


EXERCISE #10

Sort the countries by their population densities in descending order ignoring zero population countries:

package com.example.exercise;

import com.example.dao.InMemoryWorldDao;
import com.example.dao.WorldDao;
import com.example.domain.Country;

import java.util.Collection;
import java.util.Comparator;
import java.util.function.Predicate;

import static java.lang.System.out;
import static java.util.Comparator.comparingDouble;

public class Exercise10 {

    public static void main(String[] args) {
        WorldDao worldDao = InMemoryWorldDao.getInstance();
        Collection<Country> countries = worldDao.findAllCountries();
        Comparator<Country> populationDensityComparator = comparingDouble(country -> country.getPopulation() / country.getSurfaceArea());
        Predicate<Country> livesNobody = country -> country.getPopulation() == 0L;
        countries.stream().filter(livesNobody.negate()).sorted(populationDensityComparator.reversed())
                .forEach(out::println);
    }

}

The code given above will produce the following output:

Country [ name=Macao, population=473000]
Country [ name=Monaco, population=34000]
Country [ name=Hong Kong, population=6782000]
Country [ name=Singapore, population=3567000]
Country [ name=Gibraltar, population=25000]
.
.
.

Country [ name=Western Sahara, population=293000]
Country [ name=Pitcairn, population=50]
Country [ name=Falkland Islands, population=2000]
Country [ name=Svalbard and Jan Mayen, population=3200]
Country [ name=Greenland, population=56000]

You can download the source of the domain through this link.

Monday, July 24, 2017

Java'da Arayüz Kullanımı


Java, Nesneye Dayalı bir programlama dilidir. Nesneye Dayalı Programlama yaklaşımının tüm gerekliliklerini yerine getirir. Java programlama dilinde sınıf tanımlayabilirsiniz. Tanımladığınız bu sınıflarda öz nitelik ve fonksiyon ekleyerek, sınıfın durağan ve devingen davranışını modelleyebilirsiniz. Sınıflar çözümümüzün temel yapı taşlarıdır. Ama sınıflar tek başlarına yeterli değildir. Çalışma zamanında bu sınıflardan yaratacağınız nesneler ve bu nesnelere belirli bir düzene göre göndereceğiniz mesajlar ile çözüme ulaşırsınız. Her sınıfın belirli bir türde hizmet vermesi, belirli bir konuda uzmanlaşması ve böylelikle çözüme katkı vermesi arzu edilir. Bir nesneye bir mesaj göndererek, onun bir metodunu çağırarak aslında sınıfın verdiği bu hizmetten yararlanıyoruz. Gerçekte, uygulama programımız yürütme zamanında, çözüm için ihtiyacı olan türlerde nesneler yaratır ve bu nesnelere işleri ihale eder ve bir orkestra şefi gibi nesneleri aralarında uyumlu çalışacak şekilde yönetir. Bu uyumun bozulmaması için nesnelerin iç çalışma şekillerini dışarıdan gizlemesini tercih ederiz. Bunun için ise nesnelerin durumuna doğrudan erişimi engelleriz. Buna veri gizleme ilkesi diyoruz. Java'da sınıfa üye tanıtırken kullandığımız private ve protected erişim denetim sözcükleri tam olarak bu işe yarıyor. Bu durumda nesnenin durumunun bozulması ancak public tanımlı fonksiyonları aracılığı ile olabilir. Neyse ki birim testi yazıyoruz. Birim ve tümleştirme testleri hızlıca bu tür hatalı durumları yakalamamızı sağlar. Birim testi yazmıyorsanız, kullandığınız geliştirme aracının hata ayıklama aracı ile karşı karşıya kalırsınız. Eğer hata ayıklama aracını kullanmakta hünerli iseniz muhtemelen birim testi yazmıyorsunuz demektir. İşiniz bu durumda çok zor! Büyüyen bir kodda hata ayıklama oturumları saatlerinizi alabilir ve üstelik size katacağı hiç bir artı değer yoktur. Birim testi yazınız! Üstelik veri gizleme ilkesini uygulamak, birim testi yazmayı kolaylaştırır, sınıfın test edilebilirliğini artırır. Sınıfları kabaca iki gruba ayırabiliriz: alan sınıfları ve çözüm sınıfları. Alan sınıfları çözümü ile ilgilendiğimiz problemin var olduğu dünyayı modellemek için oluşturulurlar. Çözüm sınıfları ise problemin tanımlandığı uzayda var olmayan, sırf çözümü oluşturmak için kafamızda yarattığımız soyut sınıflardır. Buradaki soyut kelimesini Java'da abstract anahtar kelimesi ve nesneye dayalı programlamadaki soyut sınıflar ile karıştırmamak gerekir. 

Nesneye dayalı programlamada diğer bir araç kalıtımdır. Sınıflar arasında çeşitli türden ilişkiler kurmak mümkündür. Kalıtım ve içerme ilişkisi en sık kullandığımız türden ilişkilerdir. Kod tekrarını engellemek ve daha çok alan sınıfları arasında kullanmak üzere kalıtımdan faydalanıyoruz. Kalıtım ile temel sınıfın tüm özellikleri türetilen sınıfa aktarılmaktadır. Türetilmiş sınıfta, o sınıfa özel yeni özellikler ekleyebiliyoruz ve ayrıca temel sınıfın değiştirmek istediğimiz davranışını yeniden tanımlıyabiliyoruz (=overriding). İçerme ilişkisinde ise bir sınıf üye olarak başka bir sınıftan referans içerir. İçeren sınıf, bu referansı alt bir işi nesneye ihale etmek amacıyla kullanır. İçerme ilişkisini daha çok çözüm sınıflarında kullanmayı tercih ediyoruz. 

Aşağıda verilen kodda yer alan bir alan sınıfı (User) üzerinden bu ilişkilerin örneklerini görmeye çalışalım:

Gender.java:

package com.example.entity;

public enum Gender {
    MALE, FEMALE;
}

Phone.java:

package com.example.entity;

import javax.persistence.Embeddable;

/**
 * Binnur Kurt (binnur.kurt@gmail.com)
 */
@Embeddable
public class Phone {
    private String number;

    public Phone() {
    }

    public Phone(String number) {
        this.number = number;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "number='" + number + '\'' +
                '}';
    }
}

AbstractEntity.java:

package com.example.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

/**
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     *  For auditing purposes
     */
    @CreatedDate
    @JsonIgnore
    private LocalDateTime createdDate;
    @JsonIgnore
    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

    public AbstractEntity() {
    }

    // getters and setters

    // equals and hashCode

}

User.java:

package com.example.entity;

import com.example.validation.Email;
import com.example.validation.TcKimlikNo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.util.Collection;

@Entity
@Table(name = "users")
public class User extends AbstractEntity implements UserDetails {

    @TcKimlikNo
    private String identityNo;
    private String firstName;
    private String lastName;
    private String company;
    @Column(columnDefinition = "varchar(1024)")
    private String description;
    private String title;
    private String password;
    @Email
    private String email;
    private String address;
    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "number", column = @Column(name = "gsm_phone"))
    })
    private Phone gsmPhone;
    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "number", column = @Column(name = "office_phone"))
    })
    private Phone officePhone;
    @Enumerated(EnumType.ORDINAL)
    private Gender gender;
    private Boolean deleted;
    private Boolean suspended;

    public User() {
    }

    // getters and setters

    // hashCode and equals

    // toString

}

Alan sınıfı olan User sınıfını kalıcı olmasını istediğimiz bir sınıf olarak tanımladık. Tüm kalıcılık sınıflarında, her zaman var olmasını istediğimiz, nesnenin kimliğini tanımlayan Long tipinde bir alan yer alıyor. Denetim (=auditing) amacıyla kalıcı olmasını istediğimiz her nesnenin ne zaman yaratıldığı ve en son ne zaman güncellendiği bilgilerini de oluşturmak ve kalıcı hale getirmek istiyoruz. Bu alanları tüm kalıcılık sınıflarında tekrarlamamak amacıyla soyut bir temel sınıf (AbstractEntity) oluşturduk ve tüm kalıcılık sınıflarını bu sınıftan türettik. Ayrıca User sınıfı Gender ve Phone tiplerinden (kavramlarından) birer üye içeriyor. 

Çok şekillik, Nesneye Dayalı Programlamanın en önemli mekanizmasıdır. Çok şekillilik için aşağıdaki üç özelliğin sağlanması gerekir:
  1. Aralarında kalıtım ilişkisi olan sınıflar,
  2. Bu sınıflarda yer alan çok şekilli metodlar,
  3. Bu sınıflar arasında en temel sınıfından tanımlanmış bir referans.
Bu referansı kullanarak yapılan tüm çağrılar, yürütme zamanında bağlanır ve dinamik bağlama olarak adlandırılır. Çok şekillilik için gerekli olan birinci koşul çoğu zaman doğal olarak oluşur ancak bazen nadir de olsa oluşmaz. Örneğin Bird, Airplane, UFO (Unidentified Funny Objects) ve Superman sınıfları arasında kalıtım ilişkisi yoktur. Örneğin Bird, Airplane değildir, Superman, UFO değildir. Ancak bu dört sınıfta da ortak bir davranış vardır: fly(). fly() metodu çok şekilli bir metoddur. Ancak çok şekilli bir metodun varlığı, sınıflar arasında kalıtım ilişkisi kurulamadığı için çok şekillilik için yeterli olmaz. Sadece çok şekilliliği kurabilmek için gerçek dünyada olmayan bir sınıf uydurulur. Bu sınıf FlyingObject olsun. Şimdi problem uzayımızdaki tüm sınıflar (Bird, Airplane, UFO ve Superman) uydurulan bu FlyingObject sınıfından türetilir. Bu sınıfın gerçek dünyada olmayan, sırf çok şekillilik için kafamızdan "uydurduğumuz" bir sınıf olduğunu betimlemek üzere sınıfı soyut sınıf olarak adlandırıyoruz. Java'da soyut sınıf tanımlaması abstract anahtar kelimesi kullanılarak yapılır. Soyut bir sınıfın içinde en az bir tane soyut metod olması beklenir. Soyut metod abstract anahtar kelimesi ile tanımlanır.

Java tekli kalıtımı destekliyor. Somut bir sınıf sadece tek bir sınıftan türetilebilir. Somut sınıftan, new operatörünü kullanarak örneğini yaratabileceğimiz sınıfları anlıyoruz. Buna göre User sınıfı somut bir sınıftır. Ancak gerçek dünyada bazen çoklu kalıtım durumları ile karşılaşıyoruz. Java çoklu kalıtımı başka bir mekanizma ele alıyor: interface. Bir sınıf sadece tek bir sınıftan türetilebilirken (extends) birden fazla arayüzü gerçekleyebilir (implements). Soyut sınıftan nesne yaratamıyor olsak da soyut bir sınıfın verisi ve kurucu metodu bulunabilir. Soyut bir sınıf başka bir soyut sınıftan türetilebilir. Bu durumda soyut sınıfın soyut metodlarına işlev yüklemesi gerekmez. Soyut bir sınıf bir arayüzü gerçekleyecek ise onun metodlarına işlev yüklemek zorunda değildir. Bu durumda arayüzün metodları soyut sınıfın soyut metodlarına dönüşür. Soyut sınıfın somut metodları olabilir. Somut metod gövdesini kıvırcık parantez kullanarak verdiğimiz, gerçek dünyada örneği ile karşılaşabildiğimiz ve bunun bir sonucu olarak da modelleyebildiğimiz metodlardır. Java arayüzleri özel bir tür soyut sınıftır. Arayüzlerin verisi, kurucusu, somut metodu olamaz:
   
Arayüz = Soyut Sınıf  - Kurucu - Veri - Somut Metod
Java 8'e kadar arayüzün tüm metodları soyut olmak zorundaydı. Bu soyut metodlar Java 9'a kadar da public tanımlı olmak zorundaydı. Arayüz metodları hem public hem de abstract tanımlı olmak zorunda oldukları için tanımlanırken bunun açık olarak belirtilmesi gerekmez. Örtük olarak public ve abstract olduğu kabul edilir. Java 8 ile birlikte arayüzlere somut default tanımlı somut metodlar yazabiliyoruz. Aynı soyut sınıflarda olduğu gibi. Arayüzlerin kurucu fonksiyonları olamaz, soyut sınıfların kurucu fonksiyonları olabilir. Hem soyut sınıftan hem de arayüzden nesne yaratamazsınız. Arayüzlerin sadece örtük olarak public static final olarak tanımlı olmak zorunda olan sabitleri olabilir:
package com.example.java8;

public interface A {
        int x = 42;
        void fun() ;
}

A sınıfında fun() metodu örtük olarak hem public hem de abstract tanımlıdır. Benzer şekilde x değişkeni de örtük olarak public static final olarak tanımlıdır. Bunu istersek açık olarak da yazabiliriz:

package com.example.java8;

public abstract interface A {
        public static final int x = 42;
        abstract public void fun() ;
}

Şimdi B isimli bir arayüz tanımlayalım:

package com.example.java8;

public interface B {
    int x= 108;
    void gun();
}

B arayüzünde özel bir durum yok. A arayüzü ile herhangi bir bağlantısı bulunmuyor. Belki hem A hem de B arayüzlerinde aynı isimli bir sabit bulunuyor. Aralarında herhangi bir ilişki bulunmadığı ve bağımsız arayüzler olduğu için bu durum bizi şimdilik pek rahatsız etmedi. Şimdi A ve B arayüzlerinden kalıtımla yeni bir arayüz tanımlayalım:

package com.example.java8;

public interface C extends A,B{
    void run();    
}

Yukarıdaki kod derleyici tarafından sorunsuz bir şekilde derlenir. Hala bir sorun gözükmüyor. Şimdi C arayüzünü gerçekleyen somut bir sınıf CC oluşturalım:

package com.example.java8;

public class CC implements C {
    @Override
    public void run() {
        System.out.println(x);
    }

    @Override
    public void gun() {
         System.out.println(A.x);
    }

    @Override
    public void fun() {
         System.out.println(B.x);
    }
}

Yukarıdaki kodda run() metodunda x etiketli değişkene erişilmeye çalışılıyor. Ama şimdi bir problemimiz var: Geliştirici A'daki x'e mi erişmek istiyor yoksa B'deki x'e mi erişmeye çalışıyor? Derleyici ve kodu okuyan sizler de bunu bilemezsiniz! Derleyici bu nedenle bir belirsizlik hatası üretecektir:

Error:(6, 28) java: reference to x is ambiguous
  both variable x in com.example.java8.A and variable x in com.example.java8.B match

Bu durumda geliştiricinin yapması gereken hamle, hangi sınıftaki x'e eriştiğini A.x ya da B.x ifadesi ile derleyiciye bildirmesidir. 

Bir soyut sınıf başka bir soyut sınıfı türetebilir. Bu durumda türetildiği soyut sınıfın soyut metodlarını gerçeklemez zorunda değildir. Ancak istenirse türetilen sınıftaki soyut metodun bir gerçeklemesi verilebilir. Bir soyut sınıf bir ya da daha fazla sayıda arayüzü gerçekleyebilir. Bu durumda soyut sınıfın arayüzlerdeki metodların bir gerçeklemesini vermek zorunda değildir. Ancak istenirse arayüzlerdeki bazı soyut metodların gerçeklemesi verilebilir. Bir arayüz soyut sınıftan türetilemez. Arayüzler arasında kalıtım vardır. Üstelik bu kalıtım çokludur. Bir arayüz birden fazla arayüzden türetilebilinir:

public interface TransferService {
 boolean transfer(Account from,Account to,double amount);
}

public interface CustomerService {
 Customer createCustomer(String identityNo,String fullName);
 Customer getCustomer(String identityNo);
        Set<String> getAllIdentities();
}

public interface ReportService {
 void report(Locale locale);
}

public interface BankService extends TransferService, CustomerService, ReportService {
 String getName();
 long getId();
}


Burada BankService üç ayrı arayüzden türetilmektedir: TransferService, ReportService ve CustomerService. Soyut sınıf arayüzü gerçekleyebilir. Bu durumda BankBase örneğinde olduğu gibi arayüzün tüm metodlarını gerçeklemek zorunda değildir. SimpleBank ve BankBase örneğinde olduğu gibi soyut bir sınıf başka bir soyut sınıftan türetilebilinir. Arayüz metodları final tanımlanamaz.

Arayüzleri birer sözleşme olarak düşünebiliriz. Tasarım ilkesi olarak, çözüm sınıfları verdikleri hizmeti her zaman bir arayüz üzerinden, bir sözleşme üzerinden dışarıya açmalıdırlar. Hizmet alan sınıflar, hizmet aldıkları sınıfı değil, hizmetin arayüzünü tanımalıdırlar, sözleşmenin detaylarını bilmelidirler. Böylelikle hizmet alan ve hizmet veren sınıf arasında gevşek bir bağ kurmuş oluyoruz. Bu kurulan gevşek bağ sayesinde yazılımdaki değişiklikleri yönetmek mümkün olabilir. Bu anlamda kalıtım iki sınıf arasında sıkı bir bağ kurulmasına neden olur. Bu nedenle çözüm sınıflarında kalıtımı tercih etmiyoruz.

Tasarımda diğer bir ilke değişecek özellikler ile değişmeyecek özellikleri bir birinden ayırmaktır. Arayüz içinde sözleşmenin değişmeyen maddeleri bulunuyor. Bu sözleşmenin maddelerini yerine getiren farklı çözümler olabilir. Gelecekte bu sözleşmeyi yerine getiren başka çözüm sınıfları ortaya çıkabilir. Yazılım yeni özelliklere açık olmalıdır. Ancak bu yenilikleri mevcut kodda değişiklik yapmadan gerçeklemeliyiz. Bu tasarım ilkesi açıklık-kapalılık ilkesi olarak adlandırılır: yazılım yeniliklere açık, kod ise değişikliğe kapalı olmalıdır. 

Yukarıdaki ilkelerin gerçeklemesinde ağırlıklı olarak arayüzden yararlanıyoruz!

instanceof operatörü

instanceof operatörü, referans türünden bir değişkeninin gösterdiği nesnenin tipi ile ilgili sorgulama yapmak için kullanılır. Bu noktada soyut sınıf ile arayüz arasında derleyicinin davranışı açısından önemli bir fark bulunur:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.example.business;

public abstract class BusinessService {
 abstract public int logic(int data);
}

package com.example.business;

public class SimpleBusinessService extends BusinessService {

 @Override
 public int logic(int data) {
  return data;
 }

}

package com.example.business;

public class ValueAddedService extends BusinessService {
 private BusinessService delegate;
 
 public ValueAddedService(BusinessService delegate) {
  this.delegate = delegate;
 }

 @Override
 public int logic(int data) {
  return delegate.logic(data)+1;
 }

}

package com.example.business;

public class ExtendedBusinessService extends SimpleBusinessService {

 @Override
 public int logic(int data) {
  return super.logic(data)+42;
 }

}

package com.example.business;

public class OtherBusinessService {
 public int logic(int data) {
  return 42;
 }
}

package com.example.ui;

import java.io.Serializable;

import com.example.business.BusinessService;
import com.example.business.ExtendedBusinessService;
import com.example.business.OtherBusinessService;
import com.example.business.SimpleBusinessService;
import com.example.business.ValueAddedService;

public class BusinessApp {

 public static void main(String[] args) {
  BusinessService aService= new ExtendedBusinessService();
  System.out.println(aService instanceof SimpleBusinessService);
  System.out.println(aService instanceof ExtendedBusinessService);
  System.out.println(aService instanceof ValueAddedService);
  System.out.println(aService instanceof OtherBusinessService);

 }

}

Yukarıdaki kod parçasında BusinessService sınıfı soyut bir sınıf ve SimpleBusinessServiceValueAddedService ve ExtendedBusinessService sınıfları ise bu sınıftan türetilmiş somut sınıflar olarak tasarlanmışlardır. OtherBusinessService ise bu sınıflardan tamamen bağımsız bir sınıf olarak kodlanmıştır. Bu durumda BusinessApp uygulamasında 70. satır için derleyici hata verecektir: 

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
 Incompatible conditional operand types BusinessService and OtherBusinessService

 at com.example.ui.BusinessApp.main(BusinessApp.java:18)

Derleyici eğer referans tipinden değişkenin tipi soyut ya da somut bir sınıf ise instanceof operatörünün sağ tarafındaki tip için statik bir analiz yapar ve BusinessService tipinden bir referansın hiç bir zaman OtherBusinessService tipinden bir nesneyi gösteremeyeceğini saptar. Ardından yukarıda verilen türde bir derleme hata iletisi üretir. Oysa BusinessService bir arayüz olarak tasarlanırsa derleyici bu statik analizi gerçekleştirmeyecektir:

package com.example.business;

public abstract interface BusinessService {
 abstract public int logic(int data);
}

package com.example.business;

public class ExtendedBusinessService extends SimpleBusinessService {

 @Override
 public int logic(int data) {
  return super.logic(data)+42;
 }

}

package com.example.business;

public class SimpleBusinessService implements BusinessService {

 @Override
 public int logic(int data) {
  return data;
 }

}

package com.example.business;

public class ValueAddedService implements BusinessService {
 private BusinessService delegate;
 
 public ValueAddedService(BusinessService delegate) {
  this.delegate = delegate;
 }

 @Override
 public int logic(int data) {
  return delegate.logic(data)+1;
 }

}

package com.example.business;

public class OtherBusinessService {
 public int logic(int data) {
  return 42;
 }
}

package com.example.ui;

import java.io.Serializable;

import com.example.business.BusinessService;
import com.example.business.ExtendedBusinessService;
import com.example.business.OtherBusinessService;
import com.example.business.SimpleBusinessService;
import com.example.business.ValueAddedService;

public class BusinessApp {

 public static void main(String[] args) {
  BusinessService aService= new ExtendedBusinessService();
  System.out.println(aService instanceof SimpleBusinessService);
  System.out.println(aService instanceof ExtendedBusinessService);
  System.out.println(aService instanceof ValueAddedService);
  System.out.println(aService instanceof OtherBusinessService);

 }

}

Artık derleyici System.out.println(aService instanceof OtherBusinessService) satırı için hata vermiyor! instanceof operatörünün sağ ya da sol tarafındaki tip bir arayüz ise hiç bir zaman derleyici hata üretmez. Örneğin, x instanceof Comparable ifadesi x değişkeninin tipi ne olursa olsun her zaman derlenir.

Java 8 ile Arayüzde Gelen Yenilikler

Java 8 ile birlikte arayüz ile ilgili genel olarak fonksiyonel programlamaya yönelik üç önemli değişiklik geldi:

1. Fonksiyonel Arayüz

İçinde herhangi bir metod içermeyen arayüzlere özel bir isim veriyoruz: İşaretleyici Arayüz (=Marker Interface). Bu gruba giren işaretleyici arayüzlere örnek olarak Serializable, Cloneable ve Remote arayüzlerini verebiliriz. Bu arayüzler Java programlama dilinin henüz Java SE 5 ile gelen Damgaların (=Annotations) olmadığı ilk yılllarında kullanıldıklarını görüyoruz. Amaç sınıfı özel bir amaçla işaretlemek. Artık bir çerçeve ya da API geliştirirken Java SE 5 ile gelen damgaları kullanıyoruz.

Java SE 8 ile birlikte gelen fonksiyonel programlama ile birlikte arayüzlere yönelik yeni bir gruplama geldiğini görüyoruz: Fonksiyonel Arayüz (=Functional Interface). Bir arayüzün Fonksiyonel arayüz olabilmesi için içinde sadece tek bir public abstract metod içermesi gerekir. static ve default tanımlı metodları bu sayının dışında tutuyoruz. Bu özelliği sağlayan bir arayüzü yine Java SE 8 ile birlikte gelen @FunctionInterface notunu düşürek arayüzün bu özelliğini belirgin hale getirebiliriz.

Java programlama dilinde Fonksiyonel arayüz niteliğinde Java'nın ilk yıllarından beri kullanıdığımız çok sayıda arayüz bulunuyor: Runnable, Callable, Comparator, Comparable, ActionListener. Fonksiyonel arayüzlerin çoğu elbette Java SE 8 ile birlikte geldi:
BiConsumer<T,U>
BiFunction<T,U,R>
BinaryOperator<T>
BiPredicate<T,U>
BooleanSupplier
Consumer<T>
DoubleBinaryOperator
DoubleConsumer
DoubleFunction<R>
DoublePredicate
DoubleSupplier
DoubleToIntFunction
DoubleToLongFunction
DoubleUnaryOperator
Function<T,R>
IntBinaryOperator
IntConsumer
IntFunction<R>
IntPredicate
IntSupplier
IntToDoubleFunction
IntToLongFunction
IntUnaryOperator
LongBinaryOperator
LongConsumer
LongFunction<R>
LongPredicate
LongSupplier
LongToDoubleFunction
LongToIntFunction
LongUnaryOperator
ObjDoubleConsumer<T>
ObjIntConsumer<T>
ObjLongConsumer<T>
Predicate<T>
Supplier<T>
ToDoubleBiFunction<T,U>
ToDoubleFunction<T>
ToIntBiFunction<T,U>
ToIntFunction<T>
ToLongBiFunction<T,U>
ToLongFunction<T>
UnaryOperator<T>
2. Arayüzde artık gövdesi olan default tanımlı gerçek metod tanımlayabiliyoruz

Java 8 ile birlikte çok çekirdekli programlama için Stream API ve MapReduce çerçevesinin geldiğini görüyoruz. Stream API ile torbalar (=collections) üzerinde paralel çalışabilen işlemler tanımlayabiliyoruz. Stream API'nin torbalar üzerinde çalışabilmesi için Collection API'nin arayüzlerinde değişiklik yapmak gerekiyordu. Bu değişiklikleri geriye doğru uyumluluğu bozmadan gerçekleştirebilmek için artık arayüz içinde default tanımlı metodlar tanımlayabiliyoruz. Şimdi java.util.Collection arayüzünü inceleyelim:

public interface Collection<E> extends Iterable<E> {
    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    boolean add(E e);

    boolean remove(Object o);

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

    boolean retainAll(Collection<?> c);

    void clear();

    boolean equals(Object o);

    int hashCode();

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }

    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

Java 8'de Collection arayüzünde default tanımlı dört yeni metod geldiğini görüyoruz: removeIf(), spliterator(), stream() ve parallelStream(). Bu metodlar default ile tanımlı kıvırcık parentezlerle çevrelenmiş bir gövdeye sahipler. Derleyici, Collection arayüzünü gerçekleyen bir sınıf için eğer sınıf default tanımlı metodlar için bir gerçekleme vermez ise arayüzde verilen gerçeklemeyi kullanıyor. Bu özellik API tasarlayanlar için tasarımda büyük bir esneklik sunuyor ve API'nin bir taraftan geriye doğru uyumluluğunu korurken diğer taraftan yeni istekleri karşılamak üzere arayüze yeni metodlar eklenebilmesine olanak sağlıyor.

3. Arayüzde artık static tanımlı metod tanımlayabiliyoruz

Java 8'de arayüzde static tanımlı metod tanımlıyabiliyoruz. Java programlama diline getirilen bu değişikliğin amaçlarından biri fonksiyonel programlamayı desteklemektir. Arayüz içindeki static fonksiyonları, metodlara parametre aktarımı amacıyla kullanabiliyoruz. Arayüz içinde tanımlanmış static metodların diğer bir kulllanımı kolaylık fonksiyonları tanımlamaktır. Aşağıdaki örnekte, bir tam sayının tekliğini ve çiftliğini test eden ve boolean değer dönen iki static metodun (odd ve even) tanımlı olduğu A arayüzü ve bu metodların fonksiyonel programlamadaki örnek kullanımı verilmiştir.

package com.example;

import java.util.stream.IntStream;

/**
 *
 * @author Binnur Kurt (binnur.kurt@gmail.com)
 */
public class StudyInterfaces {
    public static void main(String[] args) {
        IntStream.range(0, 10)
                  .filter(A::odd)
                  .forEach(System.out::println);
        IntStream.range(0, 10)
                  .filter(A::even)
                  .forEach(System.out::println);
    }
}

interface A {

    static boolean odd(int x){
        return x%2 != 0;
    }
    
    static boolean even(int x){
        return x%2 == 0;
    }
    
}

Arayüzde tanımlanan static metodlar için iki kısıt bulunuyor:
  • static metodlar için çok şekillilik çalışmaz! 
  • Ayrıca static metodlar sınıfın this üyesine erişemezler ve arayüzün static olmayan metodlarını çağıramazlar!
Arayüze static metod eklerken, tasarımınızda static metoda ye verirken bu iki kısıtı göz önünde bulundurmalısınız.

Java 9 ile Arayüzde Gelen Yenilikler 

Java 8 ile birlikte arayüzde static ve default tanımlı metodlar yazabiliyoruz. Java 9'a kadar bu metodların public olmak zorunluluğu bulunmaktaydı. Java 9'da gelen yenilik ile birlikte static tanımlı metodları private olarak da tanımlanabiliyoruz:

interface I {
    public static void fun(){}
    private static void gun(){}
    public default void run(){}
    private default void sun(){}
    public void tun();
    private void zun();
    private void bun(){}
}

Buna göre yukarıdaki kod parçasında static tanımlı fun() fonksiyonu public tanımlıdır ve geçerlidir. static tanımlı diğer fonksiyon ise gun() fonksiyonu private tanımlanmıştır. Java 9 bu tanıma onay veriyor! default tanımlı metodlar ise hala public olmak zorundalar! Buna göre default tanımlı sun() fonksiyonu private tanımlandığı için derleyici hata verecektir! abstract metodlar hala sadece public olarak tanımlanabilir. Buna göre derleyici zun() fonksiyonunun tanımına kızacaktır! Peki arayüzün son metodu olan bun() için durum nedir? private tanımlandığına göre static olmak zorundadır. static anahtar kelimesini derleyici bu sefer kendisi ekler!