Вступление
Самым сладким синтаксическим сахаром, добавленным в Java до сих пор, безусловно, являются лямбда-выражения .
Java - многословный язык, который может мешать производительности и удобочитаемости. Сокращение шаблонного и повторяющегося кода всегда было популярной задачей среди разработчиков Java, и обычно требуется чистый, читаемый, краткий код.
Лямбда-выражения устранили необходимость набирать громоздкий шаблонный код, когда дело доходит до некоторых распространенных задач, позволяя разработчикам вызывать их без их принадлежности к классу и передавать их, как если бы они были объектами.
Эти выражения широко используются в Java Streams API и в среде Spring WebFlux для создания реактивных динамических приложений.
Еще одна, действительно полезная функция, добавленная в Java 8, - это ссылки на методы , которые делают лямбда-выражения намного более краткими и простыми, вызывая (ссылаясь) на методы с использованием имени метода, когда лямбда-выражение использовалось бы просто для вызова метод.
Ссылки на методы
Ссылки на методы - это, по сути, сокращенные лямбда-выражения, используемые для вызова методов.
Они состоят из двух частей:
Class::method;
И типичным примером может быть распечатка результатов, скажем, подписки на службу издателя или Java Stream:
someCodeChain.subscribe(System.out::println);
Давайте рассмотрим пример императивного кода, который затем мы превратим в функциональный код с помощью лямбда-выражений, а затем, наконец, сократим с помощью ссылок на методы.
Мы сделаем простой класс:
public class Employee {
private int id;
private String name;
private int wage;
private String position;
// Constructor, getters and setters
@Override
public String toString() {
return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
}
public int compareTo(Employee employee) {
if (this.wage <= employee.wage) {
return 1;
} else {
return -1;
}
}
}
Если мы создали этот класс в коллекции, такие как ArrayList
, мы не
могли сортировать его с помощью утилиты метод .sort()
, так как он не
реализует Comparable
интерфейс.
Что мы можем сделать, так это определить new Comparator
для этих
объектов при вызове .sort()
:
Employee emp1 = new Employee(1, "David", 1200, "Developer");
Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
Employee emp3 = new Employee(3, "Martha", 1300, "Developer");
ArrayList<Employee> employeeList = new ArrayList<>();
employeeList.add(emp1);
employeeList.add(emp2);
employeeList.add(emp3);
Collections.sort(employeeList, new Comparator<Employee>() {
public int compare(Employee emp1, Employee emp2) {
return emp1.compareTo(emp2);
}
});
System.out.println(employeeList);
Запуск этого кода даст:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Здесь анонимный класс ( Comparator
) определяет критерии сравнения.
Мы можем сделать его намного проще и короче, используя лямбда-выражение:
Collections.sort(employeeList, (e1, e2) -> e1.compareTo(e2));
Выполнение этого фрагмента кода даст:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Опять же, поскольку все, что мы делаем с этим лямбда-выражением, - это вызов одного метода, мы можем ссылаться только на этот метод:
Collections.sort(employeeList, Employee::compareTo);
И это также даст:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Типы ссылок на методы
Ссылки на методы можно использовать в нескольких различных сценариях:
- Статические методы:
Class::staticMethodName
- Методы экземпляра конкретных объектов:
object::instanceMethodName
- Методы экземпляра объектов арбитража:
Class::methodName
- Справочник конструктора:
Class::new
Давайте рассмотрим все эти типы на нескольких простых примерах.
Ссылки на статические методы
Вы можете ссылаться на любой static
метод класса, просто вызывая
содержащий его класс с именем метода.
Давайте определим класс static
методом, а затем будем ссылаться на
него из другого класса:
public class ClassA {
public static void raiseToThePowerOfTwo(double num) {
double result = Math.pow(num, 2);
System.out.println(result);
}
}
А теперь из другого класса воспользуемся static
служебным методом:
public class ClassB {
public static void main(String[] args) {
List<Double> integerList = new ArrayList<>();
integerList.add(new Double(5));
integerList.add(new Double(2));
integerList.add(new Double(6));
integerList.add(new Double(1));
integerList.add(new Double(8));
integerList.add(new Double(9));
integerList.forEach(ClassA::raiseToThePowerOfTwo);
}
}
Выполнение этого фрагмента кода даст:
25.0
4.0
36.0
1.0
64.0
81.0
Есть много классов Java, которые предлагают static
служебные методы,
которые можно здесь использовать. В нашем примере мы использовали
специальный метод, хотя и не очень полезный в данном случае.
Методы экземпляров отдельных объектов
Вы можете вызвать метод из определенного экземпляра объекта, указав метод, используя ссылочную переменную объекта.
Чаще всего это иллюстрируется настраиваемым компаратором. Мы будем
использовать тот же Employee
и тот же список, чтобы выделить разницу
между этими двумя:
public class Employee {
private int id;
private String name;
private int wage;
private String position;
// Constructor, getters and setters
@Override
public String toString() {
return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
}
public int compareTo(Employee employee) {
if (this.wage <= employee.wage) {
return 1;
} else {
return -1;
}
}
}
Теперь давайте определим CustomComparator
:
public class CustomComparator {
public int compareEntities(Employee emp1, Employee emp2) {
return emp1.compareTo(emp2);
}
}
И, наконец, давайте заполним список и отсортируем его:
Employee emp1 = new Employee(1, "David", 1200, "Developer");
Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
Employee emp3 = new Employee(3, "Martha", 1300, "Developer");
ArrayList<Employee> employeeList = new ArrayList<>();
employeeList.add(emp1);
employeeList.add(emp2);
employeeList.add(emp3);
// Initializing our CustomComparator
CustomComparator customComparator = new CustomComparator();
// Instead of making a call to an arbitrary Employee
// we're now providing an instance and its method
Collections.sort(employeeList, customComparator::compareEntities);
System.out.println(employeeList);
Запуск этого кода также даст:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Основное отличие состоит в том, что, добавляя еще один уровень через
CustomComparator
, мы можем добавить больше функций для сравнения и
убрать их из самого класса. Такой класс, как Employee
не должен быть
отягощен сложной логикой сравнения, и это приводит к более чистому и
удобочитаемому коду.
С другой стороны, иногда мы не хотим определять собственные компараторы, и их введение - это слишком хлопотно. В таких случаях мы вызываем метод из произвольного объекта определенного типа, как показано в следующем разделе.
Методы экземпляров произвольных объектов
Этот пример уже был показан в начале статьи, когда мы свели императивный подход к функциональному подходу с помощью лямбда-выражений.
Хотя, на всякий случай, поскольку этот подход используется очень часто, давайте рассмотрим другой пример:
List<Integer> integerList = new ArrayList<>();
integerList.add(new Integer(5));
integerList.add(new Integer(2));
integerList.add(new Integer(6));
integerList.add(new Integer(1));
integerList.add(new Integer(8));
integerList.add(new Integer(9));
// Referencing the non-static compareTo method from the Integer class
Collections.sort(integerList, Integer::compareTo);
// Referencing static method
integerList.forEach(System.out::print);
Выполнение этого фрагмента кода даст:
125689
Может показаться, что это то же самое, что и вызов статического метода, но это не так. Это эквивалентно вызову лямбда-выражения:
Collections.sort(integerList, (Integer a, Integer b) -> a.compareTo(b));
Здесь различие более очевидно. Если бы мы вызывали static
метод, это
выглядело бы так:
Collections.sort(integerList, (Integer a, Integer b) -> SomeClass.compare(a, b));
Ссылка на конструкторы
Вы можете ссылаться на конструктор класса так же, как и на static
метод.
Вы можете использовать ссылку на конструктор вместо создания экземпляра классического класса:
// Classic instantiation
Employee employee = new Employee();
// Constructor reference
Employee employee2 = Employe::new;
В зависимости от контекста, если присутствует несколько конструкторов, будет использоваться соответствующий, если на него есть ссылка:
Stream<Employee> stream = names.stream().map(Employee::new);
Из-за потока имен, если присутствует конструктор Employee(String name)
, он будет использоваться.
Другой способ использования ссылок на конструкторы - это когда вы хотите
отобразить поток в массив, сохранив при этом конкретный тип. Если бы вы
просто сопоставили его, а затем вызывали toArray()
, вы бы получили
массив Object
s вместо вашего конкретного типа.
Если мы попробовали, скажем:
Employee[] employeeArray = employeeList.toArray();
Конечно, мы столкнемся с ошибкой компилятора, поскольку мы .toArray()
возвращает массив Object
s. Кастинг тоже не поможет:
Employee[] employeeArray = (Employee[]) employeeList.toArray();
Но на этот раз это будет исключение времени выполнения -
ClassCastException
.
Этого можно избежать с помощью:
// Making a list of employees
List<String> employeeList = Arrays.asList("David", "Scott");
// Mapping a list to Employee objects and returning them as an array
Employee[] employeeArray = employeeList.stream().map(Employee::new).toArray(Employee[]::new);
// Iterating through the array and printing information
for (int i = 0; i < employeeArray.length; i++) {
System.out.println(employeeArray[i].toString());
}
И с этим мы получаем результат:
Name: David, Wage: 0, Position: null
Name: Scott, Wage: 0, Position: null
Заключение
Ссылки на методы - это тип лямбда-выражений, которые используются для простой ссылки на метод при их вызове. С их помощью написание кода станет намного более лаконичным и читаемым.
Лямбда-выражения познакомили разработчиков Java с более функциональным подходом к программированию, который позволяет им избегать написания подробного кода для простых операций.