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

var - Local Variable Type Inference

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

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>>
Type inference vs Dynamic typing
  • 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<>());
Type inference với nested generics
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! varreserved 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>, keyString, valueInteger. 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

Performance impact

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ốngNê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ốngKhông nên dùng var
Type không rõ ràng
Primitive literals
Null initialization
Lambda expressions
Fields, parameters, return types
Nguyên tắc vàng

Readability > Brevity: Dùng var khi nó làm code dễ đọc hơn, không chỉ để code ngắn hơn.

OCP Trap — var is NOT a keyword

varreserved 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.

OCP Trap — var + diamond operator

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>)
📖 JLS §14.4 Local Variable Declarations

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

Đọc thêm