Method References
Sau bài này, bạn sẽ:
- Hiểu được Method Reference là gì và khi nào dùng thay lambda
- Sử dụng được 4 loại method references: Static, Instance (particular/arbitrary), Constructor
- Phân biệt được Instance method of particular object vs arbitrary object
- Biết cách dùng constructor references cho factory patterns
- Áp dụng method references trong Stream operations
Bài trước: Stream Operations chi tiết — Đã học các operations để xử lý Stream. Bài này sẽ giới thiệu Method References - cách viết tắt cho lambdas.
Method Reference là gì?
Method Reference là một cách viết tắt (shorthand notation) cho lambda expression khi lambda chỉ gọi một method đã tồn tại.
Method reference sử dụng toán tử :: (double colon).
import java.util.*;
import java.util.function.*;
public class MethodReferenceIntro {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack");
// ❌ Lambda expression
names.forEach(name -> System.out.println(name));
// ✅ Method reference - ngắn gọn hơn
names.forEach(System.out::println);
// ❌ Lambda
Function<String, Integer> lengthLambda = s -> s.length();
// ✅ Method reference
Function<String, Integer> lengthRef = String::length;
}
}
- Ngắn gọn (concise): Ngắn gọn hơn lambda
- Dễ đọc (readable): Dễ đọc khi method name rõ nghĩa
- Tái sử dụng (reusable): Tận dụng methods đã có
- Ít lỗi hơn (less error-prone): Ít lỗi cú pháp hơn
4 Loại Method References
Java hỗ trợ 4 loại method references:
| Loại | Cú pháp | Lambda tương đương | Ví dụ |
|---|---|---|---|
| 1. Static method | ClassName::staticMethod | (args) -> ClassName.staticMethod(args) | Integer::parseInt |
| 2. Instance method of particular object | instance::method | (args) -> instance.method(args) | System.out::println |
| 3. Instance method of arbitrary object | ClassName::instanceMethod | (obj, args) -> obj.instanceMethod(args) | String::length |
| 4. Constructor | ClassName::new | (args) -> new ClassName(args) | ArrayList::new |
Quy tắc khớp chữ ký (Signature Matching)
Khi dùng method reference, compiler cần khớp chữ ký của method với functional interface. Quy tắc:
- Static method: Tham số của functional interface → tham số của static method
Function<String, Integer>khớpInteger.parseInt(String)✅
- Instance (particular): Tham số của functional interface → tham số của instance method
Consumer<String>khớpSystem.out.println(String)✅
- Instance (arbitrary): Tham số ĐẦU TIÊN → đối tượng gọi method, còn lại → tham số
Function<String, Integer>khớpString::lengthvì(String s) → s.length()✅BiFunction<String, String, Boolean>khớpString::startsWithvì(String s, String prefix) → s.startsWith(prefix)✅
- Constructor: Tham số → tham số constructor
Function<String, Person>khớpPerson::newnếu có constructorPerson(String)✅
Khi method được tham chiếu có nhiều bản overload, compiler chọn bản phù hợp dựa trên target type (kiểu functional interface đích). Nhưng đôi khi compiler không thể chọn và báo lỗi "reference to method is ambiguous":
// String có 2 overloads của valueOf:
// static String valueOf(Object obj)
// static String valueOf(int i)
// ... và nhiều overloads khác
// ✅ Compiler chọn đúng dựa trên target type
Function<Integer, String> f = String::valueOf; // chọn valueOf(int)
Function<Object, String> g = String::valueOf; // chọn valueOf(Object)
// ❌ Có thể gây ambiguity trong một số trường hợp
// BiFunction<char[], int, String> h = String::valueOf; // Có thể ambiguous
Khi gặp lỗi ambiguity, hãy chuyển sang dùng lambda expression để chỉ rõ method nào cần gọi.
1. Static Method Reference
Reference đến một static method của một class.
Cú pháp
ClassName::staticMethodName
Ví dụ
import java.util.*;
import java.util.function.*;
public class StaticMethodReference {
public static void main(String[] args) {
// Integer::parseInt
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5");
// ❌ Lambda
List<Integer> parsed1 = numbers.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
// ✅ Method reference
List<Integer> parsed2 = numbers.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
// Math::max
BiFunction<Integer, Integer, Integer> maxLambda = (a, b) -> Math.max(a, b);
BiFunction<Integer, Integer, Integer> maxRef = Math::max;
System.out.println(maxRef.apply(10, 20)); // 20
// Math::sqrt
Function<Double, Double> sqrtLambda = d -> Math.sqrt(d);
Function<Double, Double> sqrtRef = Math::sqrt;
// Custom static method
List<String> words = Arrays.asList("Java", "Python", "C++");
words.stream()
.map(StaticMethodReference::toUpperCaseWithPrefix)
.forEach(System.out::println);
}
// Custom static method
public static String toUpperCaseWithPrefix(String s) {
return "Language: " + s.toUpperCase();
}
}
Các ví dụ phổ biến
// String static methods
Predicate<String> isNumeric = s -> s.matches("\\d+"); // ❌ Lambda
// (No direct method reference for matches with argument)
// Collections static methods
Comparator<Integer> reverseOrder = Comparator.reverseOrder(); // Method call, not reference
// Custom utility class
class StringUtils {
public static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
public static String capitalize(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
}
List<String> words = Arrays.asList("java", "python", "c++");
// Use static method references
words.stream()
.map(StringUtils::capitalize)
.forEach(System.out::println);
2. Instance Method Reference (Particular Object)
Reference đến một instance method của một object cụ thể (đã tồn tại).
Cú pháp
objectInstance::instanceMethodName
Ví dụ
import java.util.*;
import java.util.function.*;
public class InstanceMethodOfParticularObject {
public static void main(String[] args) {
// System.out::println
List<String> names = Arrays.asList("John", "Jane", "Jack");
// ❌ Lambda
names.forEach(name -> System.out.println(name));
// ✅ Method reference
names.forEach(System.out::println);
// String instance method
String prefix = "Hello, ";
// ❌ Lambda
Function<String, String> greetLambda = name -> prefix.concat(name);
// ✅ Method reference
Function<String, String> greetRef = prefix::concat;
System.out.println(greetRef.apply("World")); // "Hello, World"
// Custom object
Printer printer = new Printer("[LOG] ");
List<String> messages = Arrays.asList("Started", "Processing", "Completed");
// ❌ Lambda
messages.forEach(msg -> printer.print(msg));
// ✅ Method reference
messages.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);
}
}
Tham chiếu qua this và super
Trong instance context, bạn có thể dùng this::method và super::method:
class Parent {
String greet(String name) {
return "Hello, " + name;
}
}
class Child extends Parent {
@Override
String greet(String name) {
return "Hi, " + name;
}
void demo() {
Function<String, String> childGreet = this::greet; // "Hi, ..."
Function<String, String> parentGreet = super::greet; // "Hello, ..."
System.out.println(childGreet.apply("An")); // Hi, An
System.out.println(parentGreet.apply("An")); // Hello, An
}
}
super::method hữu ích khi bạn muốn tham chiếu đến implementation của lớp cha trong một lambda hoặc stream pipeline.
Ví dụ với instance state
class DiscountCalculator {
private double discountRate;
public DiscountCalculator(double discountRate) {
this.discountRate = discountRate;
}
public double applyDiscount(double price) {
return price * (1 - discountRate);
}
}
public class InstanceMethodExample {
public static void main(String[] args) {
List<Double> prices = Arrays.asList(100.0, 200.0, 300.0);
DiscountCalculator calculator = new DiscountCalculator(0.2); // 20% discount
// ❌ Lambda
List<Double> discounted1 = prices.stream()
.map(price -> calculator.applyDiscount(price))
.collect(Collectors.toList());
// ✅ Method reference
List<Double> discounted2 = prices.stream()
.map(calculator::applyDiscount)
.collect(Collectors.toList());
System.out.println(discounted2); // [80.0, 160.0, 240.0]
}
}
3. Instance Method Reference (Arbitrary Object)
Reference đến một instance method của một object thuộc type chưa biết (sẽ được cung cấp trong stream).
Đây là loại khó hiểu nhất nhưng hữu ích nhất.
Cú pháp
ClassName::instanceMethodName
Sự khác biệt với loại 2
| Loại | Cú pháp | Lambda tương đương | Object |
|---|---|---|---|
| Particular object | instance::method | (args) -> instance.method(args) | Cố định (known) |
| Arbitrary object | ClassName::method | (obj, args) -> obj.method(args) | Từ parameter đầu tiên (unknown) |
Ví dụ
import java.util.*;
import java.util.function.*;
public class InstanceMethodOfArbitraryObject {
public static void main(String[] args) {
// String::length
List<String> words = Arrays.asList("Java", "Python", "C++");
// ❌ Lambda
List<Integer> lengths1 = words.stream()
.map(word -> word.length()) // 'word' là arbitrary object
.collect(Collectors.toList());
// ✅ Method reference
List<Integer> lengths2 = words.stream()
.map(String::length) // String là class, không phải instance cụ thể
.collect(Collectors.toList());
// String::toUpperCase
// ❌ Lambda
List<String> upper1 = words.stream()
.map(word -> word.toUpperCase())
.collect(Collectors.toList());
// ✅ Method reference
List<String> upper2 = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Comparator
// ❌ Lambda
words.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
// ✅ Method reference
words.sort(String::compareToIgnoreCase);
// Custom class
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 20)
);
// ❌ Lambda
List<String> names1 = people.stream()
.map(person -> person.getName())
.collect(Collectors.toList());
// ✅ Method reference
List<String> names2 = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
}
}
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; }
}
Ví dụ với BiFunction
// BiFunction<T, U, R>: (T, U) -> R
// String::concat: (String s1, String s2) -> s1.concat(s2)
BiFunction<String, String, String> concatLambda = (s1, s2) -> s1.concat(s2);
BiFunction<String, String, String> concatRef = String::concat;
String result = concatRef.apply("Hello", "World");
System.out.println(result); // "HelloWorld"
// String::startsWith: (String s, String prefix) -> s.startsWith(prefix)
BiFunction<String, String, Boolean> startsWithLambda = (s, prefix) -> s.startsWith(prefix);
BiFunction<String, String, Boolean> startsWithRef = String::startsWith;
boolean starts = startsWithRef.apply("Java", "Ja");
System.out.println(starts); // true
Khi nào dùng Arbitrary Object Reference?
Dùng khi:
- Stream operations:
map(),filter()với instance methods - Sorting:
Comparatorvới instance methods - Mapping: Transform objects sang properties
List<Employee> employees = getEmployees();
// Get all names
List<String> names = employees.stream()
.map(Employee::getName) // ✅ Arbitrary object reference
.collect(Collectors.toList());
// Sort by age
employees.sort(Comparator.comparingInt(Employee::getAge)); // ✅
// Filter active employees
List<Employee> active = employees.stream()
.filter(Employee::isActive) // ✅ Assuming boolean isActive()
.collect(Collectors.toList());
String::compareTo là loại nào?Comparator<String> comp = String::compareTo;
Nhiều người nhầm đây là static method reference vì cú pháp ClassName::method. Nhưng thực tế đây là instance method of arbitrary object (loại 3):
compareTolà instance method (không phải static)- Lambda tương đương:
(s1, s2) -> s1.compareTo(s2) s1là đối tượng gọi method (arbitrary),s2là tham số
Quy tắc phân biệt: Xem method có phải static không. Nếu KHÔNG phải static mà cú pháp là ClassName::method → đó là loại 3 (arbitrary object).
4. Constructor Reference
Reference đến một constructor để tạo object mới.
Cú pháp
ClassName::new
Ví dụ cơ bản
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class ConstructorReference {
public static void main(String[] args) {
// No-arg constructor
// ❌ Lambda
Supplier<List<String>> listSupplier1 = () -> new ArrayList<>();
// ✅ Constructor reference
Supplier<List<String>> listSupplier2 = ArrayList::new;
List<String> list = listSupplier2.get();
// Single-arg constructor
// ❌ Lambda
Function<String, Person> personCreator1 = name -> new Person(name);
// ✅ Constructor reference
Function<String, Person> personCreator2 = Person::new;
Person person = personCreator2.apply("John");
// Two-arg constructor
// ❌ Lambda
BiFunction<String, Integer, Person> personCreator3 = (name, age) -> new Person(name, age);
// ✅ Constructor reference
BiFunction<String, Integer, Person> personCreator4 = Person::new;
Person person2 = personCreator4.apply("Jane", 25);
// Stream với constructor reference
List<String> names = Arrays.asList("John", "Jane", "Jack");
// ❌ Lambda
List<Person> people1 = names.stream()
.map(name -> new Person(name))
.collect(Collectors.toList());
// ✅ Constructor reference
List<Person> people2 = names.stream()
.map(Person::new)
.collect(Collectors.toList());
}
}
class Person {
private String name;
private int age;
// Constructor 1: Single-arg
public Person(String name) {
this(name, 0);
}
// Constructor 2: Two-arg
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters, toString...
}
Array Constructor Reference
// Array constructor reference
// ❌ Lambda
Function<Integer, String[]> arrayCreator1 = size -> new String[size];
// ✅ Constructor reference
Function<Integer, String[]> arrayCreator2 = String[]::new;
String[] array = arrayCreator2.apply(10);
// Trong Stream toArray()
List<String> words = Arrays.asList("Java", "Python", "C++");
// ❌ Lambda
String[] array1 = words.stream().toArray(size -> new String[size]);
// ✅ Constructor reference
String[] array2 = words.stream().toArray(String[]::new);
Constructor reference với generic
// ✅ Constructor reference — compiler suy luận generic type
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get(); // ArrayList<String>
// ⚠️ Chú ý: generic type được suy luận từ target type
Supplier<List<Integer>> intListSupplier = ArrayList::new;
List<Integer> intList = intListSupplier.get(); // ArrayList<Integer>
// Cùng ArrayList::new nhưng khác generic type!
Factory Pattern với Constructor Reference
import java.util.*;
import java.util.function.*;
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() { System.out.println("Drawing Circle"); }
}
class Rectangle implements Shape {
public void draw() { System.out.println("Drawing Rectangle"); }
}
class ShapeFactory {
private static Map<String, Supplier<Shape>> shapeMap = new HashMap<>();
static {
// ✅ Constructor references
shapeMap.put("circle", Circle::new);
shapeMap.put("rectangle", Rectangle::new);
}
public static Shape createShape(String type) {
Supplier<Shape> supplier = shapeMap.get(type.toLowerCase());
if (supplier == null) {
throw new IllegalArgumentException("Unknown shape: " + type);
}
return supplier.get();
}
}
public class ConstructorReferenceFactory {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("circle");
circle.draw(); // Drawing Circle
Shape rectangle = ShapeFactory.createShape("rectangle");
rectangle.draw(); // Drawing Rectangle
}
}
Khi nào dùng Method Reference vs Lambda?
✅ Dùng Method Reference khi:
// 1. Lambda chỉ gọi 1 method với chính parameters của nó
list.forEach(item -> System.out.println(item)); // ❌
list.forEach(System.out::println); // ✅
// 2. Method name rõ nghĩa
numbers.stream()
.map(n -> String.valueOf(n)) // ❌
.map(String::valueOf); // ✅
// 3. Reuse existing methods
people.stream()
.map(p -> p.getName()) // ❌
.map(Person::getName); // ✅
❌ Dùng Lambda khi:
// 1. Logic phức tạp hơn một method call
list.forEach(item -> {
System.out.println("Processing: " + item);
processItem(item);
}); // ✅ Lambda
// 2. Cần thêm arguments
numbers.stream()
.map(n -> Math.pow(n, 2)) // ✅ Lambda (thêm argument 2)
// Không thể dùng Math::pow vì cần 2 arguments
// 3. Cần transform arguments
words.stream()
.map(s -> s.substring(0, 3)) // ✅ Lambda (transform arguments)
// Không thể dùng String::substring trực tiếp
// 4. Dễ hiểu hơn với lambda
Predicate<String> isLong = s -> s.length() > 10; // ✅ Clear
// vs
Predicate<String> isLong = ...; // Không có method sẵn
Bảng tổng hợp 4 loại Method References
| Loại | Cú pháp | Lambda | Ví dụ | Use Case |
|---|---|---|---|---|
| Static method | Class::staticMethod | (args) -> Class.staticMethod(args) | Integer::parseInt | Static utility methods |
| Instance (particular) | obj::method | (args) -> obj.method(args) | System.out::println | Specific object's method |
| Instance (arbitrary) | Class::method | (obj, args) -> obj.method(args) | String::length | Stream mapping, comparing |
| Constructor | Class::new | (args) -> new Class(args) | ArrayList::new | Object creation, factories |
Ví dụ tổng hợp: Stream-based Data Processor
import java.util.*;
import java.util.stream.*;
class Employee {
private String name;
private String department;
private int age;
private double salary;
public Employee(String name, String department, int age, double salary) {
this.name = name;
this.department = department;
this.age = age;
this.salary = salary;
}
// Getters
public String getName() { return name; }
public String getDepartment() { return department; }
public int getAge() { return age; }
public double getSalary() { return salary; }
public boolean isHighEarner() {
return salary > 6000;
}
@Override
public String toString() {
return String.format("%s (%s, %d years, $%.0f)", name, department, age, salary);
}
}
class EmployeeUtils {
// Static method
public static boolean isAdult(Employee e) {
return e.getAge() >= 18;
}
// Static method
public static String formatName(String name) {
return name.toUpperCase();
}
}
public class MethodReferenceComprehensive {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("An", "IT", 25, 5000),
new Employee("Bình", "HR", 30, 7000),
new Employee("Cường", "IT", 28, 8000),
new Employee("Dung", "Finance", 26, 6500),
new Employee("Em", "HR", 22, 4500)
);
System.out.println("=== 1. Static Method Reference ===");
// Filter adults using static method
List<Employee> adults = employees.stream()
.filter(EmployeeUtils::isAdult) // Static method reference
.collect(Collectors.toList());
System.out.println("Adults: " + adults.size());
System.out.println("\n=== 2. Instance Method (Arbitrary Object) ===");
// Get all names
List<String> names = employees.stream()
.map(Employee::getName) // Instance method of arbitrary object
.collect(Collectors.toList());
System.out.println("Names: " + names);
// Get all departments
Set<String> departments = employees.stream()
.map(Employee::getDepartment) // Instance method reference
.collect(Collectors.toSet());
System.out.println("Departments: " + departments);
// Filter high earners using instance method
List<Employee> highEarners = employees.stream()
.filter(Employee::isHighEarner) // Instance method reference (boolean)
.collect(Collectors.toList());
System.out.println("High earners: " + highEarners.size());
System.out.println("\n=== 3. Instance Method (Particular Object) ===");
// Print all employees
System.out.println("All employees:");
employees.forEach(System.out::println); // Instance method of particular object
System.out.println("\n=== 4. Constructor Reference ===");
// Create new employees from names
List<String> newNames = Arrays.asList("Phúc", "Giang", "Hùng");
List<Employee> newEmployees = newNames.stream()
.map(name -> new Employee(name, "New", 25, 5000))
// Constructor reference không hoàn toàn fit vì cần nhiều params
.collect(Collectors.toList());
// Better example: Simple constructor
Supplier<ArrayList<Employee>> listFactory = ArrayList::new;
ArrayList<Employee> newList = listFactory.get();
System.out.println("\n=== 5. Combined Operations ===");
// Complex pipeline with multiple method references
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment, // Method reference
Collectors.averagingDouble(Employee::getSalary) // Method reference
));
System.out.println("Average salary by department:");
avgSalaryByDept.forEach((dept, avg) ->
System.out.printf(" %s: $%.2f%n", dept, avg));
System.out.println("\n=== 6. Sorting with Method References ===");
// Sort by name
List<Employee> sortedByName = employees.stream()
.sorted(Comparator.comparing(Employee::getName)) // Method reference
.collect(Collectors.toList());
System.out.println("Sorted by name: " +
sortedByName.stream().map(Employee::getName).collect(Collectors.toList()));
// Sort by salary descending
List<Employee> sortedBySalary = employees.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.limit(3)
.collect(Collectors.toList());
System.out.println("Top 3 by salary:");
sortedBySalary.forEach(System.out::println);
System.out.println("\n=== 7. Transform Names ===");
// Format names using static method
List<String> formattedNames = employees.stream()
.map(Employee::getName) // Instance method reference
.map(EmployeeUtils::formatName) // Static method reference
.collect(Collectors.toList());
System.out.println("Formatted names: " + formattedNames);
}
}
Best Practices
✅ DO
// 1. Use method references for simple method calls
list.forEach(System.out::println);
// 2. Use constructor references for object creation
Stream.of("a", "b", "c").map(String::new);
// 3. Chain method references in streams
employees.stream()
.map(Employee::getName)
.map(String::toUpperCase)
.forEach(System.out::println);
// 4. Use method references with Comparator
list.sort(Comparator.comparing(Person::getName));
❌ DON'T
// 1. Don't force method references when lambda is clearer
numbers.stream()
.map(n -> n * 2 + 1) // ✅ Lambda is clearer
// No simple method reference for this
// 2. Don't create methods just to use method references
// ❌ Unnecessary method
public static int doubleAndAddOne(int n) {
return n * 2 + 1;
}
numbers.stream().map(MyClass::doubleAndAddOne);
// ✅ Just use lambda
numbers.stream().map(n -> n * 2 + 1);
// 3. Don't use method references for complex logic
// ❌ Method reference hides complexity
list.forEach(this::complexProcessing);
// ✅ Lambda makes complexity visible
list.forEach(item -> {
// Complex multi-step processing visible here
validate(item);
transform(item);
save(item);
});
Tóm tắt
- Method Reference là shorthand cho lambda khi lambda chỉ gọi 1 method
- 4 loại: Static method, Instance method (particular/arbitrary), Constructor
- Static:
ClassName::staticMethod- cho utility methods - Instance (particular):
instance::method- cho object cụ thể - Instance (arbitrary):
ClassName::method- cho stream operations - Constructor:
ClassName::new- cho object creation - Khi nào dùng: Lambda chỉ gọi 1 method, method name rõ nghĩa
- Khi nào không: Logic phức tạp, cần transform arguments, lambda rõ ràng hơn
Bài tập
Bài 1: Convert Lambda to Method Reference
Convert các lambda expressions sau sang method references:
// 1.
list.forEach(item -> System.out.println(item));
// 2.
numbers.stream().map(n -> String.valueOf(n));
// 3.
words.stream().map(s -> s.length());
// 4.
people.stream().sorted((p1, p2) -> p1.getName().compareTo(p2.getName()));
// 5.
Stream.of("a", "b", "c").map(s -> new Person(s));
Bài 2: Identify Method Reference Types
Xác định loại method reference (1, 2, 3, hay 4):
// a.
list.forEach(System.out::println);
// b.
words.stream().map(String::toUpperCase);
// c.
numbers.stream().map(Integer::parseInt);
// d.
names.stream().map(Person::new);
// e.
Printer printer = new Printer();
list.forEach(printer::print);
Bài 3: Stream-based Data Processor
Viết stream pipeline xử lý danh sách Product:
class Product {
String name;
double price;
String category;
// Constructor, getters...
}
List<Product> products = getProducts();
// TODO: Sử dụng method references để:
// 1. Filter products với price > 100
// 2. Group by category
// 3. Get product names per category
// 4. Sort names alphabetically
// 5. Print results
Chúc mừng! Bạn đã hoàn thành Module 10: Functional Programming trong Java.
Next steps:
- Thực hành các bài tập trong từng lesson
- Áp dụng functional programming vào projects
- Tìm hiểu thêm về Stream API advanced topics
- Explore Reactive Programming với Project Reactor hoặc RxJava
Đọc thêm
- Oracle: Method References
- JLS §15.13 — Method Reference Expressions
- Effective Java — Joshua Bloch — Item 43: Prefer method references to lambdas