- vừa được xem lúc

Generics và Stream trong Java

0 0 1

Người đăng: Ông Huy Thắng

Theo Viblo Asia

Generics

Trong Java là một tính năng mạnh mẽ được giới thiệu từ Java 5, cho phép bạn viết mã tổng quát hơn, giúp giảm thiểu lỗi runtime và cải thiện khả năng tái sử dụng mã.

Tại sao cần Generics?

  • An toàn kiểu dữ liệu (Type Safety): Đảm bảo rằng chỉ các kiểu dữ liệu mong muốn được sử dụng.
  • Giảm lỗi Runtime: Lỗi liên quan đến kiểu dữ liệu được phát hiện tại compile-time.
  • Tái sử dụng mã (Code Reusability): Giúp tạo các lớp, phương thức hoặc giao diện hoạt động với bất kỳ kiểu dữ liệu nào mà không cần viết lại.

Cách sử dụng Generics

1. Với Lớp

public class Box<T> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; }
} Box<String> stringBox = new Box<>();
stringBox.setValue("Hello Generics");
System.out.println(stringBox.getValue()); // Output: Hello Generics Box<Integer> integerBox = new Box<>();
integerBox.setValue(123);
System.out.println(integerBox.getValue()); // Output: 123

Type Parameters Các quy ước đặt tên tham số kiểu (type parameters) rất quan trọng để hiểu rõ về generics. Các tham số kiểu phổ biến bao gồm:

T - Type E - Element K - Key N - Number V - Value

2. Với phương thức

public class Utility { public static <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } }
} String[] stringArray = {"A", "B", "C"};
Integer[] intArray = {1, 2, 3}; Utility.printArray(stringArray); // Output: A, B, C
Utility.printArray(intArray); // Output: 1, 2, 3

3. Với Interface

public interface Pair<K, V> { K getKey(); V getValue();
} class KeyValuePair<K, V> implements Pair<K, V> { private K key; private V value; public KeyValuePair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; }
} Pair<Integer, String> pair = new KeyValuePair<>(1, "One");
System.out.println(pair.getKey()); // Output: 1
System.out.println(pair.getValue()); // Output: One

4. Với Wildcards

Wildcards được sử dụng khi bạn không biết chính xác kiểu dữ liệu.

// Unbounded Wildcard (?):
public static void printList(List<?> list) { for (Object obj : list) { System.out.println(obj); }
} // Upper Bounded Wildcard (<? extends T>): Chấp nhận các kiểu là T hoặc subclass của T.
public static void printNumbers(List<? extends Number> list) { for (Number num : list) { System.out.println(num); }
} // Lower Bounded Wildcard (<? super T>): Chấp nhận các kiểu là T hoặc superclass của T.
public static void addNumbers(List<? super Integer> list) { list.add(10); list.add(20);
}

5. Generics và Erasure

  • Generics chỉ tồn tại ở thời điểm biên dịch (compile-time).
  • Tại runtime, Java sử dụng type erasure để thay thế tất cả các tham chiếu kiểu bằng Object hoặc một kiểu biên dịch được xác định.
List<String> list = new ArrayList<>(); // Sau khi biên dịch, đoạn mã trên sẽ được chuyển thành:
List list = new ArrayList();

Một số lưu ý khi dùng Generics:

  • Không thể sử dụng Primitive Types (như int, double) trong Generics. Bạn cần sử dụng các Wrapper Class như Integer, Double.
  • Không thể tạo đối tượng của kiểu tham số: T obj = new T(); là không hợp lệ.
  • Không thể sử dụng instanceof với tham số kiểu: if (obj instanceof T) là không hợp lệ.

Stream

Streams trong Java là một phần của Java 8, thuộc gói java.util.stream. Chúng hỗ trợ thao tác với dữ liệu theo phong cách lập trình hàm, giúp xử lý tập hợp dữ liệu (Collections) một cách dễ dàng, gọn gàng và hiệu quả hơn.

Đặc điểm của Stream:

  1. Không lưu trữ dữ liệu: Stream không lưu trữ dữ liệu mà chỉ xử lý dữ liệu theo từng bước.
  2. Dựa trên pipeline (dòng chảy): Bao gồm 3 giai đoạn chính:
  • Tạo Stream
  • Thực hiện các thao tác trung gian (Intermediate Operations).
  • Thực hiện các thao tác kết thúc (Terminal Operations).
  1. Lazy Evaluation: Các thao tác trung gian chỉ được thực thi khi có thao tác kết thúc.
  2. Không thay đổi nguồn dữ liệu: Stream không làm thay đổi Collection hoặc mảng ban đầu.

Tạo Stream

// Collection
List<String> names = List.of("Alice", "Bob", "Charlie");
Stream<String> stream = names.stream(); // Array
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array); // Giá trị
Stream<String> stream = Stream.of("X", "Y", "Z"); // Vô hạn
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2); // 0, 2, 4, ...

Các thao tác trên Stream

1. Intermediate Operations (Thao tác trung gian)

// filter(): Lọc các phần tử thỏa mãn điều kiện.
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream() .filter(n -> n % 2 == 0) .forEach(System.out::println); // Output: 2, 4 // map(): Áp dụng một hàm lên từng phần tử và trả về Stream mới.
List<String> names = List.of("alice", "bob", "charlie");
names.stream() .map(String::toUpperCase) .forEach(System.out::println); // Output: ALICE, BOB, CHARLIE // sorted(): Sắp xếp các phần tử.
List<String> names = List.of("Charlie", "Bob", "Alice");
names.stream() .sorted() .forEach(System.out::println); // Output: Alice, Bob, Charlie // distinct(): Loại bỏ các phần tử trùng lặp.
List<Integer> numbers = List.of(1, 2, 2, 3, 3, 4);
numbers.stream() .distinct() .forEach(System.out::println); // Output: 1, 2, 3, 4 // limit(): Lấy một số lượng phần tử nhất định.
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
infiniteStream.limit(5).forEach(System.out::println); // Output: 0, 1, 2, 3, 4 // skip(): Bỏ qua một số lượng phần tử đầu tiên.
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream() .skip(2) .forEach(System.out::println); // Output: 3, 4, 5

2. Terminal Operations (Thao tác kết thúc)

// forEach(): Duyệt qua từng phần tử.
List<String> names = List.of("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println); // collect(): Thu thập kết quả thành một Collection.
List<String> names = List.of("Alice", "Bob", "Charlie");
List<String> result = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList());
System.out.println(result); // Output: [Alice] // toArray(): Chuyển Stream thành mảng.
String[] array = names.stream().toArray(String[]::new); // reduce(): Gộp các phần tử lại thành một kết quả duy nhất.
List<Integer> numbers = List.of(1, 2, 3, 4);
int sum = numbers.stream() .reduce(0, Integer::sum);
System.out.println(sum); // Output: 10 // count(): Đếm số lượng phần tử.
long count = names.stream() .filter(name -> name.startsWith("A")) .count();
System.out.println(count); // Output: 1 // anyMatch(), allMatch(), noneMatch(): Kiểm tra điều kiện.
boolean hasAlice = names.stream().anyMatch(name -> name.equals("Alice")); // true
boolean allShort = names.stream().allMatch(name -> name.length() < 10); // true // findFirst(): Lấy phần tử đầu tiên (nếu có).
Optional<String> first = names.stream().findFirst();
first.ifPresent(System.out::println); // Output: Alice

Ưu điểm của Stream

  1. Mã gọn gàng hơn: Thay vì dùng vòng lặp và điều kiện, Stream cho phép xử lý dữ liệu dễ hiểu hơn.
  2. Hiệu suất cao: Các thao tác được tối ưu hóa, chỉ thực thi khi cần thiết.
  3. Hỗ trợ song song: Sử dụng parallelStream() để xử lý dữ liệu song song.

Stream là công cụ mạnh mẽ giúp xử lý dữ liệu trong Java hiệu quả hơn.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 303

- vừa được xem lúc

Học Spring Boot bắt đầu từ đâu?

1. Giới thiệu Spring Boot. 1.1.

0 0 279

- vừa được xem lúc

Cần chuẩn bị gì để bắt đầu học Java

Cần chuẩn bị những gì để bắt đầu lập trình Java. 1.1. Cài JDK hay JRE.

0 0 52

- vừa được xem lúc

Sử dụng ModelMapper trong Spring Boot

Bài hôm nay sẽ là cách sử dụng thư viện ModelMapper để mapping qua lại giữa các object trong Spring nhé. Trang chủ của ModelMapper đây http://modelmapper.org/, đọc rất dễ hiểu dành cho các bạn muốn tìm hiểu sâu hơn. 1.

0 0 195

- vừa được xem lúc

[Java] 1 vài tip nhỏ khi sử dụng String hoặc Collection part 1

. Hello các bạn, hôm nay mình sẽ chia sẻ về mẹo check String null hay full space một cách tiện lợi. Mình sẽ sử dụng thư viện Lớp StringUtils download file jar để import vào thư viện tại (link).

0 0 72

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 139