var - Local Variable Type Inference
Sau bài này, bạn sẽ:
- Hiểu được var keyword và type inference trong Java 10+
- Nắm được khi nào nên và không nên sử dụng var
- Phân biệt được var trong Java vs dynamic typing
- Biết được các restrictions của var (chỉ local variables, phải có initializer)
- Áp dụng var best practices để code dễ đọc hơn
Bài trước: Java hiện đại: Tổng quan — Đã tìm hiểu về timeline và các phiên bản Java. Bài này sẽ học về var - local variable type inference từ Java 10.
Giới thiệu
Từ khóa var được giới thiệu trong Java 10 (March 2018), cho phép compiler tự động suy luận kiểu dữ liệu của local variable từ giá trị khởi tạo. Đây không phải là dynamic typing như JavaScript hay Python, mà vẫn là static typing - type được xác định tại compile time.
Cú pháp cơ bản
Trước Java 10
// Khai báo kiểu dài dòng
ArrayList<String> names = new ArrayList<String>();
HashMap<String, List<Integer>> scores = new HashMap<String, List<Integer>>();
// Type dài và phức tạp
Map.Entry<String, List<Student>> entry = map.entrySet().iterator().next();
Với var (Java 10+)
// Compiler tự suy luận type
var names = new ArrayList<String>(); // ArrayList<String>
var scores = new HashMap<String, List<Integer>>(); // HashMap<String, List<Integer>>
// Code ngắn gọn hơn
var entry = map.entrySet().iterator().next(); // Map.Entry<String, List<Student>>
- var trong Java: Type được xác định tại compile time, không thể thay đổi
- JavaScript/Python: Type có thể thay đổi tại runtime
var x = 10; // int
x = "hello"; // COMPILE ERROR - x là int, không thể assign String
Cách compiler suy luận type
Compiler nhìn vào right-hand side (vế phải) để xác định type:
var list = new ArrayList<String>();
// Compiler thấy: new ArrayList<String>()
// Suy luận: list có type ArrayList<String>
var stream = list.stream();
// Compiler thấy: list.stream() returns Stream<String>
// Suy luận: stream có type Stream<String>
var result = stream.filter(s -> s.length() > 5)
.collect(Collectors.toList());
// Compiler suy luận: result có type List<String>
Type Inference Decision Flowchart
var Scope — Dùng ở đâu được?
Khi nào nên dùng var
1. Local variables với type dài và rõ ràng
// GOOD - Type rõ ràng từ right-hand side
var customers = new ArrayList<Customer>();
var config = new HashMap<String, String>();
var connection = DriverManager.getConnection(url);
// BAD - Lặp lại thông tin
ArrayList<Customer> customers = new ArrayList<Customer>();
2. Enhanced for loop
// Before
for (Map.Entry<String, List<Order>> entry : orderMap.entrySet()) {
String customerId = entry.getKey();
List<Order> orders = entry.getValue();
}
// After - Cleaner
for (var entry : orderMap.entrySet()) {
var customerId = entry.getKey();
var orders = entry.getValue();
}
3. Try-with-resources
// Before
try (BufferedReader reader = new BufferedReader(
new FileReader("file.txt"))) {
// Use reader
}
// After
try (var reader = new BufferedReader(new FileReader("file.txt"))) {
// Use reader
}
4. Stream operations
// Dễ đọc hơn với var
var result = orders.stream()
.filter(order -> order.getTotal() > 1000)
.map(Order::getCustomerId)
.collect(Collectors.toSet());
Khi nào KHÔNG nên dùng var
1. Type không rõ ràng từ right-hand side
// BAD - Không rõ type là gì
var result = calculate(); // Trả về int? double? String?
// GOOD - Explicit type
double result = calculate();
2. Giá trị primitive với literals
// BAD - Không rõ intent
var count = 0; // int, nhưng có thể muốn long?
var price = 19.99; // double, nhưng có thể muốn BigDecimal?
// GOOD - Rõ ràng
int count = 0;
BigDecimal price = new BigDecimal("19.99");
3. Diamond operator không đủ thông tin
// COMPILE ERROR - Không đủ thông tin
var list = new ArrayList<>(); // ArrayList of what?
// GOOD
var list = new ArrayList<String>();
4. Null initialization
// COMPILE ERROR
var name = null; // Type là gì?
// GOOD
String name = null;
5. Lambda expressions
// COMPILE ERROR
var lambda = x -> x * 2; // Type của x là gì?
// GOOD
Function<Integer, Integer> lambda = x -> x * 2;
var với Generics
// Generic collections
var names = new ArrayList<String>();
var ages = new HashMap<String, Integer>();
// Generic methods
var optional = Optional.of("hello"); // Optional<String>
var stream = Stream.of(1, 2, 3); // Stream<Integer>
var list = List.of("a", "b", "c"); // List<String>
// Complex generics
var map = new HashMap<String, List<Customer>>();
map.put("VIP", new ArrayList<>());
var map = new HashMap<String, List<String>>();
var innerList = new ArrayList<String>();
map.put("key", innerList); // OK
// Nhưng không thể:
var map = new HashMap<>(); // ERROR - không biết type
var trong Lambda (Java 11)
Java 11 cho phép dùng var trong lambda parameters để có thể thêm annotations:
// Java 8 - Không thể thêm annotation cho inferred parameters
list.stream()
.map((s) -> s.toLowerCase()) // Không thể annotate s
.collect(Collectors.toList());
// Java 11 - Dùng var để thêm annotations
list.stream()
.map((@NonNull var s) -> s.toLowerCase()) // Có thể annotate
.collect(Collectors.toList());
// Hoặc với multiple parameters
map.forEach((@NonNull var key, @Nullable var value) -> {
System.out.println(key + " = " + value);
});
Restrictions (Giới hạn của var)
1. Chỉ dùng cho local variables
public class Example {
// ERROR - Không dùng cho fields
private var name = "Alice";
// ERROR - Không dùng cho parameters
public void setName(var name) {
this.name = name;
}
// ERROR - Không dùng cho return types
public var getName() {
return name;
}
// OK - Local variable
public void printName() {
var localName = this.name; // OK
System.out.println(localName);
}
}
2. Phải có initializer
// ERROR - Không có initializer
var x;
// OK
var x = 10;
3. Không thể dùng với multiple variables
// ERROR
var x = 1, y = 2;
// OK
var x = 1;
var y = 2;
4. Không thể dùng với array initializer
// ERROR
var array = {1, 2, 3};
// OK
var array = new int[]{1, 2, 3};
Mindmap: Giới hạn của var
var với Anonymous Classes — Truy cập member mở rộng
Với var, bạn có thể truy cập member của anonymous class mà explicit type không cho phép:
// Với explicit type — chỉ thấy Object members
Object obj = new Object() {
int x = 10;
String hello() { return "Hello"; }
};
// obj.x; // ❌ Compile error — Object không có field x
// obj.hello(); // ❌ Compile error
// Với var — compiler suy luận anonymous type
var obj = new Object() {
int x = 10;
String hello() { return "Hello"; }
};
System.out.println(obj.x); // ✅ 10
System.out.println(obj.hello()); // ✅ "Hello"
// var giữ lại anonymous type cụ thể, không upcast lên Object!
Intersection Types với var
var có thể capture intersection types mà bạn không thể khai báo explicit:
// Intersection type: Runnable AND Serializable
var task = (Runnable & Serializable) () -> System.out.println("Task");
// Type của task là: Runnable & Serializable (intersection type)
// Không thể khai báo type này bằng explicit declaration!
// Hữu ích khi cần serialize lambda
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(task); // OK vì task là Serializable
"Compile hay không?" — Quiz
Xác định đoạn code nào compile thành công:
// Câu 1:
var var = 10;
Đáp án Câu 1
✅ Compile thành công! var là reserved type name, KHÔNG phải keyword. Có thể dùng var làm tên biến. Nhưng var KHÔNG THỂ dùng làm tên class/interface.
var var = 10; // ✅ OK — var là tên biến, var là type name
int var = 20; // ✅ OK — var là tên biến
// class var {} // ❌ Compile error — không thể dùng var làm tên class
// Câu 2:
var list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);
Đáp án Câu 2
❌ Compile error ở dòng 3! new ArrayList<>() với diamond operator + var → compiler infers ArrayList<Object> (không đủ thông tin). list.get(0) trả về Object, không phải String.
var list = new ArrayList<>(); // ArrayList<Object>
list.add("Hello"); // OK — add(Object)
String s = list.get(0); // ❌ Error: Object cannot be assigned to String
Object o = list.get(0); // ✅ OK
String s = (String) list.get(0); // ✅ OK — explicit cast
// Câu 3:
var x = 1, y = 2;
Đáp án Câu 3
❌ Compile error! var không hỗ trợ multiple variable declaration trên cùng dòng.
// Câu 4:
for (var entry : Map.of("a", 1).entrySet()) {
var key = entry.getKey();
var value = entry.getValue();
}
Đáp án Câu 4
✅ Compile thành công! var trong enhanced for loop: entry có type Map.Entry<String, Integer>, key là String, value là Integer. Hoàn toàn hợp lệ.
Best Practices
Decision Tree: Khi nào nên dùng var?
1. Readability first
// GOOD - Type rõ ràng
var users = userRepository.findAll();
var config = ConfigLoader.load("app.properties");
// BAD - Type không rõ
var data = process(); // data là gì?
2. Dùng descriptive variable names
// BAD
var a = new ArrayList<String>();
var r = repository.find(id);
// GOOD
var customerNames = new ArrayList<String>();
var customerRecord = repository.find(id);
3. Avoid over-use
// TOO MUCH var
var a = 10;
var b = 20;
var c = a + b;
var d = c * 2;
// BETTER - Dùng var có chọn lọc
int a = 10;
int b = 20;
int sum = a + b;
var doubleSum = sum * 2; // Chỉ dùng var khi có lợi
4. Consider team preferences
Một số team thích explicit types, một số thích var. Quan trọng là consistency:
// Consistent style 1 - More var
var customers = new ArrayList<Customer>();
var orders = orderService.getOrders();
var total = calculator.calculate(orders);
// Consistent style 2 - Less var
List<Customer> customers = new ArrayList<>();
List<Order> orders = orderService.getOrders();
BigDecimal total = calculator.calculate(orders);
Ví dụ so sánh: Code có và không có var
Ví dụ 1: JDBC Connection
// WITHOUT var
public List<Customer> findCustomers() {
List<Customer> customers = new ArrayList<>();
try (Connection connection = DriverManager.getConnection(url);
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
Customer customer = new Customer();
customer.setId(resultSet.getLong("id"));
customer.setName(resultSet.getString("name"));
customers.add(customer);
}
} catch (SQLException exception) {
throw new RuntimeException(exception);
}
return customers;
}
// WITH var
public List<Customer> findCustomers() {
var customers = new ArrayList<Customer>();
try (var connection = DriverManager.getConnection(url);
var statement = connection.prepareStatement(sql);
var resultSet = statement.executeQuery()) {
while (resultSet.next()) {
var customer = new Customer();
customer.setId(resultSet.getLong("id"));
customer.setName(resultSet.getString("name"));
customers.add(customer);
}
} catch (SQLException exception) {
throw new RuntimeException(exception);
}
return customers;
}
Ví dụ 2: Stream Processing
// WITHOUT var
public Map<String, Long> countOrdersByStatus() {
List<Order> orders = orderRepository.findAll();
Map<String, Long> statusCounts = orders.stream()
.collect(Collectors.groupingBy(
Order::getStatus,
Collectors.counting()
));
return statusCounts;
}
// WITH var
public Map<String, Long> countOrdersByStatus() {
var orders = orderRepository.findAll();
var statusCounts = orders.stream()
.collect(Collectors.groupingBy(
Order::getStatus,
Collectors.counting()
));
return statusCounts;
}
Ví dụ 3: Complex Generics
// WITHOUT var - Verbose
Map<String, List<Map<String, Object>>> complexData =
new HashMap<String, List<Map<String, Object>>>();
for (Map.Entry<String, List<Map<String, Object>>> entry :
complexData.entrySet()) {
String key = entry.getKey();
List<Map<String, Object>> values = entry.getValue();
// Process
}
// WITH var - Cleaner
var complexData = new HashMap<String, List<Map<String, Object>>>();
for (var entry : complexData.entrySet()) {
var key = entry.getKey();
var values = entry.getValue();
// Process
}
State Diagram: Quá trình Type Inference của Compiler
Performance
var không có runtime overhead. Compiler suy luận type tại compile time, bytecode giống hệt như khi khai báo explicit type.
// Cả hai compile thành bytecode giống hệt nhau
String name1 = "Alice";
var name2 = "Alice";
Kết luận
Khi nào dùng var
| Tình huống | Nên dùng var |
|---|---|
| Type dài và phức tạp | ✅ |
| Type rõ ràng từ right-hand side | ✅ |
| Enhanced for loop | ✅ |
| Try-with-resources | ✅ |
| Stream operations | ✅ |
Khi nào KHÔNG dùng var
| Tình huống | Không nên dùng var |
|---|---|
| Type không rõ ràng | ❌ |
| Primitive literals | ❌ |
| Null initialization | ❌ |
| Lambda expressions | ❌ |
| Fields, parameters, return types | ❌ |
Readability > Brevity: Dùng var khi nó làm code dễ đọc hơn, không chỉ để code ngắn hơn.
var là reserved type name (tên kiểu dành riêng), KHÔNG phải keyword. Do đó:
var var = 10;→ hợp lệ (var là tên biến)int var = 20;→ hợp lệclass var {}→ KHÔNG hợp lệ (không thể dùng var làm tên class/interface)
Đề thi OCP rất thích hỏi: "Đoạn code nào compile?" và cho var var = ... làm đáp án đánh lừa.
var list = new ArrayList<>() suy luận ra ArrayList<Object>, KHÔNG phải ArrayList<String>. Diamond operator <> cần target type để infer generic type, nhưng var không cung cấp target type.
var list = new ArrayList<>(); // ArrayList<Object> ← TRAP!
var list = new ArrayList<String>(); // ArrayList<String> ← OK
List<String> list = new ArrayList<>(); // ArrayList<String> ← OK (target type = List<String>)
Theo JLS, var chỉ hợp lệ cho local variable declarations có initializer. Compiler thực hiện upward projection để xác định type — loại bỏ capture types và intersection types nội bộ, trả về type gần nhất mà programmer có thể viết.
Tham khảo: JLS §14.4
Bài tập thực hành
Bài 1: Refactoring với var
Refactor đoạn code sau sử dụng var ở những chỗ phù hợp:
public void processOrders() {
List<Order> orders = orderRepository.findAll();
Map<String, List<Order>> ordersByCustomer = new HashMap<>();
for (Order order : orders) {
String customerId = order.getCustomerId();
List<Order> customerOrders = ordersByCustomer.get(customerId);
if (customerOrders == null) {
customerOrders = new ArrayList<>();
ordersByCustomer.put(customerId, customerOrders);
}
customerOrders.add(order);
}
}
Bài 2: Identify problems
Tìm lỗi trong đoạn code sau:
public class Example {
private var count = 0; // (1)
public void method(var param) { // (2)
var x; // (3)
var y = null; // (4)
var list = new ArrayList<>(); // (5)
}
}
Tài liệu tham khảo
- JEP 286: Local-Variable Type Inference
- JEP 323: Local-Variable Syntax for Lambda Parameters
- Oracle Java var Guide