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.