Ép kiểu là gì
Java là ngôn ngữ kiểu tĩnh: mọi giá trị đều có kiểu, và trình biên dịch không để bạn âm thầm trộn lẫn các kiểu không tương thích. Ép kiểu (type casting) là cách bạn chuyển một giá trị từ kiểu này sang kiểu khác: đôi khi Java tự làm giúp bạn, đôi khi bạn phải yêu cầu tường minh.
Có hai chiều. Đi từ kiểu nhỏ sang kiểu lớn (một int vào một double) là an toàn và diễn ra tự động. Đi từ kiểu lớn sang kiểu nhỏ (một double vào một int) có thể mất dữ liệu, nên Java buộc bạn phải viết rõ bằng phép ép kiểu.
Mở rộng: chuyển đổi tự động
Chuyển đổi mở rộng (widening) đưa một giá trị vào kiểu có nhiều chỗ hơn. Không có gì bị mất, nên Java tự làm mà không cần ép kiểu:
int 7 được đặt vào một double và trở thành 7.0. Thứ tự mở rộng của các kiểu số là byte -> short -> int -> long -> float -> double; gán từ trái sang phải theo chuỗi đó không bao giờ cần ép kiểu. Đây là lý do việc trộn kiểu trong phép tính thường "tự nhiên chạy được": Java mở rộng toán hạng nhỏ hơn trước.
Thu hẹp: ép kiểu tường minh
Đi theo chiều ngược lại, tức chuyển đổi thu hẹp (narrowing), có thể làm mất dữ liệu, nên trình biên dịch từ chối trừ khi bạn đặt kiểu đích trong ngoặc đơn trước giá trị:
(int) pi là phép ép kiểu. Hãy để ý nó làm gì: nó cắt bỏ về phía số 0, đơn giản là vứt phần thập phân đi. 3.9 thành 3, và -3.9 thành -3. Nó không làm tròn. Nếu muốn làm tròn, bạn cần một công cụ khác:
Quên rằng phép ép kiểu cắt bỏ phần thập phân là một trong những bug phổ biến nhất của người mới: nếu con số "đã làm tròn" của bạn luôn thấp hơn một đơn vị, thường lý do là ở đây.
Cái bẫy chia số nguyên
Bẫy này ai cũng dính ít nhất một lần. Khi cả hai toán hạng đều là int, toán tử / thực hiện chia số nguyên và vứt bỏ phần dư, trước khi có bất kỳ phép gán nào vào double:
a / b là 7 / 2, được tính hoàn toàn bằng phép toán int, cho ra 3. Mở rộng sang double sau đó chỉ biến 3 thành 3.0: phần .5 đã mất từ trước. Ép một toán hạng sang double trước ((double) a / b) buộc cả biểu thức tính bằng số thực, cho ra 3.5 như bạn muốn. Bạn chỉ cần ép một bên; Java sẽ mở rộng bên kia cho khớp.
Khi thu hẹp bị tràn
Phép ép kiểu không kiểm tra giá trị có vừa hay không. Nếu không vừa, các bit đơn giản là bị cắt và bạn nhận được một kết quả bất ngờ, không lỗi, không cảnh báo:
300 không vừa trong một byte, nên các bit cao bị bỏ đi và bạn nhận được 44. Đây là mất dữ liệu "thầm lặng": chương trình chạy bình thường nhưng cho kết quả sai. Chỉ thu hẹp khi bạn chắc chắn giá trị vừa với kiểu nhỏ hơn.
Chuyển đổi giữa số và chuỗi
String là một đối tượng, không phải một số, nên bạn không thể ép kiểu nó bằng (int): đó là lỗi biên dịch. Thay vào đó, việc chuyển đổi dùng các phương thức chuyên dụng:
Đi từ văn bản sang số dùng Integer.parseInt / Double.parseDouble. Nếu chuỗi không phải số hợp lệ (ví dụ "hello"), các hàm này ném ra NumberFormatException. Đi từ số sang văn bản, dùng String.valueOf(n) hoặc chỉ cần nối với "", vì n + "" buộc int thành String. Hãy cẩn thận với khác biệt giữa "5" + 1 (cho ra chuỗi "51") và 5 + 1 (cho ra 6); + mang nghĩa nối chuỗi ngay khi có một String tham gia.
Ép kiểu đối tượng
Ép kiểu không chỉ áp dụng cho kiểu nguyên thủy mà cả cho tham chiếu đối tượng. Bạn có thể ép một tham chiếu sang kiểu có liên quan dọc theo chuỗi kế thừa: mở rộng lên kiểu cha diễn ra tự động, còn thu hẹp trở xuống cần ép kiểu tường minh và chỉ an toàn nếu đối tượng thật sự thuộc kiểu đó:
Object o = "hello";
String s = (String) o; // OK: o actually refers to a String
Integer bad = (Integer) o; // compiles, but throws ClassCastException at runtime
Phép ép thứ hai biên dịch được nhưng sập lúc chạy với ClassCastException vì đối tượng là một String, không phải Integer. Bạn sẽ gặp lại điều này kỹ hơn khi học đến kế thừa và đa hình; lúc này bạn chỉ cần biết cú pháp (Type) là cùng một ý tưởng, áp dụng cho đối tượng.
Câu hỏi thường gặp
Ép kiểu trong Java là gì?
Ép kiểu (type casting) là chuyển một giá trị từ kiểu dữ liệu này sang kiểu dữ liệu khác. Java tự động thực hiện các chuyển đổi "mở rộng" nhỏ (như int sang double), nhưng chuyển đổi "thu hẹp" có thể làm mất dữ liệu (như double sang int) thì cần ép kiểu tường minh: int n = (int) 3.9;.
Chuyển double sang int trong Java như thế nào?
Đặt kiểu đích trong ngoặc đơn trước giá trị: int n = (int) 3.9;. Phép này cắt bỏ về phía số 0 (bỏ phần thập phân), nên bạn nhận được 3 chứ không phải 4. Muốn làm tròn thì dùng Math.round(3.9), hàm này trả về 4.
Chuyển String sang int trong Java như thế nào?
String không phải là số, nên bạn không thể ép kiểu nó bằng (int). Dùng Integer.parseInt("42") để lấy một int, hoặc Double.parseDouble("3.14") để lấy một double. Nếu văn bản không phải số hợp lệ, các hàm này ném ra NumberFormatException.