Method References w Java 8 – Krótszy Kod Niż Lambda

TL;DR: Method references (::) to skrócona forma lambda expressions. Zamiast x -> System.out.println(x) piszesz System.out::println. Java 8 oferuje 4 typy: static methods, instance methods, arbitrary objects i constructors.

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ę.

Method reference to jak skrót na pulpicie – zamiast nawigować przez całą ścieżkę folderów (lambda), klikasz skrót (::) i od razu jesteś tam gdzie chcesz.
Method Reference – referencja do metody używająca operatora :: (podwójny dwukropek), krótszy zapis od lambda expression

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ć
Wymagania wstępne: Podstawy lambda expressions, functional interfaces (Predicate, Function, Consumer)

4 Typy Method References

Java 8 oferuje 4 rodzaje method references:

TypSkładniaLambda EquivalentPrzykład
Static methodClass::staticMethodx -> Class.staticMethod(x)Integer::parseInt
Instance methodobject::instanceMethodx -> object.instanceMethod(x)System.out::println
Arbitrary objectClass::instanceMethodx -> x.instanceMethod()String::length
ConstructorClass::newx -> 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) {
        List numbers = 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);
    }
}
Pro tip: Static method reference działa gdy lambda ma postać x -> Class.staticMethod(x). Parametr lambda staje się parametrem metody.

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) {
        List words = 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) {
        List words = 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]
    }
}
Pułapka: String::length to nie wywołanie metody na klasie String, ale na każdym obiekcie String z kolekcji!

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
        Supplier sbSupplier1 = () -> 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?

Używaj method reference gdy:

  • Lambda tylko wywołuje jedną istniejącą metodę
  • Nie modyfikujesz parametrów przed przekazaniem
  • Chcesz zwiększyć czytelność kodu
  • Pracujesz ze Stream API
Zostań przy lambda gdy: Robisz coś więcej niż tylko wywołanie metody, np. s -> s.length() > 5 lub x -> doSomething(x, extraParam)

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) {
        List products = 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

Typowy błąd: Mylenie String::length z String.length() – pierwsze to method reference, drugie to wywołanie metody
// BŁĄD - próba wywołania metody na klasie
// String.length() // Compilation error!

// POPRAWNIE - method reference
Function getLength = String::length;

// POPRAWNIE - lambda
Function getLengthLambda = s -> s.length();
Czy method references są szybsze niż lambda?

Wydajność jest praktycznie identyczna. JVM optymalizuje oba przypadki podobnie. Wybieraj na podstawie czytelności kodu.

Kiedy użyć lambda zamiast method reference?

Gdy robisz więcej niż tylko wywołanie metody: s -> s.length() > 5, x -> doSomething(x, otherParam), lub () -> new Person("default").

Jak działa String::length?

To arbitrary object method reference. Dla każdego String z kolekcji wywoływana jest jego własna metoda length(). Nie ma jednego obiektu String.

Czy mogę używać method reference z metodami private?

TAK, jeśli jesteś w tej samej klasie. Method references respektują zasady widoczności Javy – private, protected, public.

Co to znaczy operator ::?

Podwójny dwukropek to operator method reference wprowadzony w Java 8. Nie wywołuje metody, tylko tworzy referencję do niej.

Przydatne zasoby:

🚀 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?

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry