Ký tự đại diện (?, extends, super)
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 57 trên 87.
Tham số kiểu có giới hạn giúp giới hạn kiểu tổng quát vào những kiểu cụ thể, nhưng nếu bạn đang làm việc với các kiểu tổng quát có sẵn mà không biết hoặc không quan tâm đến tham số kiểu chính xác thì sao? Đây là lúc wildcard phát huy tác dụng.
Ký tự đại diện không giới hạn ? đại diện cho một kiểu chưa xác định. Nó hữu ích khi bạn muốn chấp nhận bất kỳ kiểu có tham số nào:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // Chạy được
printList(new ArrayList<Integer>()); // Chạy đượcWildcard giới hạn trên ? extends Type chấp nhận kiểu được chỉ định hoặc bất kỳ lớp con nào của kiểu đó. Hãy dùng nó khi bạn cần đọc từ một cấu trúc tổng quát:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // Danh sách Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // Danh sách DoubleKý tự đại diện có giới hạn dưới ? super Type chấp nhận kiểu được chỉ định hoặc bất kỳ lớp cha nào của kiểu đó. Dùng nó khi bạn cần ghi vào một cấu trúc tổng quát:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // Chạy được vì Number là kiểu cha của IntegerMột nguyên tắc hữu ích: dùng extends khi bạn chỉ đọc (bên tạo dữ liệu), dùng super khi bạn chỉ ghi (bên nhận dữ liệu). Nguyên tắc này được gọi là PECS: bên tạo dữ liệu dùng Extends, bên nhận dữ liệu dùng Super.
Thử thách
DễHãy xây dựng một hệ thống cho ăn ở sở thú để minh họa cả ba loại wildcard! Bạn sẽ tạo một hệ thống phân cấp động vật và thức ăn, rồi viết các phương thức tiện ích sử dụng wildcard không giới hạn, giới hạn trên và giới hạn dưới để xử lý những tình huống cho ăn khác nhau.
Bạn sẽ sắp xếp code trong bốn tệp:
Food.java: Tạo một hệ thống phân cấp lớp đơn giản cho thức ăn. Bắt đầu với lớp cơ sởFood, có trường riêngname(String), hàm khởi tạo để khởi tạo trường này và phương thứcgetName(). Sau đó, tạo hai lớp con trong cùng tệp:Meatkế thừa Food (hàm khởi tạo gọisuper(name)) vàVegetablekế thừa Food (theo cách tương tự).Animal.java: Tạo lớp cơ sởAnimalcó trường riêngspecies(String), hàm khởi tạo và phương thứcgetSpecies(). Tạo hai lớp con:CarnivorevàHerbivore, mỗi lớp kế thừa Animal và gọi hàm khởi tạo của lớp cha.ZooFeeder.java: Đây là nơi wildcard phát huy tác dụng! Tạo một lớp tiện ích có ba phương thức static:printInventory(List<?> items)- Dùng wildcard không giới hạn để in ra danh sách bất kỳ. Duyệt qua danh sách và in từng phần tử bằngtoString(), mỗi phần tử trên một dòng.calculateTotalFood(List<? extends Food> foods)- Dùng wildcard giới hạn trên để đọc từ một danh sách chứa kiểu Food bất kỳ. Trả về số lượng phần tử dưới dạngint. Đây là ví dụ về việc đọc từ một nguồn cung cấp.addMeatToStock(List<? super Meat> stock, String meatName)- Dùng wildcard giới hạn dưới để ghi vào một danh sách có thể chứa Meat. Tạo một đối tượngMeatmới với tên đã cho rồi thêm đối tượng đó vào kho. Đây là ví dụ về việc ghi vào một nơi tiếp nhận dữ liệu.Main.java: Kết hợp các phần để hoàn thiện hệ thống cho ăn ở sở thú! Bạn sẽ nhận hai đầu vào: tên một loại thịt (String) và tên một loại rau (String).Trước tiên, tạo một
ArrayList<Food>rồi thêm một đối tượngMeatvới tên loại thịt và một đối tượngVegetablevới tên loại rau. In raFood inventory:, sau đó gọiprintInventoryvới danh sách này.Tiếp theo, in một dòng trống, rồi in
Total food items: [count]bằng cách gọicalculateTotalFoodvới danh sách thức ăn.Cuối cùng, tạo một
ArrayList<Food>mới có tênmeatStock. In một dòng trống, rồi inAdding to meat stock.... GọiaddMeatToStockvới danh sách này và chuỗi"Beef". Sau đó, inMeat stock after adding:rồi gọiprintInventoryvới kho thịt.
Bạn sẽ nhận hai đầu vào theo thứ tự: tên một loại thịt và tên một loại rau.
Ghi đè toString() trong lớp Food để trả về tên thức ăn, nhờ đó việc in ra hoạt động chính xác. Hãy chú ý mỗi kiểu wildcard có một mục đích khác nhau: ? để linh hoạt tối đa khi bạn chỉ cần đọc dữ liệu dưới dạng Object, ? extends khi đọc các kiểu cụ thể và ? super khi ghi vào một collection!
Tự làm thử
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: Tạo một ArrayList<Food> và thêm các đối tượng Meat và Vegetable
// TODO: In ra "Food inventory:", rồi gọi printInventory
// TODO: In ra một dòng trống, rồi in "Total food items: [count]" bằng calculateTotalFood
// TODO: Tạo một ArrayList<Food> mới tên là meatStock
// TODO: In ra một dòng trống, rồi in "Adding to meat stock..."
// TODO: Gọi addMeatToStock với meatStock và "Beef"
// TODO: In ra "Meat stock after adding:", rồi gọi printInventory với meatStock
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online