Type Casting và var Keyword
Sau bài này, bạn sẽ:
- Hiểu và thực hiện type casting (widening và narrowing) một cách chính xác
- Nắm vững quy tắc Type Promotion trong expressions (JLS §5.6)
- Sử dụng var keyword (Java 10+) đúng cách và biết khi nào nên/không nên dùng
- Tránh các lỗi phổ biến liên quan đến overflow và type inference
Bài trước: Kiểu dữ liệu và Biến — Đã học về primitive types, reference types, và cách khai báo biến. Bài này sẽ tìm hiểu về chuyển đổi kiểu dữ liệu và var keyword.
Type Casting
Type casting là chuyển đổi từ type này sang type khác.
1. Widening Casting (Implicit) - Tự động
Từ nhỏ → lớn: Java tự động convert, không mất dữ liệu.
Ví dụ:
int x = 10;
double y = x; // Automatic: int → double
System.out.println(y); // 10.0
byte b = 100;
int i = b; // Automatic: byte → int
long l = i; // Automatic: int → long
Không mất dữ liệu:
int number = 123456;
long bigNumber = number; // 123456 (exact)
double decimal = number; // 123456.0 (exact)
2. Narrowing Casting (Explicit) - Thủ công
Từ lớn → nhỏ: PHẢI cast manually, có thể mất dữ liệu.
double → float → long → int → short → byte
→ char
Syntax:
double d = 9.78;
int i = (int) d; // Cast double → int
System.out.println(i); // 9 (mất phần thập phân)
Ví dụ:
// double → int (mất phần thập phân)
double price = 99.99;
int rounded = (int) price; // 99
// long → int (có thể overflow)
long bigNum = 2147483648L; // > int max value
int num = (int) bigNum; // Overflow! Result: -2147483648
// int → byte (có thể overflow)
int x = 130;
byte b = (byte) x; // Overflow! Result: -126
Khi cast từ type lớn → type nhỏ, nếu giá trị vượt quá range → overflow!
int x = 128;
byte b = (byte) x; // Overflow: 128 → -128
// Byte range: -128 to 127
// 128 wraps around to -128
Type Promotion trong Expressions
Khi tính toán với numeric types, Java tự động promote (mở rộng) operands theo quy tắc chặt chẽ.
Binary Numeric Promotion Rules (2 operands):
Khi có expression a OP b (ví dụ: a + b, a * b):
- Nếu một trong hai là
double→ cả hai promote thànhdouble - Nếu không, nếu một trong hai là
float→ cả hai promote thànhfloat - Nếu không, nếu một trong hai là
long→ cả hai promote thànhlong - Nếu không → cả hai promote thành
int(minimum!)
// Rule 1: double wins
int i = 10;
double d = 5.0;
double result = i + d; // int promoted to double → result is double
// Rule 2: float wins
int x = 5;
float f = 2.5f;
float result2 = x + f; // int promoted to float → result is float
// Rule 3: long wins
int a = 100;
long b = 200L;
long result3 = a + b; // int promoted to long → result is long
// Rule 4: Everything else becomes int
byte b1 = 10;
byte b2 = 20;
int result4 = b1 + b2; // ❗ byte + byte → int (not byte!)
Câu hỏi OCP điển hình:
byte b1 = 10;
byte b2 = 20;
byte b3 = b1 + b2; // ❌ Compile error!
// Error: incompatible types: possible lossy conversion from int to byte
Tại sao?
byte + bytepromote thànhint(theo rule 4)- Result là
int, không thể gán trực tiếp chobyte - Phải cast explicitly:
byte b1 = 10;
byte b2 = 20;
byte b3 = (byte) (b1 + b2); // ✅ OK với cast
// Hoặc dùng compound assignment (tự động cast)
byte b4 = 10;
b4 += 20; // ✅ OK (equivalent to b4 = (byte)(b4 + 20))
Unary Numeric Promotion (1 operand):
Operators: +, -, ~ (bitwise NOT)
- Nếu operand là
byte,short, hoặcchar→ promote thànhint - Nếu operand là
long,float,double→ giữ nguyên
byte b = 10;
int result = +b; // Unary + promotes byte to int
int result2 = -b; // Unary - promotes byte to int
short s = 100;
int result3 = ~s; // Bitwise NOT promotes short to int
Tại sao promote thành int minimum?
Hầu hết CPU hiện đại được thiết kế optimize cho 32-bit operations (int).
- Operations trên
byte/shortthường chậm hơnint - JVM promote lên
intđể tận dụng CPU instructions hiệu quả - Java designers chọn safety > performance — tránh overflow khi tính toán
Phép chia:
// Integer division (cả hai đều int)
int a = 5;
int b = 2;
int result = a / b; // 2 (không có phần thập phân!)
// Floating-point division (promote to double)
double result2 = (double) a / b; // 2.5 (cast một operand)
double result3 = 5.0 / 2; // 2.5 (literal double)
double result4 = a / (double) b; // 2.5 (cast operand khác)
// Pitfall:
double result5 = a / b; // 2.0 (division trước, rồi mới cast result!)
char là unsigned 16-bit integer (0 to 65535), nên có thể tính toán:
char c1 = 'A'; // 65 (ASCII)
char c2 = 'B'; // 66
int sum = c1 + c2; // 65 + 66 = 131 (promoted to int)
System.out.println(sum); // 131
// Không thể gán trực tiếp:
char c3 = c1 + 1; // ❌ Compile error (int cannot be assigned to char)
char c4 = (char) (c1 + 1); // ✅ OK → 'B'
// Compound assignment OK:
c1 += 1; // ✅ OK → 'B' (auto-cast)
Character arithmetic:
char letter = 'A';
letter++; // ✅ OK → 'B' (unary ++ doesn't promote)
char upper = 'a';
char lower = (char) (upper + 32); // 'a' + 32 = '}' (wrong!)
// Đúng:
char lower2 = (char) (upper - 'a' + 'A'); // 'A'
Compound Assignment tự động cast:
byte b = 10;
b = b + 5; // ❌ Compile error (b + 5 is int)
b += 5; // ✅ OK (equivalent to: b = (byte)(b + 5))
// Tương tự:
short s = 100;
s *= 2; // ✅ OK (auto-cast)
s = s * 2; // ❌ Compile error
Compound assignment có implicit cast, có thể gây overflow:
byte b = 127; // Max byte value
b += 1; // Overflow! → -128 (wraps around)
System.out.println(b); // -128
// Equivalent to:
b = (byte)(b + 1); // 128 cast to byte → -128
var Keyword (Java 10+)
var cho phép Java infer type từ giá trị khởi tạo — giảm boilerplate code.
// Traditional
String name = "Alice";
int age = 25;
ArrayList<String> list = new ArrayList<>();
// With var (Java 10+)
var name = "Alice"; // String (inferred)
var age = 25; // int (inferred)
var list = new ArrayList<String>(); // ArrayList<String> (inferred)
var Type Inference Restrictions
var là reserved type name (không phải keyword!) dùng cho local variable type inference.
Rules:
- ✅ Chỉ dùng cho local variables
- ✅ PHẢI khởi tạo ngay khi khai báo
- ❌ Không dùng cho fields, parameters, return types
// ✅ Valid
var x = 10;
var name = "Alice";
// ❌ Invalid
var y; // Error: cannot infer type (no initializer)
var z = null; // Error: cannot infer type from null
Khi KHÔNG thể dùng var
1. Không có initializer:
var x; // ❌ Cannot infer type
var x = 10; // ✅ OK
2. Initialized với null:
var x = null; // ❌ Cannot infer type from null
String x = null; // ✅ OK
3. Array initializer:
var arr = {1, 2, 3}; // ❌ Cannot use array initializer
var arr = new int[]{1, 2, 3}; // ✅ OK
4. Lambda hoặc method reference:
var func = () -> System.out.println("Hi"); // ❌ Cannot infer
Runnable func = () -> System.out.println("Hi"); // ✅ OK
var printer = System.out::println; // ❌ Cannot infer
Consumer<String> printer = System.out::println; // ✅ OK
5. Instance variables (fields):
public class User {
var name = "Alice"; // ❌ Cannot use var for fields
String name = "Alice"; // ✅ OK
}
6. Method parameters:
public void greet(var name) { } // ❌ Cannot use var for parameters
public void greet(String name) { } // ✅ OK
// Exception: Lambda parameters (Java 11+)
BiFunction<String, String, String> concat = (var a, var b) -> a + b; // ✅ OK
7. Method return types:
public var getName() { return "Alice"; } // ❌ Cannot use var for return
public String getName() { return "Alice"; } // ✅ OK
8. Multiple declarations:
var a = 1, b = 2; // ❌ Cannot declare multiple vars
var a = 1; // ✅ OK
var b = 2; // ✅ OK
9. Poly expressions (diamond operator):
var list = new ArrayList<>(); // ❌ Cannot infer (ArrayList<Object>!)
var list = new ArrayList<String>(); // ✅ OK
// Pitfall:
var list = new ArrayList<>(); // Inferred as ArrayList<Object>
list.add("String"); // ✅ OK
list.add(123); // ✅ OK (accepts anything - not what you want!)
10. Wildcard generics:
var list = getList(); // If getList() returns List<?> → var infers List<?>
// Cannot add anything except null to List<?>
Dùng var khi:
-
Type rõ ràng từ right-hand side
var scanner = new Scanner(System.in); // Obvious: Scanner
var list = new ArrayList<String>(); // Obvious: ArrayList<String>
var connection = DriverManager.getConnection(url); // Obvious return type -
Tên biến đã mô tả type
var userList = new ArrayList<User>(); // userList → clearly a list
var employeeMap = new HashMap<String, Employee>();
KHÔNG dùng var khi:
-
Type không rõ ràng
var result = calculate(); // ❌ Không biết type gì?
int result = calculate(); // ✅ Rõ ràng -
Initializer là literal số
var count = 0; // ❌ int? long? Ambiguous
int count = 0; // ✅ Clear intent -
Có thể nhầm lẫn type
var data = getData(); // getData() returns what? String? byte[]? Object?
var không phải keyword:
// var có thể dùng làm variable name (không khuyến khích!)
int var = 10; // ✅ Legal (var không phải keyword)
System.out.println(var); // 10
// Nhưng không thể dùng cho class name
class var { } // ❌ Compile error
var với Diamond Operator:
// ❌ BAD - infers ArrayList<Object>
var list = new ArrayList<>();
list.add("String");
list.add(123); // Accepts anything!
// ✅ GOOD - explicit type
var list = new ArrayList<String>();
list.add("String");
// list.add(123); // Compile error!
Bài tập thực hành
Bài 1: Type Casting
Viết chương trình:
- Khai báo
double price = 99.99 - Cast sang
intvà in kết quả - Khai báo
int quantity = 5 - Tính
total = price * quantityvà in với 2 chữ số thập phân
Xem đáp án
public class TypeCastingExercise {
public static void main(String[] args) {
double price = 99.99;
int roundedPrice = (int) price;
System.out.println("Rounded price: " + roundedPrice); // 99
int quantity = 5;
double total = price * quantity;
System.out.printf("Total: %.2f%n", total); // 499.95
}
}
Bài 2: BMI Calculator
Viết chương trình tính BMI (Body Mass Index):
- BMI = weight (kg) / (height (m))²
- Input: weight (kg), height (cm)
- Output: BMI với 2 chữ số thập phân
Expected output:
Nhập cân nặng (kg): 70
Nhập chiều cao (cm): 175
BMI của bạn: 22.86
Xem đáp án
import java.util.Scanner;
public class BMICalculator {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Nhập cân nặng (kg): ");
double weight = sc.nextDouble();
System.out.print("Nhập chiều cao (cm): ");
double heightCm = sc.nextDouble();
// Convert cm to meters
double heightM = heightCm / 100;
// Calculate BMI
double bmi = weight / (heightM * heightM);
System.out.printf("BMI của bạn: %.2f%n", bmi);
sc.close();
}
}
Tổng kết
Trong bài này, bạn đã học:
✅ Type Casting: Widening (automatic - nhỏ→lớn), Narrowing (manual - lớn→nhỏ)
✅ Type Promotion: byte/short/char tự động promote thành int trong expressions
✅ Compound Assignment: +=, -= có implicit cast (cẩn thận overflow!)
✅ var keyword: Type inference cho local variables (Java 10+)
✅ var restrictions: Không dùng cho fields, parameters, return types, null, lambdas
Bài tiếp theo chúng ta sẽ học về Toán tử (Operators) - arithmetic, logical, bitwise, và operator precedence!