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();
        }
    }
}