1. Обзор
В этой быстрой статье мы обсудим оператор двойного двоеточия ( ::
) в Java 8 и рассмотрим сценарии, в которых этот оператор можно использовать.
2. От лямбда-выражений к оператору двойного двоеточия
Мы видели, что с лямбда-выражениями код может стать очень кратким.
Например, для создания компаратора достаточно следующего синтаксиса:
Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge());
Затем с выводом типа:
Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());
Но можем ли мы сделать приведенный выше код еще более выразительным и читабельным? Давайте посмотрим:
Comparator c = Comparator.comparing(Computer::getAge);
Мы использовали оператор ::
как сокращение для лямбда-выражений, вызывающих определенный метод — по имени. И, наконец, результат, конечно, еще более читаемый синтаксис.
3. Как это работает?
Проще говоря, когда мы используем ссылку на метод, целевая ссылка помещается перед разделителем ::
, а имя метода указывается после него.
Например:
Computer::getAge;
Мы рассматриваем ссылку на метод getAge,
определенный в классе Computer
.
Затем мы можем работать с этой функцией:
Function<Computer, Integer> getAge = Computer::getAge;
Integer computerAge = getAge.apply(c1);
Обратите внимание, что мы ссылаемся на функцию, а затем применяем ее к правильному аргументу.
4. Ссылки на методы
Мы можем эффективно использовать этот оператор в некоторых сценариях.
4.1. Статический метод
Во-первых, мы собираемся использовать статический служебный метод :
List inventory = Arrays.asList(
new Computer( 2015, "white", 35), new Computer(2009, "black", 65));
inventory.forEach(ComputerUtils::repair);
4.2. Метод экземпляра существующего объекта
Далее давайте рассмотрим интересный сценарий — обращение к методу существующего экземпляра объекта .
Мы собираемся использовать переменную System
. out
— объект типа PrintStream
, который поддерживает метод печати
:
Computer c1 = new Computer(2015, "white");
Computer c2 = new Computer(2009, "black");
Computer c3 = new Computer(2014, "black");
Arrays.asList(c1, c2, c3).forEach(System.out::print);
4.3. Метод экземпляра произвольного объекта определенного типа
Computer c1 = new Computer(2015, "white", 100);
Computer c2 = new MacbookPro(2009, "black", 100);
List inventory = Arrays.asList(c1, c2);
inventory.forEach(Computer::turnOnPc);
Как видите, мы ссылаемся на метод turnOnPc
не на конкретный экземпляр, а на сам тип.
В строке 4 метод экземпляра turnOnPc
будет вызываться для каждого объекта инвентаризации
.
И это, естественно, означает, что для c1
будет вызываться метод turnOnPc на экземпляре
Computer
, а для c2
на экземпляре MacbookPro
.
4.4. Суперметод конкретного объекта
Предположим, у вас есть следующий метод в суперклассе Computer :
public Double calculateValue(Double initialValue) {
return initialValue/1.50;
}
и этот в подклассе MacbookPro
:
@Override
public Double calculateValue(Double initialValue){
Function<Double, Double> function = super::calculateValue;
Double pcValue = function.apply(initialValue);
return pcValue + (initialValue/10) ;
}
Вызов метода calculateValue для экземпляра
MacbookPro
:
macbookPro.calculateValue(999.99);
также вызовет вызов calculateValue
в суперклассе Computer .
5. Ссылки на конструктор
5.1. Создать новый экземпляр
Ссылка на конструктор для создания экземпляра объекта может быть довольно простой:
@FunctionalInterface
public interface InterfaceComputer {
Computer create();
}
InterfaceComputer c = Computer::new;
Computer computer = c.create();
Что делать, если у вас есть два параметра в конструкторе?
BiFunction<Integer, String, Computer> c4Function = Computer::new;
Computer c4 = c4Function.apply(2013, "white");
Если параметров три или более, вам необходимо определить новый функциональный интерфейс:
@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
default <V> TriFunction<A, B, C, V> andThen( Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (A a, B b, C c) -> after.apply(apply(a, b, c));
}
}
Затем инициализируйте свой объект:
TriFunction <Integer, String, Integer, Computer> c6Function = Computer::new;
Computer c3 = c6Function.apply(2008, "black", 90);
5.2. Создать массив
Наконец, давайте посмотрим, как создать массив объектов Computer
из пяти элементов:
Function <Integer, Computer[]> computerCreator = Computer[]::new;
Computer[] computerArray = computerCreator.apply(5);
6. Заключение
Как мы начинаем видеть, оператор двойного двоеточия, представленный в Java 8, будет очень полезен в некоторых сценариях, особенно в сочетании с потоками.
Также очень важно взглянуть на функциональные интерфейсы, чтобы лучше понять, что происходит за кулисами.
Полный исходный код примера доступен в этом проекте GitHub — это проект Maven и Eclipse, поэтому его можно импортировать и использовать как есть.