Chuyển tới nội dung chính

Type Casting và var Keyword

Mục tiêu bài học

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ớnnhỏ: 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
Overflow

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

📖 Theo JLS §5.6 (Numeric Promotion)

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):

  1. Nếu một trong haidouble → cả hai promote thành double
  2. Nếu không, nếu một trong haifloat → cả hai promote thành float
  3. Nếu không, nếu một trong hailong → cả hai promote thành long
  4. 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!)
🔥 Bẫy OCP: byte + byte = int (không phải 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 + byte promote thành int (theo rule 4)
  • Result là int, không thể gán trực tiếp cho byte
  • 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ặc char → promote thành int
  • 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?

💡 Cách nhớ: CPU thích int nhất

Hầu hết CPU hiện đại được thiết kế optimize cho 32-bit operations (int).

  • Operations trên byte/short thường chậm hơn int
  • 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!)
🔥 Bẫy OCP: char arithmetic

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
🔥 Bẫy OCP: Compound assignment narrowing

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

📖 Theo JLS §14.4 (Local Variable Declaration Statements)

varreserved 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

🔥 Bẫy OCP: var restrictions

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<?>
Khi nào dùng var?

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:

  1. Khai báo double price = 99.99
  2. Cast sang int và in kết quả
  3. Khai báo int quantity = 5
  4. Tính total = price * quantity và 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ước tiếp theo

Bài tiếp theo chúng ta sẽ học về Toán tử (Operators) - arithmetic, logical, bitwise, và operator precedence!

👉 Tiếp theo: Toán tử →