Версия Java 8
впервые была презентована еще 18 марта 2014 года и с этого времени стремительно набирает популярность среди разработчиков. Уже написано много книг по Java 8
, которые раскрывают возможности нового API и тонкости функционального программирования. Однако в этой статье мы сделаем лишь краткий обзор основных нововведений Java 8
.
Прежде всего вам нужно скачать и установить JDK 8. Сделать это можно бесплатно с официального сайта.
Обзор возможностей Java 8
Как обычно, мы приведем несколько фрагментов кода для лучшего понимания API и немного теории, поэтому не забудьте настроить Java 8 перед тем, как тестировать приведенный в статье код.
Метод forEach()
интерфейса Iterable
Всякий раз, когда нам нужно что-то сделать с элементами какой-то коллекции, мы создаем итератор и в цикле применяем какую-то бизнес-логику к каждому элементу коллекции. В этом случае мы могли бы получить ConcurrentModificationException, если итератор не используется должным образом.
Java 8 ввела новый метод forEach()
в интерфейс java.lang.Iterable
, который избавляет нас от лишнего кода и позволяет сосредоточиться только на бизнес-логике. Метод forEach()
принимает объект java.util.function.Consumer
в качестве аргумента, который позволяет отделить бизнес-логику и дает возможность повторно использовать код.
Давайте посмотрим метод forEach()
в действии:
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 |
package ua.com.prologistic; 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) { // создаем какую-то коллекцию, например, List List<Integer> myList = new ArrayList<Integer>(); for(int i=0; i<10; i++) myList.add(i); // получаем итератор для работы в while цикле Iterator<Integer> it = myList.iterator(); while(it.hasNext()){ Integer i = it.next(); System.out.println(i); } // проходим коллекцию с помощью метода forEach с использованием анонимного класса myList.forEach(new Consumer<Integer>() { public void accept(Integer t) { System.out.println(t); } }); //обходим с помощью реализации интерфейса Consumer MyConsumer action = new MyConsumer(); myList.forEach(action); } } //Реализация интерфейса Consumer class MyConsumer implements Consumer<Integer>{ public void accept(Integer t) { System.out.println(t); } } |
Количество строк кода с использованием метода forEach()
немного больше, но мы четко отделили логику итерации от бизнес-логики и в результате получили более чистый код с возможностью повторного использования.
Java 8. default и static методы в интерфейсах
Если присмотреться к методу forEach()
, то можно заметить, что он определен в Iterable
интерфейсе. А мы то знаем, что в интерфейсах нельзя писать реализацию метода. Но начиная с Java 8
в интерфейсах можно писать методы с реализацией. Для этого нужно использовать ключевые слова default
и static
. А сейчас посмотрим на реализацию метода forEach()
в интерфейсе Iterable
:
1 2 3 4 5 6 7 |
// Реализация метода forEach в Java 8 default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } |
Мы знаем, что в Java нет множественного наследования, потому что это приводит к ромбовидной проблеме. Так что же будет с интерфейсами? Ведь они теперь все больше похожи на абстрактные классы. Все сводиться к тому, что компилятор будет бросать исключение и мы должны будем обеспечивать логику реализации в классе, реализующего интерфейсы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@FunctionalInterface public interface Interface1 { void method1(String str); default void log(String str){ System.out.println("Логи: " + str); } static void print(String str){ System.out.println("Печатаем в консоль: " + str); } // попытка переопределить метод приводит к ошибке компиляции, потому что //"Методы с ключевым словом default не может быть переопределен с класса java.lang.Object" /* default String toString(){ return "i1"; } */ } |
1 2 3 4 5 6 7 8 9 10 |
@FunctionalInterface public interface Interface2 { void method2(); default void log(String str){ System.out.println("Логгируем: " + str); } } |
Обратите внимание, что в интерфейсах есть метод log(String str)
с реализацией.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class MyClass implements Interface1, Interface2 { @Override public void method2() { } @Override public void method1(String str) { } //Класс MyClass не будет скомпилирован без реализации метода log() @Override public void log(String str){ System.out.println("Логги с MyClass:"+str); Interface1.print("abc"); } } |
Как видим, Interface1
содержит реализацию статического метода, который используется в реализации метода MyClass.log()
. А сама Java 8 использует методы с ключевыми словами default
и static
в большой степени в Collection API.
Функциональные интерфейсы и лямбда-выражения в Java 8
Если вы обратите внимание на код выше, то заметите аннотацию @FunctionalInterface
. Функциональные интерфейсы — новая концепция в Java 8. Интерфейс с одним абстрактным методом становится функциональным интерфейсом. Нам не нужно использовать аннотацию @FunctionalInterface
, чтобы сделать наш интерфейс функциональным. Эта аннотация используется для того, чтобы избежать случайного добавления абстрактных методов в функциональный интерфейс. Этим она очень похожу на аннотацию @Override
. Лучшим примером функционального интерфейса будет интерфейс j
ava.lang.Runnable
с одним абстрактным методом run()
.
Одним из главных преимуществ функционального интерфейса является возможность использовать лямбда-выражения. Мы можем создать экземпляр интерфейса с помощью анонимного класса, но код выглядит громоздким:
1 2 3 4 5 |
Runnable r = new Runnable(){ @Override public void run() { System.out.println("Что-то делаем в методе run()"); }}; |
Мы уже выучили, что функциональные интерфейсы имеют только один метод, а теперь уже знаем, что лямбда-выражения могут легко обеспечить реализацию такого метода. Для этого мы просто должны предоставить аргументы метода и бизнес-логику. Например, мы можем переписать приведенный выше код с помощью лямбда-выражения:
1 2 3 |
Runnable r1 = () -> { System.out.println("Что-то делаем в методе run()"); }; |
И так, лямбда-выражения являются легким способом создания анонимных классов функциональных интерфейсов. Использование лямбда-выражений не дает никаких преимуществ вашей программе, кроме как уменьшение количества строк кода.
Новый пакет java.util.function
был добавлен с целым букетом функциональных интерфейсов для лямбда-выражений. Сами же лямбда-выражения — это огромная тема, о которой мы поговорим в следующих статьях.
Java 8 Stream API
Java Stream API был добавлен в Java 8 для выполнения операций filter/map/reduce с коллекциями. Stream API позволяет как последовательные, так и параллельное выполнение.
Подробно об этой огромной теме читайте в отдельной статьей под названием «Полное руководство по Java Stream API«.
Java 8 Time API
В Java всегда было трудно работать с датой, временем и часовыми поясами. До сих по не было никаких стандартов или удобных API для с датой и временем в Java. Одним из лучших дополнений Java 8 является пакет java.time
, который позволит упорядочить процесс работы со временем.
Пакет Java Time API
предоставляет под-пакеты java.time.format
, который обеспечивает классы для печати/парсинга даты и времени, а также java.time.zone
, который обеспечивает поддержку временных зон и их правил изменения времени.
Следует отметить, что новый API для месяцев и дней недели построен на перечислениях (Enums
), а не целочисленных константах (пора задуматься, если вы все еще не используете перечисления).
А для тех, кто хочет сразу затестить новый Time API, хочу посоветовать первым делом обратить внимание на DateTimeFormatter
— один из наиболее полезных классов, который используется для преобразования объектов в строки.
Также изменения коснулись Многопоточности, Коллекций, Ввода/Вывода и нескольких вспомогательных классов Java Core. Подробнее об этих и других нововведениях читайте в следующих статьях на Javadevblog.com