Back to blog

Giai Đoạn 3: Core Java APIs - Collections, Streams, và Nhiều Hơn Nữa

javacollectionsstreamsapibackend
Giai Đoạn 3: Core Java APIs - Collections, Streams, và Nhiều Hơn Nữa

Chào Mừng Đến Với Giai Đoạn 3

Chào mừng bạn đến với giai đoạn Core Java APIs! Nếu bạn đã hoàn thành Giai đoạn 1 và Giai đoạn 2, bạn đã có nền tảng vững chắc về cú pháp Java và lập trình hướng đối tượng. Bây giờ chúng ta sẽ khám phá các thư viện chuẩn mạnh mẽ của Java mà bạn sẽ sử dụng trong mọi ứng dụng thực tế.

Đây không chỉ là các lớp tiện ích—chúng là các API đã được kiểm nghiệm, tối ưu hóa và tạo thành xương sống của phát triển Java hiện đại. Hiểu về Collections, Streams, và Exception Handling là thiết yếu trước khi xây dựng ứng dụng Spring Boot.

Cam kết thời gian: 2 tuần, 1-2 giờ mỗi ngày
Yêu cầu: Đã hoàn thành Giai đoạn 1 & 2 hoặc có kiến thức Java OOP tương đương

Những Gì Bạn Sẽ Học

Đến cuối Giai đoạn 3, bạn sẽ có thể:

✅ Chọn loại collection phù hợp cho bất kỳ tình huống nào
✅ Làm việc hiệu quả với List, Set, Map, và Queue
✅ Viết code theo phong cách functional với Streams và lambdas
✅ Xử lý exceptions một cách tốt nhất
✅ Đọc và ghi file sử dụng các API Java hiện đại
✅ Làm việc với ngày giờ một cách chính xác
✅ Sử dụng Optional cho code null-safe
✅ Hiểu các khái niệm cơ bản về multithreading
✅ Quản lý dependencies với Maven/Gradle

Tổng Quan Collections Framework

Collections Framework của Java cung cấp các cấu trúc dữ liệu để lưu trữ, tổ chức, và thao tác các nhóm đối tượng.

Cấu Trúc Phân Cấp Collection

Collection (interface)
├── List (interface)
│   ├── ArrayList (class)
│   ├── LinkedList (class)
│   └── Vector (class)
├── Set (interface)
│   ├── HashSet (class)
│   ├── LinkedHashSet (class)
│   └── TreeSet (class)
└── Queue (interface)
    ├── LinkedList (class)
    ├── PriorityQueue (class)
    └── ArrayDeque (class)
 
Map (interface - separate hierarchy)
├── HashMap (class)
├── LinkedHashMap (class)
├── TreeMap (class)
└── Hashtable (class)

Các interface chính:

  • Collection: Interface gốc cho tất cả collections
  • List: Collection có thứ tự (cho phép trùng lặp)
  • Set: Collection không có thứ tự (không trùng lặp)
  • Queue: Collection để giữ các phần tử trước khi xử lý
  • Map: Các cặp key-value (không phải là Collection thực sự)

List: Collections Có Thứ Tự

Lists duy trì thứ tự chèn và cho phép trùng lặp.

ArrayList: Mảng Động

import java.util.ArrayList;
import java.util.List;
 
// Create ArrayList
List<String> fruits = new ArrayList<>();
 
// Add elements
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Apple");  // Duplicates allowed
 
// Access by index
String first = fruits.get(0);  // "Apple"
 
// Size
int count = fruits.size();  // 4
 
// Check existence
boolean hasOrange = fruits.contains("Orange");  // true
 
// Remove
fruits.remove("Banana");           // Remove by value
fruits.remove(0);                  // Remove by index
 
// Iterate
for (String fruit : fruits) {
    System.out.println(fruit);
}
 
// Modern iteration with forEach
fruits.forEach(System.out::println);

Khi nào dùng ArrayList:

  • Truy cập ngẫu nhiên thường xuyên (tra cứu theo index)
  • Ít thao tác chèn/xóa ở giữa
  • Triển khai List phổ biến nhất

Hiệu suất:

  • get(index): O(1)
  • add(element): O(1) amortized
  • add(index, element): O(n)
  • remove(index): O(n)

LinkedList: Danh Sách Liên Kết Đôi

import java.util.LinkedList;
import java.util.List;
 
List<String> tasks = new LinkedList<>();
 
// LinkedList also implements Queue
LinkedList<String> taskQueue = new LinkedList<>();
taskQueue.addFirst("Urgent task");
taskQueue.addLast("Regular task");
String nextTask = taskQueue.removeFirst();

Khi nào dùng LinkedList:

  • Thường xuyên chèn/xóa ở cả hai đầu
  • Triển khai hành vi queue hoặc deque
  • Ít phổ biến hơn ArrayList

Hiệu suất:

  • get(index): O(n)
  • addFirst/addLast(): O(1)
  • removeFirst/removeLast(): O(1)

Các Thao Tác List Phổ Biến

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
 
// Sublist
List<Integer> subset = numbers.subList(1, 4);  // [2, 3, 4]
 
// Sort
numbers.sort(Comparator.naturalOrder());
// Or: Collections.sort(numbers);
 
// Reverse
Collections.reverse(numbers);
 
// Search (binary search requires sorted list)
Collections.sort(numbers);
int index = Collections.binarySearch(numbers, 3);
 
// Replace all
numbers.replaceAll(n -> n * 2);  // [2, 4, 6, 8, 10]
 
// Clear
numbers.clear();

Set: Collections Duy Nhất

Sets lưu trữ các phần tử duy nhất (không trùng lặp).

HashSet: Nhanh, Không Có Thứ Tự

import java.util.HashSet;
import java.util.Set;
 
Set<String> uniqueNames = new HashSet<>();
 
// Add elements
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice");  // Duplicate - won't be added
 
System.out.println(uniqueNames.size());  // 2
 
// Check membership
boolean hasAlice = uniqueNames.contains("Alice");  // true
 
// Remove
uniqueNames.remove("Bob");
 
// Iterate (order not guaranteed)
for (String name : uniqueNames) {
    System.out.println(name);
}

Khi nào dùng HashSet:

  • Cần các phần tử duy nhất
  • Thứ tự không quan trọng
  • Tra cứu nhanh là quan trọng

Hiệu suất:

  • add/remove/contains: O(1) trung bình

LinkedHashSet: Thứ Tự Chèn

Set<String> orderedSet = new LinkedHashSet<>();
orderedSet.add("First");
orderedSet.add("Second");
orderedSet.add("Third");
 
// Iterates in insertion order: First, Second, Third

Khi nào dùng LinkedHashSet:

  • Cần các phần tử duy nhất
  • Muốn duy trì thứ tự chèn

TreeSet: Set Được Sắp Xếp

import java.util.TreeSet;
 
Set<Integer> sortedNumbers = new TreeSet<>();
sortedNumbers.add(5);
sortedNumbers.add(1);
sortedNumbers.add(3);
 
// Always sorted: [1, 3, 5]
System.out.println(sortedNumbers);
 
// Range operations
TreeSet<Integer> tree = new TreeSet<>(Set.of(1, 3, 5, 7, 9));
Set<Integer> lessThan5 = tree.headSet(5);    // [1, 3]
Set<Integer> greaterThan5 = tree.tailSet(5);  // [5, 7, 9]

Khi nào dùng TreeSet:

  • Cần các phần tử duy nhất
  • Cần thứ tự sắp xếp
  • Cần các truy vấn phạm vi

Hiệu suất:

  • add/remove/contains: O(log n)

Map: Các Cặp Key-Value

Maps lưu trữ các liên kết giữa keys và values.

HashMap: Lưu Trữ Key-Value Nhanh

import java.util.HashMap;
import java.util.Map;
 
Map<String, Integer> ages = new HashMap<>();
 
// Put key-value pairs
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Charlie", 28);
 
// Get value by key
Integer aliceAge = ages.get("Alice");  // 25
 
// Check key existence
boolean hasAlice = ages.containsKey("Alice");  // true
 
// Check value existence
boolean has25 = ages.containsValue(25);  // true
 
// Get or default
int unknownAge = ages.getOrDefault("Unknown", 0);  // 0
 
// Put if absent
ages.putIfAbsent("Alice", 30);  // Won't update (Alice exists)
 
// Remove
ages.remove("Bob");
 
// Iterate over entries
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}
 
// Iterate keys
for (String name : ages.keySet()) {
    System.out.println(name);
}
 
// Iterate values
for (Integer age : ages.values()) {
    System.out.println(age);
}
 
// Modern forEach
ages.forEach((name, age) -> System.out.println(name + " is " + age));

Khi nào dùng HashMap:

  • Cần liên kết key-value
  • Tra cứu nhanh theo key
  • Thứ tự không quan trọng
  • Triển khai Map phổ biến nhất

Hiệu suất:

  • get/put/remove: O(1) trung bình

LinkedHashMap: Thứ Tự Chèn

Map<String, String> orderedMap = new LinkedHashMap<>();
orderedMap.put("First", "1");
orderedMap.put("Second", "2");
orderedMap.put("Third", "3");
 
// Iterates in insertion order

TreeMap: Được Sắp Xếp Theo Keys

import java.util.TreeMap;
 
Map<String, Integer> sortedMap = new TreeMap<>();
sortedMap.put("Charlie", 28);
sortedMap.put("Alice", 25);
sortedMap.put("Bob", 30);
 
// Always sorted by key: Alice, Bob, Charlie

Các Thao Tác Map Nâng Cao (Java 8+)

Map<String, Integer> scores = new HashMap<>();
 
// Compute if absent
scores.computeIfAbsent("Alice", k -> 100);  // Alice: 100
 
// Compute if present
scores.computeIfPresent("Alice", (k, v) -> v + 10);  // Alice: 110
 
// Compute
scores.compute("Alice", (k, v) -> v == null ? 0 : v + 5);
 
// Merge (useful for aggregation)
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"apple", "banana", "apple", "orange", "banana", "apple"};
 
for (String word : words) {
    wordCount.merge(word, 1, Integer::sum);
}
// {apple=3, banana=2, orange=1}

Queue và Deque

Queues giữ các phần tử để xử lý.

Queue Interface

import java.util.LinkedList;
import java.util.Queue;
 
Queue<String> queue = new LinkedList<>();
 
// Add (throws exception if fails)
queue.add("First");
 
// Offer (returns false if fails)
queue.offer("Second");
queue.offer("Third");
 
// Peek (doesn't remove)
String head = queue.peek();  // "First"
 
// Poll (removes and returns, null if empty)
String first = queue.poll();  // "First"
 
// Remove (removes and returns, throws if empty)
String second = queue.remove();  // "Second"

PriorityQueue: Hàng Đợi Ưu Tiên Dựa Trên Heap

import java.util.PriorityQueue;
 
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(1);
pq.offer(3);
 
// Always polls smallest element
System.out.println(pq.poll());  // 1
System.out.println(pq.poll());  // 3
System.out.println(pq.poll());  // 5
 
// Custom comparator (reverse order)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());

Deque: Hàng Đợi Hai Đầu

import java.util.ArrayDeque;
import java.util.Deque;
 
Deque<String> deque = new ArrayDeque<>();
 
// Add at both ends
deque.addFirst("First");
deque.addLast("Last");
 
// Remove from both ends
String first = deque.removeFirst();
String last = deque.removeLast();
 
// Use as stack
deque.push("Top");
String top = deque.pop();

ArrayDeque được ưu tiên hơn Stack và LinkedList cho các thao tác stack/queue.

Streams: Xử Lý Dữ Liệu Theo Kiểu Functional

Streams cung cấp cách tiếp cận functional để xử lý collections.

Tạo Streams

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
 
// From collection
List<String> list = List.of("a", "b", "c");
Stream<String> stream1 = list.stream();
 
// From array
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
 
// Stream builder
Stream<String> stream3 = Stream.of("a", "b", "c");
 
// Infinite streams
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
Stream<Double> randomStream = Stream.generate(Math::random);
 
// Range
IntStream range = IntStream.range(1, 10);  // 1 to 9
IntStream rangeClosed = IntStream.rangeClosed(1, 10);  // 1 to 10

Các Thao Tác Trung Gian (Lazy)

Các thao tác trả về một Stream:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
 
// Filter
numbers.stream()
    .filter(n -> n % 2 == 0)  // [2, 4, 6, 8, 10]
    .forEach(System.out::println);
 
// Map
numbers.stream()
    .map(n -> n * 2)  // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
    .forEach(System.out::println);
 
// FlatMap (flatten nested structures)
List<List<Integer>> nested = List.of(
    List.of(1, 2),
    List.of(3, 4),
    List.of(5, 6)
);
 
nested.stream()
    .flatMap(List::stream)  // [1, 2, 3, 4, 5, 6]
    .forEach(System.out::println);
 
// Distinct (remove duplicates)
List.of(1, 2, 2, 3, 3, 3).stream()
    .distinct()  // [1, 2, 3]
    .forEach(System.out::println);
 
// Sorted
List.of(3, 1, 4, 1, 5).stream()
    .sorted()  // [1, 1, 3, 4, 5]
    .forEach(System.out::println);
 
// Limit and Skip
numbers.stream()
    .skip(3)     // Skip first 3
    .limit(5)    // Take next 5
    .forEach(System.out::println);  // [4, 5, 6, 7, 8]
 
// Peek (for debugging)
numbers.stream()
    .filter(n -> n % 2 == 0)
    .peek(n -> System.out.println("Filtered: " + n))
    .map(n -> n * 2)
    .forEach(System.out::println);

Các Thao Tác Đầu Cuối (Eager)

Các thao tác kích hoạt tính toán và trả về kết quả:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
 
// forEach
numbers.stream().forEach(System.out::println);
 
// Collect to List
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
 
// Collect to Set
Set<Integer> uniqueNumbers = numbers.stream()
    .collect(Collectors.toSet());
 
// Collect to Map
Map<Integer, String> numberMap = numbers.stream()
    .collect(Collectors.toMap(
        n -> n,                    // Key
        n -> "Number " + n         // Value
    ));
 
// Count
long count = numbers.stream()
    .filter(n -> n > 5)
    .count();  // 5
 
// Sum, Min, Max, Average
int sum = numbers.stream().mapToInt(Integer::intValue).sum();
OptionalInt min = numbers.stream().mapToInt(Integer::intValue).min();
OptionalInt max = numbers.stream().mapToInt(Integer::intValue).max();
OptionalDouble avg = numbers.stream().mapToInt(Integer::intValue).average();
 
// Reduce
int product = numbers.stream()
    .reduce(1, (a, b) -> a * b);  // 3628800
 
// AnyMatch, AllMatch, NoneMatch
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);  // true
boolean allPositive = numbers.stream().allMatch(n -> n > 0);   // true
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0); // true
 
// FindFirst, FindAny
Optional<Integer> first = numbers.stream().findFirst();
Optional<Integer> any = numbers.stream().findAny();

Ví Dụ Về Collectors

import java.util.stream.Collectors;
 
List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");
 
// Joining strings
String joined = names.stream()
    .collect(Collectors.joining(", "));  // "Alice, Bob, Charlie, David, Eve"
 
// Grouping
List<Person> people = List.of(
    new Person("Alice", 25),
    new Person("Bob", 30),
    new Person("Charlie", 25)
);
 
Map<Integer, List<Person>> byAge = people.stream()
    .collect(Collectors.groupingBy(Person::getAge));
// {25=[Alice, Charlie], 30=[Bob]}
 
// Partitioning
Map<Boolean, List<Integer>> partitioned = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
 
// Summarizing
IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Average: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());

Ví Dụ Stream Thực Tế

Ví dụ 1: Tìm 3 mức lương cao nhất

List<Employee> employees = getEmployees();
 
List<Double> top3Salaries = employees.stream()
    .map(Employee::getSalary)
    .sorted(Comparator.reverseOrder())
    .distinct()
    .limit(3)
    .collect(Collectors.toList());

Ví dụ 2: Đếm từ trong file

Map<String, Long> wordFrequency = Files.lines(Paths.get("file.txt"))
    .flatMap(line -> Arrays.stream(line.split("\\s+")))
    .map(String::toLowerCase)
    .collect(Collectors.groupingBy(
        word -> word,
        Collectors.counting()
    ));

Ví dụ 3: Lọc và chuyển đổi

List<String> activeUserEmails = users.stream()
    .filter(User::isActive)
    .filter(user -> user.getAge() >= 18)
    .map(User::getEmail)
    .sorted()
    .collect(Collectors.toList());

Để biết thêm các mẫu Stream nâng cao, xem hướng dẫn chuyên sâu về Java Streams của chúng tôi.

Lambda Expressions

Lambdas cho phép lập trình theo phong cách functional trong Java.

Cú Pháp Lambda

// Traditional anonymous class
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running");
    }
};
 
// Lambda expression
Runnable runnable2 = () -> System.out.println("Running");
 
// With parameters
Comparator<Integer> comparator = (a, b) -> a.compareTo(b);
 
// Multiple statements
Comparator<String> stringComparator = (s1, s2) -> {
    int result = s1.length() - s2.length();
    if (result == 0) {
        return s1.compareTo(s2);
    }
    return result;
};

Functional Interfaces

Lambdas hoạt động với functional interfaces (interfaces có một phương thức trừu tượng duy nhất):

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}
 
// Usage
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
 
System.out.println(add.calculate(5, 3));       // 8
System.out.println(multiply.calculate(5, 3));  // 15

Functional Interfaces Có Sẵn

import java.util.function.*;
 
// Predicate<T>: T -> boolean
Predicate<String> isEmpty = String::isEmpty;
System.out.println(isEmpty.test(""));  // true
 
// Function<T, R>: T -> R
Function<String, Integer> length = String::length;
System.out.println(length.apply("Hello"));  // 5
 
// Consumer<T>: T -> void
Consumer<String> print = System.out::println;
print.accept("Hello");
 
// Supplier<T>: () -> T
Supplier<Double> random = Math::random;
System.out.println(random.get());
 
// BiFunction<T, U, R>: (T, U) -> R
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 3));  // 8
 
// UnaryOperator<T>: T -> T
UnaryOperator<Integer> square = x -> x * x;
System.out.println(square.apply(5));  // 25
 
// BinaryOperator<T>: (T, T) -> T
BinaryOperator<Integer> sum = Integer::sum;
System.out.println(sum.apply(5, 3));  // 8

Method References

// Static method reference
Function<String, Integer> parser = Integer::parseInt;
 
// Instance method reference
String str = "Hello";
Supplier<Integer> lengthSupplier = str::length;
 
// Constructor reference
Supplier<List<String>> listSupplier = ArrayList::new;
 
// Examples
List<String> names = List.of("Alice", "Bob", "Charlie");
 
// Instance method of arbitrary object
names.forEach(System.out::println);  // Same as: s -> System.out.println(s)
 
names.stream()
    .map(String::toUpperCase)  // Same as: s -> s.toUpperCase()
    .forEach(System.out::println);

Exception Handling

Exceptions cung cấp cách có cấu trúc để xử lý lỗi.

Cấu Trúc Phân Cấp Exception

Throwable
├── Error (system errors - don't catch)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception
    ├── RuntimeException (unchecked)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── IllegalArgumentException
    │   └── ...
    └── Checked Exceptions
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        └── ...

Checked vs Unchecked:

  • Checked: Phải được bắt hoặc khai báo (compiler bắt buộc)
  • Unchecked (RuntimeException): Tùy chọn để bắt

Try-Catch-Finally

public void readFile(String path) {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(path));
        String line = reader.readLine();
        System.out.println(line);
    } catch (FileNotFoundException e) {
        System.err.println("File not found: " + path);
    } catch (IOException e) {
        System.err.println("Error reading file: " + e.getMessage());
    } finally {
        // Always executes (cleanup)
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                // Handle close exception
            }
        }
    }
}

Try-With-Resources (Java 7+)

Tự động đóng resources:

public void readFile(String path) {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        String line = reader.readLine();
        System.out.println(line);
    } catch (IOException e) {
        System.err.println("Error: " + e.getMessage());
    }
    // reader.close() called automatically
}
 
// Multiple resources
try (FileInputStream input = new FileInputStream("input.txt");
     FileOutputStream output = new FileOutputStream("output.txt")) {
    // Use streams
} catch (IOException e) {
    // Handle
}

Yêu cầu: Resource phải implement AutoCloseable hoặc Closeable.

Ném Exceptions

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    this.age = age;
}
 
public void processFile(String path) throws IOException {
    Files.readString(Paths.get(path));  // Throws IOException
}

Custom Exceptions

// Custom checked exception
public class InsufficientFundsException extends Exception {
    private final double amount;
    
    public InsufficientFundsException(double amount) {
        super("Insufficient funds: " + amount);
        this.amount = amount;
    }
    
    public double getAmount() {
        return amount;
    }
}
 
// Usage
public void withdraw(double amount) throws InsufficientFundsException {
    if (amount > balance) {
        throw new InsufficientFundsException(amount);
    }
    balance -= amount;
}
 
// Custom unchecked exception
public class InvalidConfigurationException extends RuntimeException {
    public InvalidConfigurationException(String message) {
        super(message);
    }
    
    public InvalidConfigurationException(String message, Throwable cause) {
        super(message, cause);
    }
}

Best Practices Xử Lý Exception

// ✅ Tốt: Exceptions cụ thể
public User findUserById(Long id) {
    return userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
 
// ❌ Không tốt: Exception chung chung
public User findUserById(Long id) {
    throw new Exception("Error");  // Too generic
}
 
// ✅ Tốt: Giữ nguyên stack trace
try {
    // ...
} catch (IOException e) {
    throw new DataAccessException("Failed to read data", e);  // Include cause
}
 
// ❌ Không tốt: Nuốt exception
try {
    // ...
} catch (Exception e) {
    // Empty catch block - exception lost!
}
 
// ✅ Tốt: Không bắt những gì không thể xử lý
public void processFile(String path) throws IOException {
    Files.readString(Paths.get(path));  // Let caller handle
}
 
// ❌ Không tốt: Bắt và ném lại không cần thiết
public void processFile(String path) {
    try {
        Files.readString(Paths.get(path));
    } catch (IOException e) {
        throw new RuntimeException(e);  // Why wrap?
    }
}

Để biết các mẫu xử lý exception toàn diện, xem Hướng Dẫn Chuyên Sâu Về Exception Handling của chúng tôi.

File I/O

Java cung cấp nhiều APIs cho các thao tác file.

Đọc Files (Cách Hiện Đại)

import java.nio.file.*;
import java.io.IOException;
 
// Read entire file as string
String content = Files.readString(Paths.get("file.txt"));
 
// Read all lines
List<String> lines = Files.readAllLines(Paths.get("file.txt"));
 
// Read bytes
byte[] bytes = Files.readAllBytes(Paths.get("file.bin"));
 
// Stream lines (memory efficient for large files)
try (Stream<String> stream = Files.lines(Paths.get("large.txt"))) {
    stream.filter(line -> line.contains("ERROR"))
          .forEach(System.out::println);
}

Ghi Files

// Write string to file
String content = "Hello, World!";
Files.writeString(Paths.get("output.txt"), content);
 
// Write lines
List<String> lines = List.of("Line 1", "Line 2", "Line 3");
Files.write(Paths.get("output.txt"), lines);
 
// Append to file
Files.writeString(
    Paths.get("log.txt"),
    "New log entry\n",
    StandardOpenOption.APPEND
);
 
// Write bytes
byte[] bytes = {1, 2, 3, 4, 5};
Files.write(Paths.get("data.bin"), bytes);

BufferedReader và BufferedWriter

// Reading with BufferedReader
try (BufferedReader reader = Files.newBufferedReader(Paths.get("file.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}
 
// Writing with BufferedWriter
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("output.txt"))) {
    writer.write("Line 1");
    writer.newLine();
    writer.write("Line 2");
    writer.newLine();
}

Các Thao Tác File

Path path = Paths.get("file.txt");
 
// Check existence
boolean exists = Files.exists(path);
 
// Check type
boolean isFile = Files.isRegularFile(path);
boolean isDirectory = Files.isDirectory(path);
 
// Get file attributes
long size = Files.size(path);
FileTime modified = Files.getLastModifiedTime(path);
 
// Create directory
Files.createDirectory(Paths.get("newdir"));
Files.createDirectories(Paths.get("parent/child/grandchild"));
 
// Copy
Files.copy(
    Paths.get("source.txt"),
    Paths.get("dest.txt"),
    StandardCopyOption.REPLACE_EXISTING
);
 
// Move/Rename
Files.move(
    Paths.get("old.txt"),
    Paths.get("new.txt"),
    StandardCopyOption.REPLACE_EXISTING
);
 
// Delete
Files.delete(Paths.get("file.txt"));
Files.deleteIfExists(Paths.get("maybe.txt"));
 
// List directory contents
try (Stream<Path> paths = Files.list(Paths.get("."))) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}
 
// Walk directory tree
try (Stream<Path> paths = Files.walk(Paths.get("."))) {
    paths.filter(p -> p.toString().endsWith(".java"))
         .forEach(System.out::println);
}

Date and Time API

Java 8 giới thiệu Date and Time API hiện đại (package java.time).

Các Classes Cốt Lõi

import java.time.*;
 
// Current date
LocalDate today = LocalDate.now();  // 2024-01-24
 
// Specific date
LocalDate birthday = LocalDate.of(1990, 5, 15);
LocalDate parsed = LocalDate.parse("2024-01-24");
 
// Current time
LocalTime now = LocalTime.now();  // 14:30:15.123
 
// Specific time
LocalTime lunch = LocalTime.of(12, 30);
LocalTime precise = LocalTime.of(14, 30, 15, 500000000);
 
// Date and time
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime meeting = LocalDateTime.of(2024, 1, 24, 14, 30);
 
// With timezone
ZonedDateTime zonedNow = ZonedDateTime.now();
ZonedDateTime paris = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
 
// Instant (timestamp)
Instant timestamp = Instant.now();

Phép Tính Ngày

LocalDate today = LocalDate.now();
 
// Add/subtract
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate nextYear = today.plusYears(1);
 
LocalDate yesterday = today.minusDays(1);
 
// Chaining
LocalDate future = today.plusMonths(2).plusDays(5);
 
// With specific values
LocalDate modified = today.withYear(2025)
                         .withMonth(12)
                         .withDayOfMonth(31);

Time Periods và Durations

// Period: date-based (days, months, years)
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 12, 31);
Period period = Period.between(start, end);
System.out.println(period.getMonths());  // 11
System.out.println(period.getDays());    // 30
 
// Duration: time-based (hours, minutes, seconds)
LocalTime startTime = LocalTime.of(9, 0);
LocalTime endTime = LocalTime.of(17, 30);
Duration duration = Duration.between(startTime, endTime);
System.out.println(duration.toHours());   // 8
System.out.println(duration.toMinutes()); // 510
 
// Create durations
Duration oneHour = Duration.ofHours(1);
Duration fiveMinutes = Duration.ofMinutes(5);
Duration thirtySeconds = Duration.ofSeconds(30);

Định Dạng và Phân Tích

LocalDate date = LocalDate.now();
 
// Format
String formatted = date.format(DateTimeFormatter.ISO_DATE);
String custom = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
String text = date.format(DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy"));
// "Friday, January 24, 2024"
 
// Parse
LocalDate parsed = LocalDate.parse("24/01/2024",
    DateTimeFormatter.ofPattern("dd/MM/yyyy"));
 
LocalDateTime dateTime = LocalDateTime.parse("2024-01-24 14:30",
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));

Ví Dụ Thực Tế

// Check if date is in range
public boolean isValidDate(LocalDate date) {
    LocalDate start = LocalDate.of(2024, 1, 1);
    LocalDate end = LocalDate.of(2024, 12, 31);
    return !date.isBefore(start) && !date.isAfter(end);
}
 
// Calculate age
public int calculateAge(LocalDate birthDate) {
    return Period.between(birthDate, LocalDate.now()).getYears();
}
 
// Get next business day
public LocalDate nextBusinessDay(LocalDate date) {
    LocalDate next = date.plusDays(1);
    while (next.getDayOfWeek() == DayOfWeek.SATURDAY ||
           next.getDayOfWeek() == DayOfWeek.SUNDAY) {
        next = next.plusDays(1);
    }
    return next;
}

Optional: Null Safety

Optional là một container có thể chứa hoặc không chứa giá trị.

Tạo Optionals

// Empty optional
Optional<String> empty = Optional.empty();
 
// Optional with value
Optional<String> name = Optional.of("Alice");
 
// Optional that might be null
String nullableValue = getName();
Optional<String> optional = Optional.ofNullable(nullableValue);

Kiểm Tra và Lấy Values

Optional<String> optional = Optional.of("Hello");
 
// Check if present
if (optional.isPresent()) {
    String value = optional.get();
    System.out.println(value);
}
 
// Better: ifPresent with lambda
optional.ifPresent(value -> System.out.println(value));
optional.ifPresent(System.out::println);
 
// Get or default
String value = optional.orElse("Default");
 
// Get or compute default
String value2 = optional.orElseGet(() -> computeDefault());
 
// Get or throw
String value3 = optional.orElseThrow(() -> 
    new NoSuchElementException("Value not found")
);

Chuyển Đổi Optionals

Optional<String> name = Optional.of("Alice");
 
// Map
Optional<Integer> length = name.map(String::length);
 
// FlatMap (when mapper returns Optional)
Optional<String> upperCase = name.flatMap(n -> Optional.of(n.toUpperCase()));
 
// Filter
Optional<String> longName = name.filter(n -> n.length() > 5);

Ví Dụ Thực Tế

// Instead of:
public String getUserEmail(Long userId) {
    User user = findUserById(userId);
    if (user != null) {
        return user.getEmail();
    }
    return "[email protected]";
}
 
// Use Optional:
public String getUserEmail(Long userId) {
    return findUserById(userId)
        .map(User::getEmail)
        .orElse("[email protected]");
}
 
// Chaining
Optional<String> result = getUser()
    .filter(User::isActive)
    .map(User::getProfile)
    .map(Profile::getEmail)
    .filter(email -> email.contains("@"));
 
// Java 9+: ifPresentOrElse
optional.ifPresentOrElse(
    value -> System.out.println("Found: " + value),
    () -> System.out.println("Not found")
);
 
// Java 9+: or (fallback Optional)
Optional<String> result = findInCache()
    .or(() -> findInDatabase())
    .or(() -> fetchFromAPI());

Multithreading Cơ Bản

Hiểu về threads là thiết yếu cho lập trình đồng thời.

Tạo Threads

// Extending Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running: " + getName());
    }
}
 
MyThread thread = new MyThread();
thread.start();
 
// Implementing Runnable (preferred)
Runnable task = () -> System.out.println("Task running");
Thread thread2 = new Thread(task);
thread2.start();
 
// With lambda
new Thread(() -> {
    System.out.println("Lambda thread");
}).start();

Các Phương Thức Thread

Thread thread = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Count: " + i);
        try {
            Thread.sleep(1000);  // Sleep 1 second
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
});
 
thread.start();  // Start thread
thread.join();   // Wait for thread to complete
 
// Thread info
String name = thread.getName();
long id = thread.getId();
Thread.State state = thread.getState();

ExecutorService (Được Ưu Tiên)

import java.util.concurrent.*;
 
// Fixed thread pool
ExecutorService executor = Executors.newFixedThreadPool(4);
 
// Submit tasks
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Task completed";
});
 
// Get result (blocks)
String result = future.get();
 
// Shutdown
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
 
// Single thread executor
ExecutorService single = Executors.newSingleThreadExecutor();
 
// Cached thread pool (creates threads as needed)
ExecutorService cached = Executors.newCachedThreadPool();
 
// Scheduled executor
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
scheduled.schedule(() -> System.out.println("Delayed task"), 5, TimeUnit.SECONDS);
scheduled.scheduleAtFixedRate(() -> System.out.println("Periodic"), 0, 1, TimeUnit.SECONDS);

Synchronized Access

public class Counter {
    private int count = 0;
    
    // Synchronized method
    public synchronized void increment() {
        count++;
    }
    
    // Synchronized block
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }
    
    public synchronized int getCount() {
        return count;
    }
}
 
// Atomic classes (better performance)
import java.util.concurrent.atomic.AtomicInteger;
 
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet();
atomicCount.decrementAndGet();
int value = atomicCount.get();

Lưu ý: Concurrency là một chủ đề sâu. Đây chỉ là phần giới thiệu. Spring Boot xử lý hầu hết threading cho bạn.

Maven và Gradle Cơ Bản

Build tools quản lý dependencies và vòng đời dự án.

Maven (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    
    <dependencies>
        <!-- Example dependency -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.3-jre</version>
        </dependency>
        
        <!-- Test dependency -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Các lệnh Maven phổ biến:

mvn clean          # Clean build artifacts
mvn compile        # Compile source code
mvn test           # Run tests
mvn package        # Create JAR/WAR
mvn install        # Install to local repository
mvn dependency:tree  # Show dependency tree

Gradle (build.gradle)

plugins {
    id 'java'
    id 'application'
}
 
group = 'com.example'
version = '1.0.0'
 
sourceCompatibility = '17'
targetCompatibility = '17'
 
repositories {
    mavenCentral()
}
 
dependencies {
    // Example dependency
    implementation 'com.google.guava:guava:32.1.3-jre'
    
    // Test dependency
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
 
application {
    mainClass = 'com.example.Main'
}
 
test {
    useJUnitPlatform()
}

Các lệnh Gradle phổ biến:

gradle clean        # Clean build artifacts
gradle build        # Compile and package
gradle test         # Run tests
gradle run          # Run application
gradle dependencies  # Show dependencies

Maven vs Gradle:

  • Maven: Dựa trên XML, ưu tiên quy ước hơn cấu hình, được chấp nhận rộng rãi
  • Gradle: Groovy/Kotlin DSL, linh hoạt hơn, build nhanh hơn
  • Spring Boot hoạt động mượt mà với cả hai

Để biết hướng dẫn đầy đủ, xem Hướng Dẫn Chuyên Sâu Về Maven và Gradle của chúng tôi.

Best Practices

1. Chọn Collection Phù Hợp

// ✅ ArrayList cho truy cập ngẫu nhiên
List<String> names = new ArrayList<>();
 
// ✅ LinkedList cho chèn thường xuyên ở hai đầu
Deque<String> queue = new LinkedList<>();
 
// ✅ HashSet cho tính duy nhất
Set<String> uniqueIds = new HashSet<>();
 
// ✅ HashMap cho tra cứu key-value
Map<String, User> userMap = new HashMap<>();
 
// ✅ TreeSet/TreeMap khi cần thứ tự sắp xếp
Set<Integer> sortedScores = new TreeSet<>();

2. Sử Dụng Streams Cho Tính Dễ Đọc

// ❌ Imperative
List<String> result = new ArrayList<>();
for (User user : users) {
    if (user.isActive()) {
        result.add(user.getEmail());
    }
}
Collections.sort(result);
 
// ✅ Declarative với streams
List<String> result = users.stream()
    .filter(User::isActive)
    .map(User::getEmail)
    .sorted()
    .collect(Collectors.toList());

3. Xử Lý Exceptions Hợp Lý

// ✅ Sử dụng try-with-resources
try (BufferedReader reader = Files.newBufferedReader(path)) {
    // Use reader
} catch (IOException e) {
    logger.error("Failed to read file: " + path, e);
}
 
// ✅ Không nuốt exceptions
try {
    riskyOperation();
} catch (Exception e) {
    logger.error("Operation failed", e);
    throw new ServiceException("Failed to process", e);
}

4. Sử Dụng Optional Thay Vì Null

// ❌ Kiểm tra null ở khắp nơi
public String getUserEmail(Long id) {
    User user = findUser(id);
    if (user != null && user.getEmail() != null) {
        return user.getEmail();
    }
    return "unknown";
}
 
// ✅ Optional
public Optional<String> getUserEmail(Long id) {
    return findUser(id)
        .map(User::getEmail);
}

5. Sử Dụng Date/Time API Hiện Đại

// ❌ Date/Calendar cũ
Date date = new Date();
Calendar calendar = Calendar.getInstance();
 
// ✅ java.time hiện đại
LocalDate today = LocalDate.now();
LocalDateTime now = LocalDateTime.now();
Instant timestamp = Instant.now();

Tổng Kết

Bây giờ bạn đã làm chủ các core APIs của Java:

✅ Collections Framework (List, Set, Map, Queue)
✅ Streams và lập trình functional
✅ Lambda expressions và method references
✅ Exception handling với best practices
✅ File I/O với NIO.2 API hiện đại
✅ Date and Time API
✅ Optional cho null safety
✅ Các khái niệm multithreading cơ bản
✅ Các kiến thức cơ bản về Maven và Gradle

Đây là các công cụ thiết yếu mà bạn sẽ sử dụng hàng ngày trong phát triển Spring Boot.

Các Bước Tiếp Theo

Bạn đã sẵn sàng chuyển sang Giai Đoạn 4: Spring Boot Fundamentals, nơi bạn sẽ học:

  • Cấu trúc dự án Spring Boot
  • Dependency injection và IoC
  • Phát triển RESTful API
  • Spring Data JPA
  • Spring Security cơ bản
  • Testing với Spring Boot

🚀 Tiếp tục đến Lộ trình Học Spring Boot →


Trước: Giai Đoạn 2: Lập Trình Hướng Đối Tượng ← Tiếp: Lộ trình Học Spring Boot → Hướng Dẫn Chuyên Sâu:

Chúc bạn code vui vẻ! ☕

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.