Java Foundations
15/39
Bài 15 / 39~12 phútCú pháp Java & Kiểu dữ liệuLộ trình · chặng 02/21Miễn phí… lượt xem

Ép kiểu trong Java: Widening, Narrowing và Safe Cast

Nắm vững cơ chế ép kiểu trong Java: widening tự động, narrowing tường minh có nguy cơ tràn số, autoboxing và pattern matching instanceof an toàn.

TL;DR: Java chia ép kiểu thành hai cơ chế chính: widening (mở rộng kiểu tự động, an toàn về phạm vi) và narrowing (thu hẹp kiểu, yêu cầu cú pháp cast tường minh và tiềm ẩn nguy cơ tràn số hoặc mất phần thập phân). Với kiểu tham chiếu, upcasting luôn tự động và an toàn, trong khi downcasting cần cú pháp (ChildType) và có thể ném ClassCastException nếu kiểu thực tế không tương thích. Hãy dùng pattern matching instanceof (Java 16+) để downcast an toàn và Math.toIntExact() để bắt lỗi tràn số khi thu hẹp kiểu số nguyên.

Bài 3 đã giới thiệu wrapper class và autoboxing, còn Bài 4 đề cập đến phép chia số nguyên cần ép kiểu. Bài này đi sâu vào toàn bộ cơ chế chuyển đổi kiểu dữ liệu trong Java: khi nào trình biên dịch tự động nâng kiểu, khi nào bạn phải ép kiểu tường minh, và cách phòng ngừa mất dữ liệu hoặc sập chương trình tại runtime.

1. Analogy đổ nước giữa các chai mô tả cơ chế ép kiểu như thế nào?

Hãy hình dung việc ép kiểu tương tự như đổ nước giữa các chai có thể tích khác nhau:

  • Đổ chai 100 ml vào chai 500 ml: Quá trình diễn ra tự nhiên, không mất một giọt nước nào và không cần kiểm tra. Đây là cơ chế widening (mở rộng kiểu tự động).
  • Đổ chai 500 ml vào chai 100 ml: Nước có nguy cơ tràn ra ngoài nếu lượng nước ban đầu vượt quá 100 ml. Java bắt buộc bạn phải viết cú pháp ép kiểu tường minh như lời cam kết chấp nhận rủi ro này. Đây là cơ chế narrowing (thu hẹp kiểu).

2. Cơ chế widening mở rộng kiểu tự động hoạt động ra sao?

Widening primitive conversion xảy ra hoàn toàn tự động khi bạn gán giá trị sang một kiểu dữ liệu có kích thước bit hoặc miền giá trị rộng hơn:

Java
byte  b = 42;
short s = b;    // byte -> short: tu dong
int   i = s;    // short -> int:  tu dong
long  l = i;    // int -> long:   tu dong
float f = l;    // long -> float: tu dong (co the mat precision)
double d = f;   // float -> double: tu dong

Thứ tự mở rộng kiểu chuẩn giữa các kiểu nguyên thủy:

Chuỗi widening tự động từ byte 8 bit lên short, int, long, float rồi double, với char 16 bit không dấu đi vào từ bên hông ở bậc int; mũi tên từ long sang float được đánh dấu là chỗ chữ số có thể rụng

Kiểu char là số nguyên không dấu 16-bit (0–65535) nên có thể tự động widening sang int thành mã code point Unicode:

Java
char c = 'A';
int code = c;  // 65 -- Unicode code point cua 'A'
System.out.println(code);  // 65
Cảnh báo mất độ chính xác với float và double

Chuyển đổi int -> float hoặc long -> double là widening hợp lệ về mặt phạm vi nhưng có thể làm rụng các chữ số có nghĩa do float chỉ có 23 bit mantissa (~7 chữ số thập phân) và double có 52 bit mantissa (~15 chữ số).

3. Cơ chế narrowing thu hẹp kiểu tiềm ẩn rủi ro mất dữ liệu gì?

Narrowing primitive conversion xảy ra khi chuyển từ kiểu lớn hơn về kiểu nhỏ hơn. Bạn bắt buộc phải chỉ định kiểu đích trong dấu ngoặc đơn:

Java
double d = 3.99;
int i = (d);        // COMPILE ERROR neu khong viet (int)
int i = (int) d;    // 3 -- cat bo phan thap phan ve phia 0

long l = 100L;
int x = (int) l;    // OK neu l nam trong khoang int

Khi giá trị vượt quá sức chứa của kiểu đích, Java áp dụng cơ chế cắt bit bù 2 (two's complement wrap-around) một cách im lặng:

Java
int big = 130;
byte b = (byte) big;  // byte chua tu -128 den 127
System.out.println(b); // -126 (130 - 256 = -126)
Biểu thứcKết quảBản chất cơ chế
(int) 3.93Cắt bỏ phần thập phân về 0 (truncate toward zero)
(int) -3.9-3Cắt bỏ phần thập phân về 0
(byte) 127127Đúng giá trị cực đại của byte
(byte) 128-128Tràn số bit bù 2 (wrap-around)
(byte) 130-126130 - 256 = -126
(short) 40_000-2553640000 - 65536 = -25536

Thử ngẫmmột biến đếm số lượng giao dịch từ database trả về long được ép kiểu thô (int) total — khi dữ liệu vượt 2,1 tỷ bản ghi, hệ thống sẽ báo lỗi hay âm thầm ghi nhận số âm?

4. Ép kiểu kiểu tham chiếu upcasting và downcasting khác nhau thế nào?

Với kiểu tham chiếu (reference types), ép kiểu tuân thủ mối quan hệ kế thừa hướng đối tượng:

4.1 Upcasting: An toàn và tự động

Upcasting chuyển tham chiếu từ lớp con lên lớp cha. Quá trình này luôn an toàn vì lớp con luôn kế thừa đầy đủ hành vi của lớp cha:

Java
String text = "OLHub Platform";
Object obj = text;  // Upcasting tu dong -- String IS-A Object

4.2 Downcasting: Tiềm ẩn ngoại lệ ClassCastException

Downcasting chuyển từ lớp cha về lớp con. Do một biến kiểu cha có thể đang nắm giữ bất kỳ đối tượng con nào, JVM yêu cầu bạn viết ép kiểu tường minh và sẽ kiểm tra kiểu thực tế tại runtime:

Java
Object obj = "Hello";
String s = (String) obj; // Hop le tai runtime vi obj thuc su la String

Object numberObj = Integer.valueOf(42);
String bad = (String) numberObj; // ClassCastException tai runtime!

Biến kiểu cha đi qua câu hỏi instanceof: khớp thì dùng thẳng biến đã được pattern matching gán, không khớp thì rẽ sang nhánh khác hoặc giá trị mặc định

5. Mối quan hệ giữa autoboxing, unboxing và type casting là gì?

Autoboxing và unboxing là cơ chế chuyển đổi giữa kiểu nguyên thủy và wrapper class tương ứng:

Java
int primitive = 100;
Integer boxed = primitive; // Autoboxing: Integer.valueOf(100)
int unboxed = boxed;       // Unboxing: boxed.intValue()
Bẫy NullPointerException khi unbox

Nếu wrapper object mang giá trị null, quá trình unboxing sẽ kích hoạt boxed.intValue() và ném ngay NullPointerException. Luôn kiểm tra khác null trước khi unbox.

Lưu ý rằng hai wrapper class khác nhau không có quan hệ kế thừa trực tiếp, do đó không thể ép kiểu ngang hàng:

Java
Integer i = 10;
// Long l = (Long) i; // COMPILE ERROR -- Integer khong the cast sang Long
long safeLong = i.longValue(); // Chuyen doi qua method hop le

6. Các mẫu hình safe cast và pattern matching instanceof hoạt động ra sao?

6.1 Thu hẹp an toàn với Math.toIntExact

Thay vì ép kiểu thô (int) bigLong khiến số bị tràn im lặng, hãy dùng Math.toIntExact() để kích hoạt cơ chế fail-fast:

Java
long value = 3_000_000_000L;
try {
    int safeInt = Math.toIntExact(value);
} catch (ArithmeticException e) {
    System.out.println("Phat hien tran so: " + e.getMessage());
}

6.2 Pattern matching for instanceof từ Java 16

Java 16 chính thức giới thiệu pattern matching cho instanceof, giúp gộp thao tác kiểm tra kiểu và ép kiểu vào một biểu thức duy nhất:

Java
Object item = "Java Foundations";

if (item instanceof String s) {
    // Bien s tu dong mang kieu String ma khong can ep kieu thu cong
    System.out.println("Do dai chuoi: " + s.length());
}

6.3 Chuyển đổi giữa String và số nguyên

Không thể ép kiểu trực tiếp giữa String và int. Hãy dùng các hàm tiện ích chuẩn:

Java
// String sang int
int parsed = Integer.parseInt("12345"); // Nem NumberFormatException neu loi format

// int sang String
String text = String.valueOf(12345);

7. Mã nguồn ví dụ minh họa toàn diện cơ chế ép kiểu như thế nào?

Java
public class TypeCastingDemo {
    public static void main(String[] args) {
        // 1. Widening tu dong
        int count = 50;
        double price = count; // int -> double: tu dong
        System.out.println("Price: " + price);

        // 2. Narrowing voi Math.round vs truncate
        double rawScore = 8.75;
        int truncated = (int) rawScore;          // 8
        int rounded = (int) Math.round(rawScore); // 9
        System.out.println("Truncated: " + truncated + ", Rounded: " + rounded);

        // 3. Narrowing fail-fast
        long userCount = 5_000_000_000L;
        try {
            int safeCount = Math.toIntExact(userCount);
        } catch (ArithmeticException ex) {
            System.out.println("Bat tran so an toan voi Math.toIntExact");
        }

        // 4. Pattern matching instanceof an toan
        Object data = "OLHub Platform 2026";
        if (data instanceof String str && !str.isEmpty()) {
            System.out.println("Noi dung hop le: " + str.toUpperCase());
        }
    }
}

8. Các bẫy pitfall thường gặp nhất khi ép kiểu trong Java là gì?

Tình huống rủi roCú pháp có lỗiHậu quả thực tếGiải pháp an toàn
Cắt phần thập phân(int) 4.9Nhận giá trị 4 thay vì 5Dùng (int) Math.round(d)
Tràn số nguyên ngầm(byte) 130Nhận kết quả -126 im lặngKiểm tra cận biên hoặc Math.toIntExact()
Downcast sai kiểu(String) objNumberNém ClassCastExceptionDùng if (obj instanceof String s)
Unboxing biến nullint x = nullableIntegerNém NullPointerExceptionKiểm tra null hoặc gán giá trị mặc định
Ép kiểu String sang int(int) "42"Lỗi biên dịch (Compile error)Dùng Integer.parseInt(str)

9. Tài liệu Deep Dive Oracle quy định gì về type conversion?

📚 Deep Dive Oracle — tài liệu đặc tả chuẩn

10. Điểm cốt lõi cần nhớ về toàn bộ cơ chế ép kiểu là gì?

  • Widening: Tự động mở rộng kiểu, an toàn về miền giá trị nhưng cần cẩn trọng rụng độ chính xác với float.
  • Narrowing: Thu hẹp kiểu bắt buộc khai báo tường minh; dùng Math.toIntExact() để chống tràn số âm thầm.
  • Upcasting & Downcasting: Upcast luôn an toàn; downcast bắt buộc bảo vệ bằng instanceof dạng pattern matching.
  • String và số: Tuyệt đối không dùng toán tử ép kiểu giữa chuỗi và số nguyên, hãy dùng Integer.parseInt().

11. Tự kiểm tra kiến thức về ép kiểu trong Java như thế nào?

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Vì sao int i = 100; float f = i; được chấp nhận tự động trong khi float f = 100.0f; int i = f; lại báo lỗi biên dịch?
  2. Q2
    Giá trị của biểu thức (byte) 130 trong Java là bao nhiêu? Giải thích cơ chế bên dưới.
  3. Q3
    Đoạn mã Integer val = null; int x = val; sẽ phát sinh lỗi gì khi thực thi? Sửa thế nào?
  4. Q4
    Phương thức nào trong gói java.lang.Math giúp thu hẹp long về int kèm cơ chế fail-fast khi tràn số?
  5. Q5
    Lợi ích chính của Pattern Matching for instanceof (Java 16+) so với cách kiểm tra truyền thống là gì?

Bài tiếp theo: String, Text Block và StringBuilder — xử lý text hiện đại trong Java

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Lộ trình Junior Java Backend Developer

Chặng 2/21 · Lập trình từ số 0 với Java — bài 15/32 của chặng

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

String, Text Block và StringBuilder trong Java hiện đại