Ссылки на методы в Java 8

Введение Самым сладким синтаксическим сахаром [https://en.wikipedia.org/wiki/Syntactic_sugar], добавленным в Java до сих пор, безусловно, являются лямбда-выражения [/ lambda-expressions-in-java]. Java - многословный язык, который может мешать производительности и удобочитаемости. Сокращение шаблонного и повторяющегося кода всегда было популярной задачей среди разработчиков Java, и обычно требуется чистый, читаемый, краткий код. Лямбда-выражения устранили необходимость набирать громоздкую шаблонную cod

Вступление

Самым сладким синтаксическим сахаром, добавленным в 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 с более функциональным подходом к программированию, который позволяет им избегать написания подробного кода для простых операций.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus