Table of Contents
- Introduction to Method References
- Types of Method References
- Static Method References
- Instance Method References
- Arbitrary Object Method References
- Constructor References
- Method References vs Lambda Expressions
- Best Practices
- Common Use Cases
- Complete Examples
Introduction to Method References
Method references are a shorthand notation of lambda expressions to call methods. They provide a way to refer to methods without executing them, making code more readable and concise.
Syntax:
ClassName::methodName
Method References vs Lambda Expressions:
// Lambda expression Function<String, Integer> lambda = s -> Integer.parseInt(s); // Method reference Function<String, Integer> methodRef = Integer::parseInt;
Types of Method References
There are four types of method references in Java:
- Reference to static method -
ClassName::staticMethodName - Reference to instance method of particular object -
object::instanceMethodName - Reference to instance method of arbitrary object -
ClassName::instanceMethodName - Reference to constructor -
ClassName::new
Static Method References
Basic Static Method References:
import java.util.*; import java.util.function.*; public class StaticMethodReferences { // Static methods for demonstration public static boolean isEven(int number) { return number % 2 == 0; } public static String toUpperCase(String str) { return str.toUpperCase(); } public static int square(int number) { return number * number; } public static void print(String message) { System.out.println(message); } public static void main(String[] args) { // 1. Using static method references with different functional interfaces // Function<T, R> - takes T, returns R Function<String, Integer> parseInt = Integer::parseInt; System.out.println("Parsed integer: " + parseInt.apply("123")); // Predicate<T> - takes T, returns boolean Predicate<Integer> isEvenPredicate = StaticMethodReferences::isEven; System.out.println("Is 10 even? " + isEvenPredicate.test(10)); // Function<T, R> with custom method Function<String, String> toUpper = StaticMethodReferences::toUpperCase; System.out.println("Uppercase: " + toUpper.apply("hello")); // UnaryOperator<T> - takes T, returns T (subtype of Function) UnaryOperator<Integer> squareOperator = StaticMethodReferences::square; System.out.println("Square of 5: " + squareOperator.apply(5)); // Consumer<T> - takes T, returns void Consumer<String> printer = StaticMethodReferences::print; printer.accept("Hello from method reference!"); // 2. Using with streams List<String> numbers = Arrays.asList("1", "2", "3", "4", "5"); // Convert strings to integers using method reference List<Integer> integers = numbers.stream() .map(Integer::parseInt) // Static method reference .toList(); System.out.println("Parsed integers: " + integers); // Filter even numbers List<Integer> evenNumbers = integers.stream() .filter(StaticMethodReferences::isEven) .toList(); System.out.println("Even numbers: " + evenNumbers); // Square each number List<Integer> squares = integers.stream() .map(StaticMethodReferences::square) .toList(); System.out.println("Squares: " + squares); } } Output:
Parsed integer: 123 Is 10 even? true Uppercase: HELLO Square of 5: 25 Hello from method reference! Parsed integers: [1, 2, 3, 4, 5] Even numbers: [2, 4] Squares: [1, 4, 9, 16, 25]
Common Built-in Static Method References:
import java.util.*; import java.util.function.*; public class BuiltInStaticReferences { public static void main(String[] args) { List<String> strings = Arrays.asList("1", "2", "3", "4", "5"); // Common static method references // Integer.parseInt(String) Function<String, Integer> parseInt = Integer::parseInt; // String.valueOf(Object) Function<Object, String> valueOf = String::valueOf; // Math.max(int, int) IntBinaryOperator max = Math::max; // Math.pow(double, double) DoubleBinaryOperator pow = Math::pow; // System.out.println(String) Consumer<String> printer = System.out::println; // Examples List<Integer> numbers = strings.stream() .map(Integer::parseInt) .toList(); System.out.println("Numbers: " + numbers); int maximum = numbers.stream() .reduce(0, Math::max); System.out.println("Max: " + maximum); // Using method references in different contexts BiFunction<Integer, Integer, Integer> maxFunction = Math::max; System.out.println("Max of 10 and 20: " + maxFunction.apply(10, 20)); // Method reference for sorting List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana"); names.sort(String::compareToIgnoreCase); System.out.println("Sorted names: " + names); } } Output:
Numbers: [1, 2, 3, 4, 5] Max: 5 Max of 10 and 20: 20 Sorted names: [Alice, Bob, Diana, John]
Instance Method References
Reference to Instance Method of Particular Object:
import java.util.*; import java.util.function.*; class StringProcessor { private String prefix; public StringProcessor(String prefix) { this.prefix = prefix; } public String process(String input) { return prefix + input.toUpperCase(); } public boolean containsWord(String sentence, String word) { return sentence.contains(word); } public void printWithTimestamp(String message) { System.out.println(new Date() + ": " + message); } } class Calculator { public int add(int a, int b) { return a + b; } public int multiply(int a, int b) { return a * b; } } public class InstanceMethodReferences { public static void main(String[] args) { // 1. Reference to instance method of specific object StringProcessor processor = new StringProcessor("PREFIX: "); // Using method reference for instance method Function<String, String> processorFunction = processor::process; System.out.println(processorFunction.apply("hello")); // Using method reference that takes multiple parameters BiFunction<String, String, Boolean> containsFunction = processor::containsWord; System.out.println("Contains 'Java': " + containsFunction.apply("I love Java programming", "Java")); // Consumer with instance method reference Consumer<String> printer = processor::printWithTimestamp; printer.accept("Hello World!"); // 2. Using with Calculator Calculator calculator = new Calculator(); IntBinaryOperator adder = calculator::add; IntBinaryOperator multiplier = calculator::multiply; System.out.println("10 + 20 = " + adder.applyAsInt(10, 20)); System.out.println("10 * 20 = " + multiplier.applyAsInt(10, 20)); // 3. Using with streams and specific objects List<String> words = Arrays.asList("apple", "banana", "cherry", "date"); // Using instance method reference in map List<String> processedWords = words.stream() .map(processor::process) .toList(); System.out.println("Processed words: " + processedWords); // 4. Using System.out instance List<String> messages = Arrays.asList("Error", "Warning", "Info"); messages.forEach(System.out::println); } } Output:
PREFIX: HELLO Contains 'Java': true Wed Jan 10 14:30:00 IST 2024: Hello World! 10 + 20 = 30 10 * 20 = 200 Processed words: [PREFIX: APPLE, PREFIX: BANANA, PREFIX: CHERRY, PREFIX: DATE] Error Warning Info
Arbitrary Object Method References
Reference to Instance Method of Arbitrary Object:
import java.util.*; import java.util.function.*; public class ArbitraryObjectMethodReferences { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana", "Charlie"); // 1. Reference to instance method of arbitrary object // The first parameter becomes the target object // Equivalent to: (str1, str2) -> str1.compareToIgnoreCase(str2) Comparator<String> ignoreCaseComparator = String::compareToIgnoreCase; names.sort(ignoreCaseComparator); System.out.println("Sorted ignoring case: " + names); // 2. Using with map - String::toUpperCase // Equivalent to: str -> str.toUpperCase() List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .toList(); System.out.println("Uppercase names: " + upperCaseNames); // 3. Using with filter - String::isEmpty // Equivalent to: str -> str.isEmpty() List<String> strings = Arrays.asList("", "Hello", "", "World", ""); List<String> nonEmpty = strings.stream() .filter(String::isEmpty) .toList(); System.out.println("Empty strings: " + nonEmpty); // 4. Using String::length // Equivalent to: str -> str.length() List<Integer> nameLengths = names.stream() .map(String::length) .toList(); System.out.println("Name lengths: " + nameLengths); // 5. More complex examples with custom objects List<Person> people = Arrays.asList( new Person("John", 25), new Person("Alice", 30), new Person("Bob", 22), new Person("Diana", 28) ); // Using Person::getName List<String> personNames = people.stream() .map(Person::getName) .toList(); System.out.println("Person names: " + personNames); // Using Person::getAge List<Integer> ages = people.stream() .map(Person::getAge) .toList(); System.out.println("Ages: " + ages); // Sorting by age using Person::getAge people.sort(Comparator.comparing(Person::getAge)); System.out.println("Sorted by age: " + people); // 6. Method references with multiple parameters List<String> words = Arrays.asList("Apple", "Banana", "Cherry"); // Using method reference in reduce Optional<String> concatenated = words.stream() .reduce(String::concat); System.out.println("Concatenated: " + concatenated.orElse("")); } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return name + "(" + age + ")"; } } Output:
Sorted ignoring case: [Alice, Bob, Charlie, Diana, John] Uppercase names: [ALICE, BOB, CHARLIE, DIANA, JOHN] Empty strings: [, , ] Name lengths: [5, 3, 7, 5, 4] Person names: [John, Alice, Bob, Diana] Ages: [25, 30, 22, 28] Sorted by age: [Bob(22), John(25), Diana(28), Alice(30)] Concatenated: AppleBananaCherry
Constructor References
Reference to Constructors:
import java.util.*; import java.util.function.*; class Product { private String name; private double price; public Product(String name) { this.name = name; this.price = 0.0; } public Product(String name, double price) { this.name = name; this.price = price; } public Product() { this.name = "Unknown"; this.price = 0.0; } @Override public String toString() { return String.format("Product{name='%s', price=%.2f}", name, price); } } class Person { private String name; public Person(String name) { this.name = name; } @Override public String toString() { return "Person{name='" + name + "'}"; } } public class ConstructorReferences { public static void main(String[] args) { // 1. Reference to no-argument constructor Supplier<Product> productSupplier = Product::new; Product defaultProduct = productSupplier.get(); System.out.println("Default product: " + defaultProduct); // 2. Reference to constructor with parameters Function<String, Product> productCreator = Product::new; Product laptop = productCreator.apply("Laptop"); System.out.println("Created product: " + laptop); // 3. Reference to constructor with multiple parameters BiFunction<String, Double, Product> productCreatorWithPrice = Product::new; Product expensiveLaptop = productCreatorWithPrice.apply("Gaming Laptop", 1999.99); System.out.println("Expensive product: " + expensiveLaptop); // 4. Using constructor references with streams List<String> productNames = Arrays.asList("Mouse", "Keyboard", "Monitor", "CPU"); // Create products from names List<Product> products = productNames.stream() .map(Product::new) .toList(); System.out.println("Products from names: " + products); // 5. Using with different types List<String> personNames = Arrays.asList("Alice", "Bob", "Charlie"); List<Person> people = personNames.stream() .map(Person::new) .toList(); System.out.println("People: " + people); // 6. Array constructor references IntFunction<int[]> intArrayCreator = int[]::new; int[] intArray = intArrayCreator.apply(5); System.out.println("Int array length: " + intArray.length); Function<Integer, String[]> stringArrayCreator = String[]::new; String[] stringArray = stringArrayCreator.apply(3); System.out.println("String array length: " + stringArray.length); // 7. Complex example with custom functional interface interface TriFunction<A, B, C, R> { R apply(A a, B b, C c); } class TripleProduct { private String name; private double price; private String category; public TripleProduct(String name, double price, String category) { this.name = name; this.price = price; this.category = category; } @Override public String toString() { return String.format("TripleProduct{name='%s', price=%.2f, category='%s'}", name, price, category); } } TriFunction<String, Double, String, TripleProduct> tripleCreator = TripleProduct::new; TripleProduct triple = tripleCreator.apply("Tablet", 499.99, "Electronics"); System.out.println("Triple product: " + triple); } } Output:
Default product: Product{name='Unknown', price=0.00} Created product: Product{name='Laptop', price=0.00} Expensive product: Product{name='Gaming Laptop', price=1999.99} Products from names: [Product{name='Mouse', price=0.00}, Product{name='Keyboard', price=0.00}, Product{name='Monitor', price=0.00}, Product{name='CPU', price=0.00}] People: [Person{name='Alice'}, Person{name='Bob'}, Person{name='Charlie'}] Int array length: 5 String array length: 3 Triple product: TripleProduct{name='Tablet', price=499.99, category='Electronics'} Method References vs Lambda Expressions
Comparison and When to Use:
import java.util.*; import java.util.function.*; public class MethodRefVsLambda { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana"); System.out.println("Original list: " + names); // Scenario 1: Static method reference vs lambda // Using lambda List<String> upperCaseLambda = names.stream() .map(s -> s.toUpperCase()) .toList(); // Using method reference (better) List<String> upperCaseMethodRef = names.stream() .map(String::toUpperCase) .toList(); System.out.println("Uppercase (lambda): " + upperCaseLambda); System.out.println("Uppercase (method ref): " + upperCaseMethodRef); // Scenario 2: Instance method reference vs lambda String prefix = "Hello "; // Using lambda List<String> greetingsLambda = names.stream() .map(name -> prefix + name) .toList(); // Using method reference with custom method Greeter greeter = new Greeter(prefix); List<String> greetingsMethodRef = names.stream() .map(greeter::greet) .toList(); System.out.println("Greetings (lambda): " + greetingsLambda); System.out.println("Greetings (method ref): " + greetingsMethodRef); // Scenario 3: When to prefer lambda over method reference List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // Complex transformation - lambda is better List<String> complexLambda = numbers.stream() .map(n -> { String result = "Number: " + n; if (n % 2 == 0) { result += " (even)"; } else { result += " (odd)"; } return result; }) .toList(); System.out.println("Complex transformation: " + complexLambda); // Simple transformation - method reference is better List<Integer> squaresMethodRef = numbers.stream() .map(MethodRefVsLambda::square) .toList(); System.out.println("Squares: " + squaresMethodRef); // Scenario 4: Constructor references // Using lambda Supplier<List<String>> listSupplierLambda = () -> new ArrayList<>(); // Using constructor reference (better) Supplier<List<String>> listSupplierMethodRef = ArrayList::new; List<String> newList = listSupplierMethodRef.get(); newList.add("New element"); System.out.println("New list: " + newList); } public static int square(int n) { return n * n; } } class Greeter { private String prefix; public Greeter(String prefix) { this.prefix = prefix; } public String greet(String name) { return prefix + name; } } Output:
Original list: [John, Alice, Bob, Diana] Uppercase (lambda): [JOHN, ALICE, BOB, DIANA] Uppercase (method ref): [JOHN, ALICE, BOB, DIANA] Greetings (lambda): [Hello John, Hello Alice, Hello Bob, Hello Diana] Greetings (method ref): [Hello John, Hello Alice, Hello Bob, Hello Diana] Complex transformation: [Number: 1 (odd), Number: 2 (even), Number: 3 (odd), Number: 4 (even), Number: 5 (odd)] Squares: [1, 4, 9, 16, 25] New list: [New element]
Best Practices
1. Prefer Method References for Simple Cases:
// ✅ Good - method reference is cleaner list.stream().map(String::toUpperCase).toList(); // ❌ Less readable - lambda is unnecessary list.stream().map(s -> s.toUpperCase()).toList();
2. Use Lambda for Complex Logic:
// ✅ Good - lambda for complex logic list.stream().map(s -> { if (s == null) return "NULL"; return s.trim().toUpperCase(); }).toList(); // ❌ Bad - don't create complex method just to use method reference 3. Choose Descriptive Method Names:
// ✅ Good - descriptive method names list.stream().filter(ValidationUtils::isValidEmail).toList(); // ❌ Avoid - unclear method names list.stream().filter(Utils::check).toList();
4. Be Consistent:
// ✅ Good - consistent usage list.stream() .map(String::trim) .filter(String::isEmpty) .forEach(System.out::println); // ❌ Inconsistent - mixing styles unnecessarily list.stream() .map(s -> s.trim()) .filter(str -> str.isEmpty()) .forEach(x -> System.out.println(x));
Common Use Cases
1. Stream Processing:
import java.util.*; import java.util.stream.*; public class StreamProcessingExamples { public static void main(String[] args) { List<String> names = Arrays.asList("John", "Alice", "", "Bob", "", "Diana"); // Common stream operations with method references // 1. Filtering List<String> nonEmpty = names.stream() .filter(String::isEmpty) .toList(); System.out.println("Empty strings: " + nonEmpty); // 2. Mapping List<String> upperCase = names.stream() .filter(s -> !s.isEmpty()) .map(String::toUpperCase) .toList(); System.out.println("Uppercase names: " + upperCase); // 3. Sorting List<String> sorted = names.stream() .filter(s -> !s.isEmpty()) .sorted(String::compareToIgnoreCase) .toList(); System.out.println("Sorted names: " + sorted); // 4. Collection to map List<Person> people = Arrays.asList( new Person("John", 25), new Person("Alice", 30), new Person("Bob", 22) ); Map<String, Integer> nameToAge = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge )); System.out.println("Name to age map: " + nameToAge); // 5. Grouping Map<Integer, List<Person>> peopleByAge = people.stream() .collect(Collectors.groupingBy( Person::getAge )); System.out.println("People by age: " + peopleByAge); // 6. Method reference in reduce Optional<String> concatenated = names.stream() .filter(s -> !s.isEmpty()) .reduce(String::concat); System.out.println("Concatenated: " + concatenated.orElse("")); } } Output:
Empty strings: [, ] Uppercase names: [JOHN, ALICE, BOB, DIANA] Sorted names: [Alice, Bob, Diana, John] Name to age map: {John=25, Alice=30, Bob=22} People by age: {22=[Bob(22)], 25=[John(25)], 30=[Alice(30)]} Concatenated: JohnAliceBobDiana 2. Event Handling and Callbacks:
import java.util.*; import java.util.function.*; public class EventHandlingExamples { public static void main(String[] args) { Button button = new Button(); TextField textField = new TextField(); // Method references for event handlers // Static method reference button.setOnClick(EventHandlingExamples::handleButtonClick); // Instance method reference TextProcessor processor = new TextProcessor(); textField.setOnInput(processor::processText); // System method reference textField.setOnError(System.err::println); // Simulate events button.simulateClick(); textField.simulateInput("Hello World"); textField.simulateError("Input too long"); // Timer example Timer timer = new Timer(); timer.scheduleRepeating(EventHandlingExamples::printTimestamp); // Simulate timer ticks timer.simulateTicks(3); } public static void handleButtonClick() { System.out.println("Button clicked!"); } public static void printTimestamp() { System.out.println("Timestamp: " + new Date()); } } // Supporting classes for event handling class Button { private Runnable onClickHandler; public void setOnClick(Runnable handler) { this.onClickHandler = handler; } public void simulateClick() { if (onClickHandler != null) { onClickHandler.run(); } } } class TextField { private Consumer<String> onInputHandler; private Consumer<String> onErrorHandler; public void setOnInput(Consumer<String> handler) { this.onInputHandler = handler; } public void setOnError(Consumer<String> handler) { this.onErrorHandler = handler; } public void simulateInput(String text) { if (onInputHandler != null) { onInputHandler.accept(text); } } public void simulateError(String error) { if (onErrorHandler != null) { onErrorHandler.accept(error); } } } class TextProcessor { public void processText(String text) { System.out.println("Processing text: " + text.toUpperCase()); } } class Timer { private Runnable onTickHandler; public void scheduleRepeating(Runnable handler) { this.onTickHandler = handler; } public void simulateTicks(int count) { for (int i = 0; i < count; i++) { if (onTickHandler != null) { onTickHandler.run(); } } } } Output:
Button clicked! Processing text: HELLO WORLD Input too long Timestamp: Wed Jan 10 14:30:00 IST 2024 Timestamp: Wed Jan 10 14:30:01 IST 2024 Timestamp: Wed Jan 10 14:30:02 IST 2024
Complete Examples
Complete Working Example:
import java.util.*; import java.util.function.*; import java.util.stream.*; public class MethodReferencesCompleteExample { public static void main(String[] args) { System.out.println("=== Method References Complete Example ===\n"); // Example 1: Static Method References staticMethodReferences(); // Example 2: Instance Method References instanceMethodReferences(); // Example 3: Arbitrary Object Method References arbitraryObjectMethodReferences(); // Example 4: Constructor References constructorReferences(); // Example 5: Real-world Use Case - Data Processing dataProcessingExample(); } public static void staticMethodReferences() { System.out.println("1. Static Method References:"); List<String> numbersAsString = Arrays.asList("1", "2", "3", "4", "5"); // Using static method references List<Integer> numbers = numbersAsString.stream() .map(Integer::parseInt) // Integer.parseInt() .toList(); System.out.println("Parsed numbers: " + numbers); // Using custom static method List<Integer> squares = numbers.stream() .map(MethodReferencesCompleteExample::square) .toList(); System.out.println("Squares: " + squares); // Using Math static methods Optional<Integer> max = numbers.stream() .reduce(Math::max); // Math.max() System.out.println("Max: " + max.orElse(0)); System.out.println(); } public static void instanceMethodReferences() { System.out.println("2. Instance Method References:"); String prefix = "Hello, "; Greeter greeter = new Greeter(prefix); List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // Using instance method reference List<String> greetings = names.stream() .map(greeter::greet) .toList(); System.out.println("Greetings: " + greetings); // Using System.out instance names.forEach(System.out::println); System.out.println(); } public static void arbitraryObjectMethodReferences() { System.out.println("3. Arbitrary Object Method References:"); List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana"); // Using arbitrary object method references List<String> upperCase = names.stream() .map(String::toUpperCase) // str -> str.toUpperCase() .toList(); System.out.println("Uppercase: " + upperCase); List<Integer> lengths = names.stream() .map(String::length) // str -> str.length() .toList(); System.out.println("Lengths: " + lengths); // Sorting with method reference names.sort(String::compareToIgnoreCase); System.out.println("Sorted: " + names); System.out.println(); } public static void constructorReferences() { System.out.println("4. Constructor References:"); List<String> productNames = Arrays.asList("Laptop", "Mouse", "Keyboard"); // Using constructor reference List<Product> products = productNames.stream() .map(Product::new) .toList(); System.out.println("Products: " + products); // Using no-arg constructor reference Supplier<List<String>> listSupplier = ArrayList::new; List<String> newList = listSupplier.get(); newList.add("New item"); System.out.println("New list: " + newList); System.out.println(); } public static void dataProcessingExample() { System.out.println("5. Real-world Use Case - Data Processing:"); List<Employee> employees = Arrays.asList( new Employee("Alice", "Engineering", 75000), new Employee("Bob", "Marketing", 65000), new Employee("Charlie", "Engineering", 80000), new Employee("Diana", "HR", 60000), new Employee("Eve", "Engineering", 90000) ); // Process data using method references System.out.println("All employees:"); employees.forEach(System.out::println); // Filter engineering department List<Employee> engineers = employees.stream() .filter(Employee::isEngineer) .toList(); System.out.println("\nEngineering team:"); engineers.forEach(System.out::println); // Get names only List<String> names = employees.stream() .map(Employee::getName) .toList(); System.out.println("\nEmployee names: " + names); // Group by department Map<String, List<Employee>> byDepartment = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment )); System.out.println("\nEmployees by department:"); byDepartment.forEach((dept, emps) -> { System.out.println(dept + ": " + emps); }); // Calculate average salary by department Map<String, Double> avgSalaryByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary) )); System.out.println("\nAverage salary by department:"); avgSalaryByDept.forEach((dept, avg) -> { System.out.printf("%s: $%.2f\n", dept, avg); }); } // Supporting static method public static int square(int n) { return n * n; } } // Supporting classes class Greeter { private String prefix; public Greeter(String prefix) { this.prefix = prefix; } public String greet(String name) { return prefix + name; } } class Product { private String name; public Product(String name) { this.name = name; } @Override public String toString() { return "Product{name='" + name + "'}"; } } class Employee { private String name; private String department; private double salary; public Employee(String name, String department, double salary) { this.name = name; this.department = department; this.salary = salary; } public String getName() { return name; } public String getDepartment() { return department; } public double getSalary() { return salary; } public boolean isEngineer() { return "Engineering".equals(department); } @Override public String toString() { return String.format("Employee{name='%-8s', dept='%-12s', salary=$%7.2f}", name, department, salary); } } Expected Output:
=== Method References Complete Example === 1. Static Method References: Parsed numbers: [1, 2, 3, 4, 5] Squares: [1, 4, 9, 16, 25] Max: 5 2. Instance Method References: Greetings: [Hello, Alice, Hello, Bob, Hello, Charlie] Alice Bob Charlie 3. Arbitrary Object Method References: Uppercase: [JOHN, ALICE, BOB, DIANA] Lengths: [4, 5, 3, 5] Sorted: [Alice, Bob, Diana, John] 4. Constructor References: Products: [Product{name='Laptop'}, Product{name='Mouse'}, Product{name='Keyboard'}] New list: [New item] 5. Real-world Use Case - Data Processing: All employees: Employee{name='Alice ', dept='Engineering', salary=$75000.00} Employee{name='Bob ', dept='Marketing ', salary=$65000.00} Employee{name='Charlie ', dept='Engineering', salary=$80000.00} Employee{name='Diana ', dept='HR ', salary=$60000.00} Employee{name='Eve ', dept='Engineering', salary=$90000.00} Engineering team: Employee{name='Alice ', dept='Engineering', salary=$75000.00} Employee{name='Charlie ', dept='Engineering', salary=$80000.00} Employee{name='Eve ', dept='Engineering', salary=$90000.00} Employee names: [Alice, Bob, Charlie, Diana, Eve] Employees by department: HR: [Employee{name='Diana ', dept='HR ', salary=$60000.00}] Engineering: [Employee{name='Alice ', dept='Engineering', salary=$75000.00}, Employee{name='Charlie ', dept='Engineering', salary=$80000.00}, Employee{name='Eve ', dept='Engineering', salary=$90000.00}] Marketing: [Employee{name='Bob ', dept='Marketing ', salary=$65000.00}] Average salary by department: HR: $60000.00 Engineering: $81666.67 Marketing: $65000.00 Key Takeaways
- Static Method References -
ClassName::staticMethodName - Instance Method References -
object::instanceMethodName - Arbitrary Object Method References -
ClassName::instanceMethodName - Constructor References -
ClassName::new
When to Use:
- Use method references when you're simply calling an existing method
- Use lambda expressions when you need complex logic or multiple statements
- Method references improve readability for simple method calls
- They make code more concise and expressive
Method references are a powerful feature that makes functional programming in Java more readable and maintainable, especially when working with streams and functional interfaces.