Method References in Java

Table of Contents

  1. Introduction to Method References
  2. Types of Method References
  3. Static Method References
  4. Instance Method References
  5. Arbitrary Object Method References
  6. Constructor References
  7. Method References vs Lambda Expressions
  8. Best Practices
  9. Common Use Cases
  10. 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:

  1. Reference to static method - ClassName::staticMethodName
  2. Reference to instance method of particular object - object::instanceMethodName
  3. Reference to instance method of arbitrary object - ClassName::instanceMethodName
  4. 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

  1. Static Method References - ClassName::staticMethodName
  2. Instance Method References - object::instanceMethodName
  3. Arbitrary Object Method References - ClassName::instanceMethodName
  4. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper