Статьи

Раскройте всю мощь Java 8 Stream


В этом посте мы собираемся раскрыть всю мощь Java 8 Stream API.
Следует помнить одну важную вещь: почти все изменения, внесенные в Java 8, предназначены для того, чтобы помочь разработчикам писать более качественный код.


Две основные функции, представленные в Java 8, — это лямбда-выражения и Stream API, который интенсивно использует Lambdas.
Благодаря этим функциям Java подошла очень близко к функциональному программированию.

В этом посте мы сконцентрируемся на том, чего можно достичь с помощью Java Stream API

Так что без лишних слов давайте попадем в Stream.


Все мы написали подобный код для перебора коллекции и обработки каждого элемента.
Employee e1=new Employee("Alex Tudor","Senior Developer","2014-02-16");
Employee e2=new Employee("John Michael","Research Analyst","2011-08-20");
Employee e3=new Employee("Anna Stark","Web Designer","2009-09-15");
List<Employee> empList=new ArrayList<>();
empList.add(e1);
empList.add(e2);
empList.add(e3);

for(Employee e:empList){
    if(Employee.isJoinAfter2012(e.getJoiningDate())){
    //Make a list of these Employees or 
    //increment a counter to get total number of Employees joined after 2012
  }
}

Это может выглядеть нормально для вас, но включает в себя стандартный код и не передает намерений программиста.
Вы можете утверждать, что приведенный выше код легко читается и действительно передает то, что программист пытается достичь. Но это легко загрязняется, когда мы вложили в циклы множество тестов if-else и локальных переменных. Тогда становится действительно трудно понять, что код пытается сделать.

Давайте посмотрим, как мы можем использовать Java 8 Streams для этой задачи. 

Предположим, нам нужно узнать список всех сотрудников, присоединившихся после 2012 года. Ниже приведен код с использованием Stream API.
List<Employee> after2012List=empList.stream()
		       .filter(emp -> Employee.isJoinAfter2012(emp.getJoiningDate()) )
		       .collect(Collectors.toList());

Точно так же, если нам нужно просто подсчитать общее количество сотрудников, присоединившихся после 2012 года, мы можем сделать это, как показано ниже
long empAfter2012=empList.stream()
			    .filter(emp->Employee.isJoinAfter2012(emp.getJoiningDate()) )
	    .count();	

Обратите внимание, что при использовании Stream API мы просто говорим языку, что мы хотим, а не как это сделать.
Как и в первом примере, мы только что создали поток из списка, затем использовали фильтр, чтобы сохранить только тех сотрудников, которые присоединились после 2012 года, а затем вернули список этих сотрудников. Мы не делали никаких тестов, мы не создавали ArrayList и добавляли каждого сотрудника в этот список вручную.

Аналогично, во втором примере мы только что прошли критерии фильтрации, а затем мы вернули общее количество всех сотрудников, которые соответствуют этим критериям.
Обратите внимание, что у нас не было никакого локального счетчика, и нам не нужно вручную увеличивать его. С Streams вы просто говорите, что вам нужно, вы не обязаны рассказывать, как это сделать.

Java 8 Stream API очень мощный, и он действительно делает сложную задачу очень простой.
Если вы все еще не убеждены в силе потоков, просто подождите, пройдя нижеприведенные примеры, я уверен, что вы сможете это оценить.

Я скачал набор данных фильма, содержащий информацию о 3452 фильмах из

Zepfanman IMDB Лист данных фильмов 


Извлекаем из него полезную информацию с помощью Streams.
Я немного очистил загруженный набор данных, удалив ненужную информацию, и сделал его значениями, разделенными точкой с запятой (;). 

Набор данных группирует информацию о каждом фильме в 14 столбцах
Название : Название фильма

Тип заголовка : Тип фильма (например, документальный или художественный фильм, короткометражный фильм и т. Д.)

Режиссеры : Имя режиссеров этого фильма

Рейтинг AVG : Средний рейтинг фильма

Рейтинг IMDB : Рейтинг фильма на IMDB 

Гнилые помидоры Рейтинг : Рейтинг фильмов на гнилых помидорах

Время выполнения : продолжительность фильма в минутах

Год : год выпуска фильма

Жанры : Жанры фильма, такие как боевик, биография, драма и т. Д.

Количество голосов IMDB : голосов, которые он получил на IMDB от пользователей

IMDB Top 250 : находится ли фильм в списке лучших 250 IMDB (если да, то Y в противном случае N или пусто)

1001 Должен видеть
: должен ли фильм быть ниже 1001 Должен видеть список (если да, то Y в противном случае N или пусто)

Желание увидеть (оценка АФ) : оценка по шкале от А до F, отражающая популярность этого фильма

URL : URL, где вы можете посмотреть фильм на IMDB

Примечание. Не все столбцы имеют значение для каждого фильма, некоторые столбцы пустые, если для этих столбцов нет данных. 
Как и для второго фильма в наборе данных (фильм 24), имя режиссера и многие другие столбцы пустые.

Как создать поток из файла?


Чтобы использовать Stream API, мы должны сначала получить поток из файла набора данных movies.csv.

Java 8 предоставляет
открытый java.util.stream.Stream <java.lang.String> lines ()  в классе BufferedReader, чтобы легко получить поток из файла.
BufferedReader breader=null;		
try{		
       	Path path = Paths.get("src/resources", "movies.csv");
       breader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1);       
    }catch(IOException exception){
        		System.out.println("Error occurred while trying to read the file");
       		System.exit(0);
     	}
		
List<String> lines=breader.lines()
  	                        .collect(Collectors.toList());

Выше кода читает файл movies.csv, breader.lines () возвращает Stream, затем мы вызываем collect (), чтобы вернуть список строк из файла.
Теперь мы готовы играть с Stream API. 
Извлеките названия всех фильмов и сохраните их в списке 
List<String> movies=lines.stream()
		     .skip(1)
		     .map(line -> Arrays.asList(line.split(";")))	
		     .map(list -> {String movie=list.get(0).trim(); return movie;})
		     .collect(Collectors.toList());		
System.out.println(movies);

Сначала мы получаем Stream, вызывая stream () в списке, который мы создали ранее, читая файл, мы пропустили первую строку потока, так как это строка заголовка с именами столбцов.
Затем мы разделяем поток (линию) с помощью; в качестве разделителя, который возвращает массив строк. Мы преобразовали массив String в список. Поскольку нам нужно только название фильмов, поэтому мы создали поток только из названий фильмов, снова используя map ().  
Мы используем map () для преобразования потока одной вещи в поток другой вещи . В конце мы собрали все имена фильмов в список, используя Collectors.toList (). 

Вместо того, чтобы последовательно использовать две map (), мы можем добиться того же самого с помощью только одной map (). 


Ниже код эквивалентен приведенному выше
List<String> movieNames=lines.stream()
			    .skip(1)
			    .map(line -> Arrays.asList(line.split(";")).get(0).trim())	
			    .collect(Collectors.toList());		
System.out.println(movieNames);

В приведенном выше фрагменте кода
map (line -> Arrays.asList (line.split («;»)). Get (0)). Trim () передает поток только имен фильмов в коллекцию () 
Позволяет узнать имя режиссера, который снял фильм Red River
lines.stream()
  .skip(1)
	  .map(line -> Arrays.asList(line.split(";")))	
	  .filter(movie -> {String movieName=movie.get(0); 
	                    return movieName.trim().equalsIgnoreCase("Red River");})
	  .forEach(list -> {String director=list.get(2);
	                    System.out.println("Red River was directed by
                          "+director);});

На этот раз мы используем filter (), чтобы проверить, является ли название фильма Red River или нет. Если это так, мы получаем его директора, чтобы получить режиссера фильма Red River. Обратите внимание, что forEach () будет выполняться только тогда, когда условие фильтра возвращает true ,  
Перечислите названия всех документальных фильмов из набора данных
List<String> docMovies=lines.stream()
			   .skip(1)
			   .map(line -> Arrays.asList(line.split(";")))	
			   .filter(list -> {String type=list.get(1); 
		                  return type.trim().equalsIgnoreCase("documentary");})
			   .map(movie -> {String movieName=movie.get(0); 
                               return movieName;})
			   .collect(Collectors.toList());
System.out.println(docMovies);

Обратите внимание, как мы используем map () после filter (), чтобы просто получить имена всех документальных фильмов.
В этом прелесть потока, вы можете манипулировать им, цепляя методы один за другим так, как вы хотите. 
Узнайте общее количество документальных фильмов из набора данных 


С этого времени нас интересует только общее количество документальных фильмов, которые мы можем использовать count () из класса Stream, чтобы получить это.
long totalDocMovies=lines.stream()
			.skip(1)
			.map(line -> Arrays.asList(line.split(";")).get(1))	
			.filter(movieType -> 
                               movieType.trim().equalsIgnoreCase("documentary"))
			.count();

System.out.println("Total Documentary Movies : "+totalDocMovies);

map (line -> Arrays.asList (line.split («;»)). get (1)) пропускает поток только типов фильмов для фильтрации.
Список всех документальных фильмов, выпущенных в 2000 году
List<String> doc2000List=lines.stream()
			     .skip(1)
			     .map(line -> Arrays.asList(line.split(";")))	
			     .filter(movie -> {String movieType=movie.get(1).trim(); 
		                             return (!movieType.equals("")&& 
                                  movieType.equalsIgnoreCase("documentary"));})
			     .filter(list -> {String year=list.get(7).trim();
		                            return (!year.equals("")&& 
                                                  year.equals("2000"));})
			     .map(movie -> {String name=movie.get(0); return name;})
			     .collect(Collectors.toList());				   
System.out.println(doc2000List);

Обратите внимание, что мы использовали два фильтра: первый для фильтрации документальных фильмов и второй для фильтрации документальных фильмов, выпущенных в 2000 году. В конце мы собрали названия этих фильмов. 

Вместо использования двух filter () мы можем выполнить оба теста только в одном filter (), как показано здесь
List<String> doc2000=lines.stream()
		  .skip(1)
		  .map(line -> Arrays.asList(line.split(";")))	
		  .filter(movie -> {
		                    String type=movie.get(1).trim();
		                    String year=movie.get(7).trim();
		          return (!type.equals("")&& !year.equals("")&&
                       type.equalsIgnoreCase("documentary")&& year.equals("2000"));})
		  .map(movie -> {String name=movie.get(0); return name;})
		  .collect(Collectors.toList());				   
System.out.println(doc2000);
Список документальных фильмов, выпущенных в 2000 году, имеющих рейтинг IMDB больше или равный 7 

Это похоже на описанное выше, нам просто нужно добавить еще один тест, чтобы посмотреть рейтинг фильма в IMDB.
List<String> doc2000IMDB7=lines.stream()
    		.skip(1)
    		.map(line -> Arrays.asList(line.split(";")))	
    		.filter(movie -> {String type=movie.get(1).trim();
    			            return (!type.equals("")&& 
                                        type.equalsIgnoreCase("documentary"));})
    		.filter(movie -> {String year=movie.get(7).trim();
    				     return (!year.equals("")&& year.equals("2000"));})
    		.filter(movie -> {String imdb=movie.get(4).trim();
    				     return (!imdb.equals("")&&
                                        Float.parseFloat(imdb)>= 7);})
    		.map(movie ->    {String movieName=movie.get(0).trim();
    				     return movieName;})
    		.collect(Collectors.toList());		
System.out.println(doc2000IMDB7);

Если вы хотите, вы можете использовать только один фильтр () и поместить все эти три теста в него.  
Узнайте минимальное время выполнения фильма из набора данных, другими словами, какова минимальная продолжительность любого фильма среди всех фильмов
String minimumRuntime=lines.stream()
  			  .skip(1)
  			  .map(line -> {String runtime=line.split(";")[6]; 
                                      return runtime.trim();})	
  			  .filter(movieRuntime -> !movieRuntime.equals(""))
  			  .min(Comparator.comparing(time -> Float.parseFloat(time)))
  			  .get();  				      
  		
System.out.println("Minimum Runtime "+minimumRuntime);

Мы использовали map (), чтобы получить только время выполнения фильмов из потока.
Тогда мы фильтровали только те фильмы, для которых время выполнения не пустое. Если мы не фильтруем, мы можем получить NumberFormatException, пытаясь проанализировать пустую строку для плавающего. Теперь мы можем использовать min (), чтобы выяснить минимальное время выполнения среди всех. 
Узнайте максимальную продолжительность фильма из набора данных, другими словами, какова максимальная продолжительность любого фильма среди всех фильмов 


Класс java.util.stream.Stream также имеет max (), который мы будем использовать, чтобы узнать максимальное время выполнения. 
String maximumRuntime=lines.stream()
			       .skip(1)
			       .map(line -> {String runtime=line.split(";")[6]; 
                                         return runtime.trim();})	
			       .filter(movieRuntime -> !movieRuntime.equals(""))
			       .max(Comparator.comparing(time -> Float.parseFloat(time)))
			       .get();
			      
	
System.out.println("Maximum Runtime "+maximumRuntime);
Перечислите список из 5 лучших фильмов на IMDB
lines.stream()
     .skip(1)
     .map(line -> Arrays.asList(line.split(";")) )
     .filter(movie -> {String imdbVotes=movie.get(9).trim();
    		                 return !imdbVotes.equals("");})     		                              
     .sorted((movie1,movie2) -> {String m1Votes=movie1.get(9).trim();
    		                    String m2Votes=movie2.get(9).trim();
    	       return Integer.valueOf(m2Votes).compareTo(Integer.valueOf(m1Votes));} )
     .limit(5)
     .forEach(movie -> {System.out.println(movie.get(0)+" --- "+movie.get(9));});

В новом Stream API намного больше, чем мы рассмотрели здесь.
Но это поможет вам начать писать код с использованием Java 8 Stream API

Если вы заинтересованы в том, чтобы попробовать фрагменты кода самостоятельно, получите его от  Github