Home > наука, программирование > Будущее Java – грядущие новшества Java 8

Будущее Java – грядущие новшества Java 8

December 13th, 2011 Leave a comment Go to comments

По результатам выступлений на конференции “Сиклум Java Субботник” и Встречи JUG KPI (посвященных лямбда выражениям (функциям) в Java), несколько небольших примеров и пояснений к презентации “Будущее Java, грядущие новшества Java 8” (видео доклада), а также исходный код приводимых примеров.

Для начала стоит предупредить, что все излагаемое в презентации может измениться; не думаю, правда, что радикально, но все же. Так, к примеру, за время, прошедшее между двумя докладами, defender methods успели окончательно сменить название на virtual extension methods.

И второе, те примеры кода, которые приводились во время презентации, работают! Точнее, почти работают… Вы можете скачать предварительную версию JDK 8 с поддержкой лямбда-выражений и начать экспериментировать.

  • Virtual extension methods

Итак, предположим, мы имеем интерфейс NewInterface и хотим в него добавить новый метод void test() без необходимости его реализации в классах, которые реализуют данный интерфейс. Для этого в третьей строке, после описания метода, добавляем ключевое слово default и указываем имя класса, а также статического метода в данном классе, который будет вызываться в случае обращения к методу void test().

interface NewInterface{
void test2();
void test() default DefaultClass.test;
          //default { DefaultClass.test(this);};
}

Код в третьей строке и будет тем самым virtual extension method.

В одном из описаний упоминается, что к методу вызываемому по умолчанию, после слова default можно обращаться так, как сделано в 4 строке, но в данный момент такой код не компилируется.

Класс DefaultClass выглядит следующим образом:

class DefaultClass {
    public static void test(NewInterface ni){
        System.out.println("Default Hello");
    }
}

Теперь можно написать класс NewClass реализующий наш интерфейс NewInterface:

class NewClass implements NewInterface{
	public void test2(){
		System.out.println("My Hello");
	}
}

Метод test2() “нормальный”, поэтому его мы обязаны реализовать. Если мы реализуем и метод void test(), то при обращении к нему будет вызываться данная реализации, иначе – дефолтная реализация из класса DefaultClass.

Протестируем то, что получилось, выполнив следующий код:

public class Java8DefenderTest {
    public static void main(String[] args) {
        NewInterface ni = new NewClass();
		ni.test2();
		ni.test();
	}
}

Компиляция проходит без ошибок, а вот при запуске получаем следующее:

My Hello
Exception in thread "main" java.lang.AbstractMethodError: java8defender.NewClass.test()V
at java8defender.Java8DefenderTest.main(Java8DefenderTest.java:26)

И с этим, к сожалению, я пока не знаю что делать :( Можно было бы предположить, что поддержка virtual extension methods еще не реализована, но новые методы по работе с коллекциями их уже успешно используют.

С тем, как с помощью virtual extension methods реализовать множественное наследование поведения, вы можете ознакомиться на слайде 11 презентации.

Дополнительную информацию о virtual extension methods можно почерпнуть из документа “Interface evolution via virtual extension methods”, а для любителей дискретной математики и формальных грамматик из “Featherweight Defenders: A formal model for virtual extension methods in Java”.

  • Лямбда-выражения

Говоря короток, лямбда-выражение – это удобный способ реализации анонимного класса на основе интерфейса, содержащего единственный метод. Интерфейс содержащий единственный метод называется функциональным интерфейсом (functional interface). К примеру, такие интерфейсы как Runnable, Callable, EventHandler и Comparator являются функциональными.

Рассмотрим пример. Пусть у нас есть следующий интерфейс:

interface F1{
	int add(int x, int y);
 }

Его реализация с помощью анонимного класса и вызов метода add(int x, int y) будут следующими:

F1 fun = new F1(){
	public int add(int x, int y){
		return x+y;
	}
};
fun.add(3,5);

С помощью лямбда-выражения данный анонимный класс переписывается следующим образом:

F1 func1 = (a,b) -> a+b;
int i = 5, j = 13;
func1.add(i,j);

В круглых скобках указываются входные параметры (в соответствии с параметрами метода), а после стрелки – тело (реализация) того самого единственного метода, который был объявлен в интерфейсе. При желании его (тело) можно заключить в фигурные скобки и написать return:

F1 func1 = (a,b) -> {
        System.out.println("a=" + a);
        System.out.println("b=" + b);
        return a+b;
}
int i = 5, j = 13;
func1.add(i,j);

В блоке кода может быть реализована и любая другая функциональность, а также могут выполняться обращения к переменным за пределами лямбда-выражения. Ограничение: внешние переменные должны быть т.н. effectively final, то есть вести себя как переменная перед которой стоял бы модификатор final.

Основное значение и преимущество лямбда-выражений состоит в том, что их можно передавать в качестве параметров в методы в удобном компактном виде (пример на слайде 26). Так же планируется, что в будущем можно будет в качестве параметров передавать ссылки на методы и конструкторы, без их непосредственного вызова. К примеру:

String::isEmpty
"abc"::length
Person::getLastName
ArrayList<String>::new

Но на данный момент эта функциональность еще не работает.

Детальнее про лямбда-выражения можно почитать в проекте Lambda Specification либо в более короткой статье State of the Lambda (кстати обнаружил ее новую версию лишь сейчас).

  • Functional Collection Patterns

Судя по тому, что написано в JDK Enhancement Proposals (JEP), virtual extension methods и лямбда-выражения добавляются в Java с целью введение функциональных методов (функций высшего порядка) по работе с коллекциями – Functional Collection Patterns. Эти методы: filter, map, reduce (свертка), а так же целый ряд других, уже добавлены в интерфейс Iterable.java с использованием virtual extension methods. Данные методы будут работать с коллекциями и в качестве параметров будут принимать лямбда-выражения, которые и будут определять, чего именно мы хотим добиться от нашей коллекции:

List<Student> students = ...
double highestScore =
    students.filter(s -> s.getGradYear() == 2010 || s.getGradYear() == 2011)
        .map(s -> s.getScore())
	  //.map(Student::getScore) //либо указатель на функцию, пока не работает
	    .reduce(0.0, (left, right) -> (left > right) ? left : right );

Но основной целью является даже не добавление методов по работе с коллекциями, а добавление параллельных версий этих методов, оптимизированных для оптимальной работы с большими объемами данных на многоядерных (многопроцессорных) системах. Параллелизм будет основываться на подходе «divide-and-conquer» с использование Fork-Join framework из Java 7. Загляните в исходный код ParallelIterables.java и увидите там универсальный алгоритм для параллельной работы с коллекциями.

К сожалению, статей и материалов по данной тематике я не нашел. Разве только презентация Brian Goetz “Language / Library / VM co-evolution in Java SE 8” с конференции Devoxx 2011. Ну а так же исходный код будущей Java SE 8 ;)

Так что пока ждемс …

Tags: ,
  1. L
    January 13th, 2012 at 18:32 | #1

    Если я правильно понимаю, это Java-реализация того, что появилось в C# 3.0: там это называется просто методами расширения (и к виртуальности не имеют никакого отношения), а также лямбда-функциями. И если мои суждения верны, то решение в Java 8 имеет как минимум три недостатка:

    * Жёсткая привязка к интерфейсами – т.е., придумать что-либо в духе Object.dumpToStdout() не представляется возможным.

    * Невозможность написания расширения, если нет возможности изменить сам интерфейс;

    * Интерфейс без надобности сомнительно расширяется утилитными методами, без которых он вполне и так может существовать. Тот же Iterable не обязан иметь метод isEmpty() — оно ему попросту не нужно. В C# такую проблему решили по-другому и программист в праве сам решать, когда ему может понадобиться метод расширения: он просто подключает к своему исходному коду классы с методами расширения как “плагины”, при этом не меняя классов из стандартной библиотеки.

    Поправьте, пожалуйста, если я ошибаюсь.

  2. L
    January 13th, 2012 at 19:02 | #2

    Или они “виртуальные” потому, что там есть возможность потом переопределить их в подклассе?

  3. L
    January 13th, 2012 at 19:15 | #3

    Всё, понял: в “шарпе” это реализуется на этапе компиляции и компилятору известно о методе расширения. А в Java эту проблему решили перенести на рантайм. Интересно. )))

  4. January 14th, 2012 at 19:01 | #4

    Основная цель виртуальных методов – позволить добавлять новые методы в существующие интерфейсы с сохранением бинарной совместимости и совместимости исходного кода. Других целей на виртуальные методы создатели пока не возлагают.
    То есть, все три указанных недостатка верны. Но я бы не очень соотносил “виртуальные методы” и “методами расширения”, так-как они предназначены для решения разных задач.
    Введение виртуальных методов не предназначено для расширения функциональности классов, они лишь снимают ограничение на то, что класс должен реализовывать все методы интерфейса.
    Это более подробно объясняет в своем докладе Brian Goetz, автор данных нововведений: http://www.parleys.com/#st=5&id=2633

  5. L
    January 14th, 2012 at 20:35 | #5

    Да, я повёлся на термин “extension method”, не уделив этому должного внимания, основываясь на терминологии C#. На самом деле всё куда более глубоко, чем кажется. Спасибо за корректировку. :)

  1. No trackbacks yet.

*