Оператор :: (Double Colon) в Java 8: от новичка до профи
Вы когда-нибудь писали код на Java 8 и видели странную конструкцию вроде System.out::println или String::length? Выглядит как магия, не так ли? На самом деле, это не магия, а один из самых элегантных инструментов современной Java — оператор двойное двоеточие (он же double colon operator или ссылка на метод).
В этой статье мы разберём оператор :: от самых основ до продвинутых приёмов. Вы научитесь писать более чистый, лаконичный и читаемый код. Поехали!
Что такое оператор :: и зачем он нужен?
Оператор :: (double colon) был введён в Java 8 как синтаксический сахар для лямбда-выражений. Если лямбда-выражение просто вызывает существующий метод, вы можете заменить его ссылкой на метод. Это делает код короче и выразительнее.
Сравните сами:
// Лямбда-выражение
list.forEach(s -> System.out.println(s));
// Ссылка на метод (оператор ::)
list.forEach(System.out::println);
Второй вариант не только короче, но и сразу говорит: "Для каждого элемента вызови метод println у System.out". Красота!
Четыре вида ссылок на метод
В Java 8 существует четыре основных типа method reference. Давайте разберём каждый с примерами.
1. Ссылка на статический метод
Синтаксис: ClassName::staticMethodName
Пример: подсчёт количества чётных чисел в списке.
import java.util.Arrays;
import java.util.List;
public class StaticMethodReference {
public static boolean isEven(Integer number) {
return number % 2 == 0;
}
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// Лямбда
long countLambda = numbers.stream()
.filter(n -> StaticMethodReference.isEven(n))
.count();
// Ссылка на статический метод
long countRef = numbers.stream()
.filter(StaticMethodReference::isEven)
.count();
System.out.println("Чётных чисел: " + countRef); // 3
}
}
Обратите внимание: мы не вызываем метод isEven — мы просто передаём ссылку на него. Java сама подставит каждый элемент списка в качестве аргумента.
2. Ссылка на метод экземпляра конкретного объекта
Синтаксис: objectReference::instanceMethodName
Этот вид используется, когда у вас уже есть конкретный объект, и вы хотите вызвать его метод.
import java.util.Arrays;
import java.util.List;
public class InstanceMethodReference {
public static void main(String[] args) {
List<String> names = Arrays.asList("Анна", "Борис", "Виктор");
// Создаём объект StringBuilder
StringBuilder sb = new StringBuilder("Имена: ");
// Лямбда
names.forEach(name -> sb.append(name).append(" "));
// Ссылка на метод экземпляра
// (но append возвращает StringBuilder, поэтому используем лямбду)
// А вот для System.out.println - идеально:
names.forEach(System.out::println);
System.out.println(sb.toString()); // Имена: Анна Борис Виктор
}
}
Классический пример — System.out::println. System.out — это конкретный объект PrintStream, а println — его метод.
3. Ссылка на метод экземпляра произвольного объекта определённого типа
Синтаксис: ClassName::instanceMethodName
Этот вид часто путают с предыдущим. Здесь мы не указываем конкретный объект — вместо этого мы говорим: "Возьми любой объект этого класса и вызови у него метод".
import java.util.Arrays;
import java.util.List;
public class ArbitraryObjectReference {
public static void main(String[] args) {
List<String> words = Arrays.asList("яблоко", "груша", "апельсин");
// Лямбда
words.stream()
.map(word -> word.toUpperCase())
.forEach(System.out::println);
// Ссылка на метод экземпляра произвольного объекта
words.stream()
.map(String::toUpperCase) // toUpperCase вызывается у каждого String
.forEach(System.out::println);
}
}
Здесь String::toUpperCase означает: "Для каждого элемента (который является String) вызови метод toUpperCase". Java сама понимает, что первый аргумент — это объект, у которого вызывается метод.
4. Ссылка на конструктор
Синтаксис: ClassName::new
Позволяет создавать новые объекты через ссылку на конструктор.
import java.util.function.Supplier;
public class ConstructorReference {
public static void main(String[] args) {
// Ссылка на конструктор без аргументов
Supplier<StringBuilder> supplier = StringBuilder::new;
// Создаём новый StringBuilder через ссылку
StringBuilder sb = supplier.get();
sb.append("Привет, мир!");
System.out.println(sb.toString()); // Привет, мир!
// Более сложный пример с массивом
// (требуется IntFunction)
java.util.function.IntFunction<String[]> arrayCreator = String[]::new;
String[] array = arrayCreator.apply(5);
System.out.println("Длина массива: " + array.length); // 5
}
}
Где применяется оператор :: на практике?
Ссылки на методы особенно полезны при работе с Stream API и функциональными интерфейсами. Рассмотрим несколько реальных сценариев.
Сортировка с Comparator
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortingExample {
public static void main(String[] args) {
List<String> cities = Arrays.asList("Москва", "Санкт-Петербург", "Казань", "Новосибирск");
// Сортировка по длине строки
cities.sort(Comparator.comparingInt(String::length));
System.out.println(cities); // [Казань, Москва, Новосибирск, Санкт-Петербург]
// Сортировка в обратном порядке
cities.sort(Comparator.comparingInt(String::length).reversed());
System.out.println(cities); // [Санкт-Петербург, Новосибирск, Москва, Казань]
}
}
Фильтрация и преобразование данных
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Анна", "Борис", "Виктор", null, "Галина");
// Убираем null, преобразуем в верхний регистр, собираем в список
List<String> processed = names.stream()
.filter(java.util.Objects::nonNull) // Ссылка на статический метод
.map(String::toUpperCase) // Ссылка на метод экземпляра
.collect(Collectors