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) amortizedadd(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, ThirdKhi 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 orderTreeMap: Đượ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, CharlieCá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 10Cá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)); // 15Functional 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)); // 8Method 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 treeGradle (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 dependenciesMaven 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:
- Java Collections Framework →
- Streams & Lập Trình Hàm →
- Best Practices Exception Handling →
- Modules và Build Tools →
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.