Czym są Method References?
Method reference to sposób na odniesienie się do istniejącej metody bez jej wywoływania. To skrócona forma lambda expression, gdy lambda tylko wywołuje jedną metodę.
Dlaczego to ważne?
Method references czynią kod jeszcze bardziej zwięzłym i czytelnym niż lambda expressions. Gdy lambda tylko przekazuje parametry do istniejącej metody, method reference eliminuje zbędny kod. To kluczowy element programowania funkcyjnego w Java 8.
Co się nauczysz:
- Jak używać operatora :: (podwójny dwukropek)
- 4 typy method references: static, instance, arbitrary object, constructor
- Kiedy używać method reference zamiast lambda expression
- Jak method references współpracują ze Stream API
- Typowe błędy i jak ich unikać
4 Typy Method References
Java 8 oferuje 4 rodzaje method references:
Typ | Składnia | Lambda Equivalent | Przykład |
---|---|---|---|
Static method | Class::staticMethod | x -> Class.staticMethod(x) | Integer::parseInt |
Instance method | object::instanceMethod | x -> object.instanceMethod(x) | System.out::println |
Arbitrary object | Class::instanceMethod | x -> x.instanceMethod() | String::length |
Constructor | Class::new | x -> new Class(x) | ArrayList::new |
Static Method References
Najprostrzy typ – odwołanie do metody statycznej:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StaticMethodReference { public static void main(String[] args) { Listnumbers = Arrays.asList("1", "2", "3", "4", "5"); // Lambda expression List lambdaResult = numbers.stream() .map(s -> Integer.parseInt(s)) .collect(Collectors.toList()); // Method reference - krótsze! List methodRefResult = numbers.stream() .map(Integer::parseInt) .collect(Collectors.toList()); System.out.println(lambdaResult); // [1, 2, 3, 4, 5] System.out.println(methodRefResult); // [1, 2, 3, 4, 5] // Inne przykłady static method references numbers.stream() .mapToDouble(Double::parseDouble) .forEach(System.out::println); } }
Instance Method References
Odwołanie do metody konkretnego obiektu:
import java.util.Arrays; import java.util.List; public class InstanceMethodReference { public static void main(String[] args) { Listwords = Arrays.asList("Java", "Python", "JavaScript", "C++"); // Lambda expression words.stream() .forEach(word -> System.out.println(word)); // Method reference do konkretnego obiektu words.stream() .forEach(System.out::println); // Własny obiekt Printer printer = new Printer("[INFO] "); // Lambda words.stream() .forEach(word -> printer.print(word)); // Method reference words.stream() .forEach(printer::print); } } class Printer { private String prefix; public Printer(String prefix) { this.prefix = prefix; } public void print(String message) { System.out.println(prefix + message); } }
Arbitrary Object Method References
Najciekawszy typ – odwołanie do metody dowolnego obiektu tego samego typu:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class ArbitraryObjectMethodReference { public static void main(String[] args) { Listwords = Arrays.asList("Java", "Stream", "API", "Lambda"); // Lambda expression List lengths1 = words.stream() .map(word -> word.length()) .collect(Collectors.toList()); // Method reference - każdy String wywołuje swoją metodę length() List lengths2 = words.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(lengths1); // [4, 6, 3, 6] System.out.println(lengths2); // [4, 6, 3, 6] // Inne przykłady words.stream() .map(String::toUpperCase) // każdy String robi toUpperCase() na sobie .forEach(System.out::println); // Sortowanie List sorted = words.stream() .sorted(String::compareToIgnoreCase) .collect(Collectors.toList()); System.out.println(sorted); // [API, Java, Lambda, Stream] } }
Constructor References
Odwołanie do konstruktora klasy:
import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import java.util.function.Function; import java.util.stream.Collectors; public class ConstructorReference { public static void main(String[] args) { // Constructor reference bez parametrów SuppliersbSupplier1 = () -> new StringBuilder(); Supplier sbSupplier2 = StringBuilder::new; StringBuilder sb1 = sbSupplier1.get(); StringBuilder sb2 = sbSupplier2.get(); // Constructor reference z parametrem List names = Arrays.asList("Anna", "Bartek", "Czesław"); Function personFactory1 = name -> new Person(name); Function personFactory2 = Person::new; List people = names.stream() .map(Person::new) .collect(Collectors.toList()); people.forEach(System.out::println); // Constructor reference dla kolekcji List copyOfNames = names.stream() .collect(Collectors.toCollection(ArrayList::new)); } } class Person { private String name; public Person(String name) { this.name = name; } @Override public String toString() { return "Person: " + name; } }
Kiedy Używać Method References?
- Lambda tylko wywołuje jedną istniejącą metodę
- Nie modyfikujesz parametrów przed przekazaniem
- Chcesz zwiększyć czytelność kodu
- Pracujesz ze Stream API
Praktyczne Przykłady ze Stream API
Method references świetnie współpracują z operacjami Stream:
import java.util.*; import java.util.stream.Collectors; public class StreamWithMethodReferences { public static void main(String[] args) { Listproducts = Arrays.asList( "laptop", "mysz", "klawiatura", "monitor", "słuchawki" ); // Pipeline pełen method references List result = products.stream() .filter(Objects::nonNull) // static method .map(String::trim) // arbitrary object method .map(String::toUpperCase) // arbitrary object method .sorted(String::compareToIgnoreCase) // arbitrary object method .collect(Collectors.toList()); result.forEach(System.out::println); // Grupowanie z method references Map > byLength = products.stream() .collect(Collectors.groupingBy(String::length)); System.out.println(byLength); // {4=[mysz], 5=[laptop], 10=[klawiatura, słuchawki], 7=[monitor]} // Znajdowanie maksimum Optional longest = products.stream() .max(Comparator.comparing(String::length)); longest.ifPresent(System.out::println); // klawiatura (lub słuchawki) } }
Typowe Błędy i Pułapki
// BŁĄD - próba wywołania metody na klasie // String.length() // Compilation error! // POPRAWNIE - method reference FunctiongetLength = String::length; // POPRAWNIE - lambda Function getLengthLambda = s -> s.length();
Wydajność jest praktycznie identyczna. JVM optymalizuje oba przypadki podobnie. Wybieraj na podstawie czytelności kodu.
Gdy robisz więcej niż tylko wywołanie metody: s -> s.length() > 5
, x -> doSomething(x, otherParam)
, lub () -> new Person("default")
.
To arbitrary object method reference. Dla każdego String z kolekcji wywoływana jest jego własna metoda length(). Nie ma jednego obiektu String.
TAK, jeśli jesteś w tej samej klasie. Method references respektują zasady widoczności Javy – private, protected, public.
Podwójny dwukropek to operator method reference wprowadzony w Java 8. Nie wywołuje metody, tylko tworzy referencję do niej.
Przydatne zasoby:
- Oracle Tutorial – Method References
- Oracle – java.util.function package
- Oracle – Stream API documentation
🚀 Zadanie dla Ciebie
Przepisz ten kod używając method references wszędzie gdzie to możliwe:
List<String> names = Arrays.asList("anna", "bartek", "czesław");
names.stream()
.filter(name -> name.length() > 4)
.map(name -> name.toUpperCase())
.sorted((a, b) -> a.compareTo(b))
.forEach(name -> System.out.println(name));
Które lambda expressions można zastąpić method references, a które nie?
Method references to potężne narzędzie Java 8 które czyni kod jeszcze bardziej zwięzłym niż lambda expressions. Operator :: staje się naturalny po kilku dniach używania. Którego typu method references używasz najczęściej?