Автор оригинала: Pankaj Kumar.
Java 8 была выпущена 18 марта 2014 года. Это было давно, но до сих пор многие проекты выполняются на Java 8. Это потому, что это был крупный релиз с большим количеством новых функций. Давайте рассмотрим все интересные и основные функции Java 8 на примере кода.
Краткий обзор функций Java 8
Некоторые из важных функций Java 8 являются;
- Метод forEach() в итерационном интерфейсе
- стандартные и статические методы в интерфейсах
- Функциональные интерфейсы и Лямбда-выражения
- API потока Java для массовых операций с данными в коллекциях
- API времени Java
- Улучшения API сбора
- Улучшения API параллелизма
- Улучшения ввода-вывода Java
Давайте кратко рассмотрим эти функции Java 8. Я приведу некоторые фрагменты кода для лучшего понимания функций простым способом.
1. Метод forEach() в итерационном интерфейсе
Всякий раз, когда нам нужно пройти через коллекцию, нам нужно создать итератор, вся цель которого состоит в повторении, а затем у нас есть бизнес-логика в цикле для каждого из элементов Коллекции. Мы можем получить исключение ConcurrentModificationException, если итератор используется неправильно.
Java 8 представила для каждого метода в java.lang.Итерируемый интерфейс, так что при написании кода мы фокусируемся на бизнес-логике. Метод forEach использует функцию java.util..Объект потребителя в качестве аргумента, поэтому он помогает разместить нашу бизнес-логику в отдельном месте, которое мы можем повторно использовать. Давайте рассмотрим каждое использование на простом примере.
package com.journaldev.java8.foreach; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import java.lang.Integer; public class Java8ForEachExample { public static void main(String[] args) { //creating sample Collection ListmyList = new ArrayList (); for(int i=0; i<10; i++) myList.add(i); //traversing using Iterator Iterator it = myList.iterator(); while(it.hasNext()){ Integer i = it.next(); System.out.println("Iterator Value::"+i); } //traversing through forEach method of Iterable with anonymous class myList.forEach(new Consumer () { public void accept(Integer t) { System.out.println("forEach anonymous class Value::"+t); } }); //traversing with Consumer interface implementation MyConsumer action = new MyConsumer(); myList.forEach(action); } } //Consumer implementation that can be reused class MyConsumer implements Consumer { public void accept(Integer t) { System.out.println("Consumer impl Value::"+t); } }
Количество строк может увеличиться, но для каждого метода помогает наличие логики итерации и бизнес-логики в отдельном месте, что приводит к более высокому разделению проблем и более чистому коду.
2. стандартные и статические методы в интерфейсах
Если вы внимательно прочтете сведения о каждом методе, вы заметите, что он определен в итерационном интерфейсе, но мы знаем, что интерфейсы не могут иметь тело метода. Начиная с Java 8, интерфейсы расширены, чтобы иметь метод с реализацией. Мы можем использовать по умолчанию
и статическое
ключевое слово для создания интерфейсов с реализацией метода. для каждой реализации метода в итерационном интерфейсе:
default void forEach(Consumer super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
Мы знаем, что Java не обеспечивает множественное наследование в классах , потому что это приводит к Алмазной проблеме . Итак, как теперь это будет обрабатываться с интерфейсами, поскольку интерфейсы теперь похожи на абстрактные классы?
Решение заключается в том, что компилятор создаст исключение в этом сценарии, и нам придется предоставить логику реализации в классе, реализующем интерфейсы.
package com.journaldev.java8.defaultmethod; @FunctionalInterface public interface Interface1 { void method1(String str); default void log(String str){ System.out.println("I1 logging::"+str); } static void print(String str){ System.out.println("Printing "+str); } //trying to override Object method gives compile-time error as //"A default method cannot override a method from java.lang.Object" // default String toString(){ // return "i1"; // } }
package com.journaldev.java8.defaultmethod; @FunctionalInterface public interface Interface2 { void method2(); default void log(String str){ System.out.println("I2 logging::"+str); } }
Обратите внимание, что оба интерфейса имеют общий метод log() с логикой реализации.
package com.journaldev.java8.defaultmethod; public class MyClass implements Interface1, Interface2 { @Override public void method2() { } @Override public void method1(String str) { } //MyClass won't compile without having it's own log() implementation @Override public void log(String str){ System.out.println("MyClass logging::"+str); Interface1.print("abc"); } }
Как вы можете видеть, Интерфейс 1
имеет реализацию статического метода, которая используется в Мой класс.long()
реализация метода. Java 8 использует по умолчанию и статические методы в значительной степени в API сбора и методы по умолчанию добавляются, чтобы наш код оставался обратно совместимым.
Если какой-либо класс в иерархии имеет метод с такой же сигнатурой, то методы по умолчанию становятся неактуальными. Объект является базовым классом, поэтому, если у нас есть методы по умолчанию equals (), hashCode() в интерфейсе, это станет неуместным. Вот почему для большей ясности интерфейсам не разрешается использовать методы по умолчанию для объектов.
Для получения более подробной информации об изменениях интерфейса в Java 8, пожалуйста, прочитайте Изменения интерфейса Java 8 .
3. Функциональные интерфейсы и лямбда-выражения
Если вы заметили приведенный выше код интерфейса, вы заметите аннотацию @functional Interface. Функциональные интерфейсы-это новая концепция, представленная в Java 8. Интерфейс с ровно одним абстрактным методом становится Функциональным интерфейсом. Нам не нужно использовать аннотацию @Functional Interface для обозначения интерфейса как функционального интерфейса.
Аннотация @Functional Interface-это средство, позволяющее избежать случайного добавления абстрактных методов в функциональные интерфейсы. Вы можете думать об этом так @Переопределите аннотацию, и лучше всего ее использовать. ява.ланг.Запускаемый с помощью одного абстрактного метода run() является отличным примером функционального интерфейса.
Одним из основных преимуществ функционального интерфейса является возможность использовать лямбда-выражения для их создания. Мы можем создать экземпляр интерфейса с анонимным классом, но код выглядит громоздким.
Runnable r = new Runnable(){ @Override public void run() { System.out.println("My Runnable"); }};
Поскольку функциональные интерфейсы имеют только один метод, лямбда-выражения могут легко обеспечить реализацию метода. Нам просто нужно предоставить аргументы метода и бизнес-логику. Например, мы можем написать вышеописанную реализацию, используя лямбда-выражение как:
Runnable r1 = () -> { System.out.println("My Runnable"); };
Если у вас есть один оператор в реализации метода, нам также не нужны фигурные скобки. Например, приведенный выше интерфейс 1 анонимный класс может быть создан с помощью лямбда-кода следующим образом:
Interface1 i1 = (s) -> System.out.println(s); i1.method1("abc");
Таким образом, лямбда-выражения являются средством для легкого создания анонимных классов функциональных интерфейсов. Использование лямбда-выражений не дает никаких преимуществ во время выполнения, поэтому я буду использовать их осторожно, потому что я не против написать несколько дополнительных строк кода.
Новый пакет java.util.function
был добавлен с набором функциональных интерфейсов для предоставления целевых типов для лямбда-выражений и ссылок на методы. Лямбда-выражения-огромная тема, об этом я напишу отдельную статью в будущем.
Вы можете прочитать полный учебник в руководстве по лямбда-выражениям Java 8 .
4. Java Stream API для массовых операций с данными в коллекциях
В Java 8 добавлен новый java.util.stream
для выполнения операций фильтрации/сопоставления/уменьшения подобных с коллекцией. Потоковый API позволит выполнять как последовательное, так и параллельное выполнение. Это одна из лучших функций для меня, потому что я много работаю с коллекциями и, как правило, с большими данными, нам нужно отфильтровать их в зависимости от некоторых условий.
Интерфейс сбора данных был расширен с помощью методов stream() и parallelStream() по умолчанию для получения потока для последовательного и параллельного выполнения. Давайте рассмотрим их использование на простом примере.
package com.journaldev.java8.stream; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; public class StreamExample { public static void main(String[] args) { ListmyList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); //sequential stream Stream sequentialStream = myList.stream(); //parallel stream Stream parallelStream = myList.parallelStream(); //using lambda with Stream API, filter example Stream highNums = parallelStream.filter(p -> p > 90); //using lambda in forEach highNums.forEach(p -> System.out.println("High Nums parallel="+p)); Stream highNumsSeq = sequentialStream.filter(p -> p > 90); highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p)); } }
Если вы запустите приведенный выше пример кода, вы получите такой вывод:
High Nums parallel=91 High Nums parallel=96 High Nums parallel=93 High Nums parallel=98 High Nums parallel=94 High Nums parallel=95 High Nums parallel=97 High Nums parallel=92 High Nums parallel=99 High Nums sequential=91 High Nums sequential=92 High Nums sequential=93 High Nums sequential=94 High Nums sequential=95 High Nums sequential=96 High Nums sequential=97 High Nums sequential=98 High Nums sequential=99
Обратите внимание, что значения параллельной обработки не в порядке, поэтому параллельная обработка будет очень полезна при работе с огромными коллекциями.
Охватить все о потоковом API в этом посте невозможно, вы можете прочитать все о потоковом API в примере учебника Java 8 по потоковому API .
5. API времени Java
Всегда было трудно работать с датой, временем и часовыми поясами на java. В Java не было стандартного подхода или API для определения даты и времени в Java. Одним из приятных дополнений в Java 8 является пакет java.time
, который упростит процесс работы со временем в java.
Просто взглянув на пакеты API Java Time, я чувствую, что они будут очень просты в использовании. В нем есть некоторые подпакеты java.time.format, которые предоставляют классы для печати и анализа дат и времени, а java.time.zone обеспечивает поддержку часовых поясов и их правил.
API нового времени предпочитает перечисления целочисленным константам для месяцев и дней недели. Одним из полезных классов является DateTimeFormatter для преобразования объектов DateTime в строки. Для получения полного руководства перейдите к примеру учебника Java Date Time API .
6. Улучшения API сбора данных
Мы уже видели для каждого метода() и API потока для коллекций. Некоторые новые методы, добавленные в API сбора, включают:
Итератор
метод по умолчаниюforEachRemaining(действие потребителя)
для выполнения данного действия для каждого оставшегося элемента до тех пор, пока все элементы не будут обработаны или действие не вызовет исключение.Коллекция
метод по умолчаниюremoveIf(фильтр предикатов)
для удаления всех элементов этой коллекции, удовлетворяющих данному предикату.Коллекция
spliterator()
метод, возвращающий экземпляр Spliterator, который можно использовать для последовательного или параллельного обхода элементов.- Сопоставить
Заменить ()
,вычислить()
,объединить()
методы. - Повышение производительности для класса HashMap с коллизиями ключей
7. Улучшения API параллелизма
Некоторые важные параллельные усовершенствования API включают:
ConcurrentHashMap
вычисление(), для каждого(), для каждой записи(), для каждого ключа(), для каждого значения(), методы слияния(), сокращения() и поиска ().CompletableFuture
, который может быть явно завершен (установка его значения и статуса).Исполнители
newWorkStealingPool()
метод создания пула потоков, крадущих работу, с использованием всех доступных процессоров в качестве целевого уровня параллелизма.
8. Улучшения ввода-вывода Java
Некоторые улучшения ввода-вывода, известные мне, заключаются в следующем:
Files.list(Путь к каталогу)
, который возвращает лениво заполненный поток, элементами которого являются записи в каталоге.Файлы.строки(Путь к пути)
, который считывает все строки из файла в виде потока.Files.find ()
, который возвращает поток, лениво заполненный путем поиска файлов в файловом дереве, коренящемся в данном начальном файле.BufferedReader.lines ()
, которые возвращают поток, элементами которого являются строки, считываемые из этого BufferedReader.
Различные улучшения Java 8 Core API
Некоторые другие улучшения API, которые могут пригодиться, включают:
- ThreadLocal статический метод с начальным(Поставщик-поставщик) для простого создания экземпляров.
- Интерфейс компаратора был расширен множеством стандартных и статических методов для естественного упорядочения, обратного порядка и т.д.
- методы min(), max() и sum() в классах целочисленных, длинных и двойных оболочек.
- логические И(), логические() и логические методы Xor() в логическом классе.
- Zip – файл .stream() метод для получения упорядоченного потока по записям ZIP-файла. Записи отображаются в потоке в том порядке, в каком они отображаются в центральном каталоге ZIP-файла.
- Несколько полезных методов в математическом классе.
добавлена команда jjs
для вызова движка Nashorn.добавлена команда jdeps
для анализа файлов классов- Мост JDBC-ODBC был удален.
- Пространство памяти PermGen было удалено
Это все для функций Java 8 с примерами программ. Если я пропустил некоторые важные функции Java 8, пожалуйста, дайте мне знать в комментариях.