I/O, networking, reflection và metadata
Những API nền tảng quyết định tính đúng đắn khi xử lý file, text, network, metadata runtime và tích hợp framework. Trọng tâm là hiểu boundary giữa byte và character, resource lifecycle, blocking model, serialization risk, annotation/reflection và các utility API dễ tạo bug production.
1. Byte, character và charset
InputStream/OutputStream làm việc với byte; Reader/Writer làm việc với character. Chuyển giữa hai thế giới bắt buộc có charset. Với dữ liệu có protocol hoặc được lưu trữ lâu dài, không nên phụ thuộc default charset của môi trường; hãy chỉ rõ charset như UTF-8 để producer và consumer cùng hiểu một mapping byte ↔ text.
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
return reader.lines().toList();
}
| API | Làm việc với | Dùng khi |
|---|---|---|
InputStream/OutputStream | Raw bytes | File nhị phân, socket payload, compressed/encrypted data |
Reader/Writer | Characters | Text sau khi charset đã được xác định |
StandardCharsets.UTF_8 | Charset contract | Protocol/storage cần deterministic behavior giữa các máy |
Unicode code point không luôn tương ứng một Java char, vì char là một UTF-16 code unit. Ký tự supplementary như nhiều emoji có thể dùng surrogate pair. Vì vậy String.length() đếm số UTF-16 code unit, không phải số code point và càng không bảo đảm bằng số “ký tự người dùng nhìn thấy”. Khi logic cần code point, dùng codePoints() hoặc các API code-point-aware.
char là một Unicode character” là cách nói quá đơn giản. Chính xác hơn: char là 16-bit UTF-16 code unit; một Unicode code point có thể cần hai char.2. NIO.2 file system
Path là representation của path phụ thuộc filesystem provider; Files cung cấp thao tác như copy, move, walk và đọc attributes. Khi làm việc với file path cần phân biệt lexical normalization, symbolic link và real path. normalize() chỉ xử lý các thành phần như ./.. về mặt cú pháp; nó không tự resolve symbolic link thành target thật.
Path target = base.resolve(userInput).normalize();
if (!target.startsWith(base.normalize())) {
throw new SecurityException("Path traversal");
}
Đoạn kiểm tra trên giúp chặn traversal dạng ../ ở mức lexical path, nhưng không nên hiểu nó là mọi trường hợp đều an toàn trước filesystem race hoặc symlink manipulation. TOCTOU (time-of-check to time-of-use) xảy ra khi ứng dụng “check” một file/path rồi sau đó “use” ở bước khác, trong khoảng giữa target có thể đã đổi.
Files.lines, Files.list và Files.walk trả về stream giữ resource hệ thống; stream này cần được đóng bằng try-with-resources. Với file lớn, streaming giúp tránh nạp toàn bộ nội dung vào heap.
try (Stream<Path> entries = Files.list(directory)) {
entries.filter(Files::isRegularFile)
.forEach(System.out::println);
}
FileChannel/ByteBuffer phù hợp cho random access, scatter/gather hoặc memory mapping. Direct buffer và mapped buffer có lifecycle khác object heap thông thường: chúng có thể dùng native memory và vì vậy cần theo dõi capacity/resource pressure thay vì chỉ nhìn Java heap.
Files là façade cao cấp cho NIO.2; FileChannel/ByteBuffer dùng khi cần control thấp hơn như positioning, zero-copy-ish operations hoặc memory mapping.3. Blocking và non-blocking I/O
Classic stream/socket thường dùng blocking style: thread gọi read/write và chờ khi operation chưa hoàn tất. NIO channel có thể chạy non-blocking với Selector, cho phép một thread quản lý nhiều connection nhưng đổi lại phải quản lý registration, readiness và state machine phức tạp hơn.
Virtual thread làm blocking style scale tốt hơn trong nhiều server application vì chi phí giữ một blocked virtual thread thấp hơn platform thread. Tuy nhiên virtual thread không làm kernel/network vô hạn, không loại bỏ connection pool limit, bandwidth limit hay downstream saturation, và không thay thế backpressure.
| Model | Điểm mạnh | Trade-off |
|---|---|---|
| Blocking + platform thread | Code tuyến tính, dễ đọc | Nhiều blocked connection có thể tốn thread OS |
| Non-blocking + Selector | Một số ít thread quản lý nhiều connection | State machine/callback complexity cao hơn |
| Blocking + virtual thread | Giữ programming model đơn giản, scale tốt cho I/O-bound concurrency | Vẫn cần bounded resource, timeout, backpressure và quan sát pinning/downstream limits |
Java HTTP Client
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(2))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(3))
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
Phân biệt connect timeout với timeout/deadline cho request; luôn kiểm tra HTTP status trước khi xử lý body; với nguồn không tin cậy cần có chiến lược giới hạn response size; và không retry non-idempotent request một cách mù quáng vì unknown outcome có thể tạo duplicate side effect.
4. Object serialization
Java native serialization có thể lưu và khôi phục object graph, nhưng nó tạo coupling vào class shape, khó quản lý compatibility/version và có lịch sử rủi ro bảo mật. Không deserialize dữ liệu không tin cậy. Với boundary giữa service, storage lâu dài hoặc API, ưu tiên format có schema/contract rõ như JSON hay Protobuf đi cùng validation.
Nếu buộc dùng native serialization, cần hiểu serialVersionUID, transient, custom readObject và serialization filter. Constructor của một Serializable class không chạy theo cách tạo object bình thường trong quá trình deserialize, nên invariant có thể bị phá nếu dữ liệu được khôi phục mà không validation.
| Khái niệm | Vai trò | Rủi ro cần nhớ |
|---|---|---|
serialVersionUID | Nhận diện version compatibility của serialized class | Không biến serialization thành schema evolution system hoàn chỉnh |
transient | Loại field khỏi default serialized form | Field sau deserialize có thể cần tái thiết lập invariant |
readObject | Custom restore logic | Là điểm cần validation nghiêm ngặt |
| Serialization filter | Giới hạn class/graph được deserialize | Là defense-in-depth, không biến untrusted native serialization thành boundary mặc định an toàn |
ObjectInputStream trực tiếp trên request upload hoặc network payload.5. Annotation và metadata
Annotation là metadata có target và retention. SOURCE chỉ phục vụ compiler/tool và không đi tới runtime; CLASS được giữ trong class file nhưng không nhất thiết khả dụng qua reflection; RUNTIME cho phép truy vấn annotation lúc chương trình chạy.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audited {
String value() default "";
}
@Inherited chỉ áp dụng theo quy tắc giới hạn cho annotation trên class: nó không tự làm method annotation hoặc annotation trên interface “kế thừa” theo cách người mới thường tưởng.
Annotation processor chạy lúc compile để validate input hoặc sinh source/resource. Nó khác runtime reflection: processor có thể đưa lỗi sớm hơn ở build time và tránh một phần dynamic lookup khi chạy.
6. Reflection, MethodHandle, VarHandle và proxy
Reflection cho phép truy vấn class/member động, nhưng đổi lại là giảm static type-safety, làm encapsulation phức tạp hơn và tăng maintenance cost. setAccessible bị module system giới hạn mạnh hơn so với thời classpath-only. Khi framework scan metadata nhiều lần có thể cache, nhưng cache phải cân nhắc lifecycle của class loader để tránh giữ class loader ngoài ý muốn.
Method method = type.getDeclaredMethod("calculate", Order.class);
Object value = method.invoke(target, order);
MethodHandle và VarHandle
MethodHandle là một typed, directly executable reference tới method/constructor/field-like operation và tích hợp sâu hơn với JVM dynamic invocation/optimization. VarHandle cung cấp access mode cho field/array với memory semantics như opaque, acquire/release, volatile và compare-and-set (CAS). Đây là primitives phù hợp cho library/infrastructure; chúng không phải lý do để thay field access thông thường trong application code.
Dynamic proxy
JDK dynamic proxy tạo implementation runtime cho interface và chuyển method call tới InvocationHandler. Proxy dựa trên class concrete cần cơ chế bytecode generation khác. Khi dùng proxy phải nhớ các tác động tới identity, equals/hashCode, final method, self-invocation và annotation lookup — đây là các điểm rất quan trọng khi học framework như Spring.
| Công cụ | Mục đích chính | Điểm cần cẩn thận |
|---|---|---|
| Reflection | Khám phá/gọi member runtime | Encapsulation, type-safety, modules, cache/classloader lifecycle |
MethodHandle | Typed executable reference cho dynamic invocation | Lookup/access rules và complexity |
VarHandle | Low-level variable access + memory ordering/CAS | Concurrency semantics phải hiểu chính xác |
| JDK dynamic proxy | Intercept interface method call | Interface-only model, identity/equality, self-invocation, annotation visibility |
7. Regex, locale và time
Regex
Regex dùng lặp lại nên compile thành Pattern. Regex hoặc input do người dùng kiểm soát có thể gây catastrophic backtracking/ReDoS nếu pattern có cấu trúc backtracking tệ. Cần giới hạn input và tránh pattern nguy hiểm thay vì coi regex là validation “miễn phí”.
private static final Pattern ID = Pattern.compile("[A-Z]{2}-\\d+");
boolean valid = ID.matcher(value).matches();
Locale
Case conversion cho identifier, protocol token hoặc machine-readable key nên dùng Locale.ROOT để tránh behavior thay đổi theo locale máy/người dùng. Với text hiển thị cho con người, dùng locale phù hợp user/UI thay vì hard-code machine rule.
String normalizedKey = input.toLowerCase(Locale.ROOT);
Date/time
Date/time cần phân biệt instant (một điểm trên timeline), local representation (ngày/giờ không tự mang offset) và zone rules. DST khiến “một ngày lịch” không luôn bằng đúng 24 giờ; vì vậy duration-based calculation và calendar-based calculation có semantics khác nhau.
8. Interview checklist
- Giải thích byte stream khác character stream và tại sao charset phải explicit.
- Giải thích vì sao
charkhông đồng nghĩa với một Unicode code point. - Biết lifecycle của
Files.lines/list/walkvà khi nào cần try-with-resources. - Phân biệt lexical normalize, symbolic link, real path và TOCTOU.
- So sánh blocking, non-blocking Selector và blocking style trên virtual thread; nhấn mạnh backpressure/resource limits vẫn tồn tại.
- Phân biệt connect timeout, request timeout và unknown outcome khi retry.
- Nêu rủi ro native serialization và vai trò của filter/validation.
- Phân biệt annotation retention và annotation processing vs runtime reflection.
- Giải thích reflection,
MethodHandle,VarHandlevà JDK dynamic proxy ở đúng abstraction level. - Nêu được regex ReDoS,
Locale.ROOTcho machine keys và DST/time-zone semantics.
Scope: trang này tập trung mental model và production traps của platform API; networking protocol design, advanced concurrency và framework proxy semantics được học sâu ở các chuyên đề kế tiếp.