Array Covariance và ArrayStoreException
Vấn đề
Phần tiêu đề “Vấn đề”Mảng đối tượng trong Java có tính chất covariant (hiệp biến): nếu Dog là lớp con của Animal, thì Dog[] được coi là một kiểu con của Animal[] - trình biên dịch cho phép gán một Dog[] cho biến kiểu Animal[]. Điều này có thể dẫn đến lỗi lúc chạy chương trình mà trình biên dịch không phát hiện được:
class Animal {}class Dog extends Animal {}class Cat extends Animal {}
Dog[] dogs = new Dog[3];Animal[] animals = dogs; // BIÊN DỊCH OK! Dog[] được coi là Animal[]
animals[0] = new Cat(); // BIÊN DỊCH OK (Cat cũng là Animal)... nhưng NÉM ArrayStoreException lúc chạy!Vì sao nguy hiểm?
Phần tiêu đề “Vì sao nguy hiểm?”Trình biên dịch chỉ thấy biến animals có kiểu Animal[], nên animals[0] = new Cat() có vẻ hợp lệ về mặt kiểu. Nhưng thực tế, animals đang trỏ đến một mảng Dog[] thực sự trên heap - JVM phải kiểm tra kiểu phần tử lúc chạy và phát hiện Cat không phải Dog, ném ra ArrayStoreException. Đây là lỗi chỉ xuất hiện khi chạy, dù trình biên dịch “tưởng” mọi thứ ổn:
Object[] objects = new String[3]; // biên dịch OK - String[] là Object[]objects[0] = "hello"; // OKobjects[1] = 42; // BIÊN DỊCH OK (Integer là Object)... nhưng ArrayStoreException lúc chạy!So sánh với Generic collection
Phần tiêu đề “So sánh với Generic collection”List<T> (generic) thì không cho phép điều tương tự - đây là lỗi biên dịch ngay lập tức, an toàn hơn nhiều:
List<Dog> dogs = new ArrayList<>();// List<Animal> animals = dogs; // LỖI BIÊN DỊCH! List<Dog> KHÔNG phải là List<Animal>Đây chính là một trong những lý do generic collection (List, Map,…) được ưu tiên hơn mảng đối tượng thô trong code Java hiện đại - generic bắt lỗi kiểu ngay lúc biên dịch, mảng chỉ bắt được lúc chạy.
Giải pháp
Phần tiêu đề “Giải pháp”- Ưu tiên dùng generic collection (
List<T>,ArrayList<T>,…) thay vì mảng đối tượng khi cần tính đa hình, để lỗi kiểu được bắt lúc biên dịch thay vì lúc chạy - Nếu buộc phải dùng mảng, tránh gán mảng kiểu con cho biến kiểu cha rồi ghi phần tử không đúng kiểu con gốc vào đó
- Mảng kiểu nguyên thủy (
int[],double[],…) không gặp vấn đề này vì không có covariance giữa các kiểu nguyên thủy
Tóm tắt
Phần tiêu đề “Tóm tắt”Array covariance:
Dog[]được coi làAnimal[], cho phép gán sai kiểu phần tử biên dịch được nhưng némArrayStoreExceptionlúc chạy.
- Generic collection (
List<T>) an toàn hơn mảng đối tượng - bắt lỗi kiểu lúc biên dịch thay vì lúc chạy- Chỉ xảy ra với mảng kiểu tham chiếu (object), không xảy ra với mảng kiểu nguyên thủy