Lambda và Stream API trong Java
1. Lambda Expressions
1.1 Khái Niệm
- Lambda là gì?:
- Biểu thức hàm ẩn danh
- Triển khai functional interface
-
Cú pháp ngắn gọn, rõ ràng
-
Functional Interface:
- Interface chỉ có một abstract method
- Được đánh dấu @FunctionalInterface
- Có thể có các default/static methods
1.2 Cú Pháp Lambda
public class LambdaExample {
public void demonstrateLambda() {
// Không tham số
Runnable runnable = () -> System.out.println("Hello Lambda");
// Một tham số
Consumer<String> consumer = s -> System.out.println(s);
// Nhiều tham số
Comparator<String> comparator =
(s1, s2) -> s1.compareTo(s2);
// Block code
Consumer<String> blockConsumer = s -> {
System.out.println("Processing: " + s);
System.out.println("Done");
};
// Method reference
Consumer<String> methodRef = System.out::println;
}
}
1.3 Built-in Functional Interfaces
public class FunctionalInterfacesExample {
public void demonstrate() {
// Function<T,R>: Nhận T, trả về R
Function<String, Integer> length = String::length;
// Predicate<T>: Nhận T, trả về boolean
Predicate<String> isEmpty = String::isEmpty;
// Consumer<T>: Nhận T, không trả về
Consumer<String> printer = System.out::println;
// Supplier<T>: Không nhận, trả về T
Supplier<String> supplier = () -> "Hello";
// BiFunction<T,U,R>: Nhận T và U, trả về R
BiFunction<String, String, String> concat =
String::concat;
}
// Sử dụng functional interfaces
public void processStrings(List<String> strings) {
// Function
strings.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// Predicate
List<String> nonEmpty = strings.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
// Consumer
strings.forEach(s -> System.out.println("Item: " + s));
}
}
2. Stream API
2.1 Stream Basics
- Stream là gì?:
- Sequence of elements
- Support sequential/parallel operations
- Lazy evaluation
-
Non-mutating operations
-
Tạo Stream:
- Từ Collection
- Từ Array
- Từ các giá trị
- Từ các nguồn khác (files, etc)
public class StreamBasics {
public void createStreams() {
// Từ Collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// Từ Array
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
// Stream.of
Stream<String> stream3 = Stream.of("a", "b", "c");
// Stream.generate
Stream<String> stream4 =
Stream.generate(() -> "element")
.limit(10);
// Stream.iterate
Stream<Integer> stream5 =
Stream.iterate(0, n -> n + 2)
.limit(10);
}
}
2.2 Intermediate Operations
- Filter:
- Lọc elements theo điều kiện
- Trả về stream mới
-
Lazy evaluation
-
Map:
- Biến đổi mỗi element
- Có thể thay đổi kiểu dữ liệu
- flatMap cho nested streams
public class StreamOperations {
public void demonstrateOperations() {
List<String> words = Arrays.asList(
"Java", "Stream", "API"
);
// Filter
List<String> longWords = words.stream()
.filter(w -> w.length() > 3)
.collect(Collectors.toList());
// Map
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
// FlatMap
List<List<Integer>> numbers = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
List<Integer> flattened = numbers.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Sorted
List<String> sorted = words.stream()
.sorted()
.collect(Collectors.toList());
// Distinct
List<Integer> distinct = Stream.of(1, 1, 2, 2, 3)
.distinct()
.collect(Collectors.toList());
}
}
2.3 Terminal Operations
- Collect:
- Chuyển stream thành collection
- Nhiều collectors có sẵn
-
Custom collector
-
Reduce:
- Kết hợp các elements
- Có thể chỉ định giá trị khởi tạo
- Associative operation
public class TerminalOperations {
public void demonstrate() {
List<String> words = Arrays.asList(
"Java", "Stream", "API"
);
// Collect to List
List<String> collected = words.stream()
.collect(Collectors.toList());
// Collect to Set
Set<String> uniqueWords = words.stream()
.collect(Collectors.toSet());
// Collect to Map
Map<String, Integer> wordLengths = words.stream()
.collect(Collectors.toMap(
w -> w,
String::length
));
// Joining
String joined = words.stream()
.collect(Collectors.joining(", "));
// Counting
long count = words.stream()
.filter(w -> w.length() > 3)
.count();
// Reduce
Optional<String> concatenated = words.stream()
.reduce((a, b) -> a + ", " + b);
// Sum
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum);
}
}
2.4 Parallel Streams
- Đặc điểm:
- Tự động parallel processing
- Sử dụng ForkJoinPool
- Phù hợp cho large datasets
public class ParallelStreamExample {
public void demonstrateParallel() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Sequential stream
int sumSequential = numbers.stream()
.reduce(0, Integer::sum);
// Parallel stream
int sumParallel = numbers.parallelStream()
.reduce(0, Integer::sum);
// Convert to parallel
int sumConverted = numbers.stream()
.parallel()
.reduce(0, Integer::sum);
}
public void processLargeData() {
// Giả sử có một list lớn
List<Transaction> transactions = getTransactions();
// Xử lý parallel
double totalAmount = transactions.parallelStream()
.filter(t -> t.getType() == Type.CREDIT)
.mapToDouble(Transaction::getAmount)
.sum();
}
}
3. Common Use Cases và Best Practices
3.1 Collection Processing
public class CollectionProcessing {
public void processCollections() {
List<Person> persons = getPersons();
// Filtering và Mapping
List<String> adultNames = persons.stream()
.filter(p -> p.getAge() >= 18)
.map(Person::getName)
.collect(Collectors.toList());
// Grouping
Map<String, List<Person>> byCountry = persons.stream()
.collect(Collectors.groupingBy(Person::getCountry));
// Partitioning
Map<Boolean, List<Person>> partitioned = persons.stream()
.collect(Collectors.partitioningBy(
p -> p.getAge() >= 18
));
// Statistics
DoubleSummaryStatistics ageStats = persons.stream()
.collect(Collectors.summarizingDouble(
Person::getAge
));
}
}
3.2 Best Practices
public class StreamBestPractices {
// 1. Sử dụng method reference khi có thể
public void useMethodReference() {
List<String> words = Arrays.asList("a", "b", "c");
// Instead of
words.stream()
.map(s -> s.toUpperCase())
.forEach(s -> System.out.println(s));
// Use
words.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
}
// 2. Tránh side effects
public void avoidSideEffects() {
List<String> words = Arrays.asList("a", "b", "c");
List<String> upper = new ArrayList<>();
// BAD: Side effect
words.stream()
.map(String::toUpperCase)
.forEach(upper::add);
// GOOD: Use collect
List<String> upperGood = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
// 3. Sử dụng parallel stream cẩn thận
public void carefulWithParallel() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// BAD: Mutable reduction with parallel
ArrayList<Integer> bad = numbers.parallelStream()
.collect(ArrayList::new,
ArrayList::add,
ArrayList::addAll);
// GOOD: Use proper collector
List<Integer> good = numbers.parallelStream()
.collect(Collectors.toList());
}
}
3.3 Performance Considerations
public class StreamPerformance {
// 1. Sử dụng appropriate operations
public void appropriateOperations() {
List<Integer> numbers = getNumbers();
// BAD: Multiple streams
int count = numbers.stream()
.filter(n -> n > 10)
.collect(Collectors.toList())
.size();
// GOOD: Use count directly
long countGood = numbers.stream()
.filter(n -> n > 10)
.count();
}
// 2. Optimize operation order
public void optimizeOrder() {
List<String> words = getWords();
// BAD: Heavy operation before filter
words.stream()
.map(this::heavyOperation)
.filter(w -> w.length() > 5)
.collect(Collectors.toList());
// GOOD: Filter before heavy operation
words.stream()
.filter(w -> w.length() > 5)
.map(this::heavyOperation)
.collect(Collectors.toList());
}
// 3. Consider parallel stream carefully
public void parallelConsiderations() {
List<Integer> numbers = getNumbers();
// Only use parallel for:
// - Large data sets
// - Independent operations
// - When overhead is worth it
if (numbers.size() > 10000) {
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
} else {
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
}
}
}