Mục lục bài họcĐang ở d03-b4
Polymorphism, the Object Class, and is-a versus has-a
Two types, two jobs
In Employee e = new Manager("An", 8, 4); the variable has a declared type (Employee) and the object has an actual type (Manager). The compiler checks the declared type: e.bonus() fails to compile if bonus exists only in Manager. At run time Java uses the actual type to decide which body executes, so e.pay() runs the Manager version. That late decision is dynamic binding, and the ability of one line of code to run different bodies is polymorphism.
The payoff is an array or list of the superclass type holding mixed objects: one loop calling pay() makes every element behave according to its own class, and adding a new subclass later requires no change to the loop.
Everything extends Object
Every class inherits from Object, whose two methods matter here. The default toString() prints a class name and a hash code, which is useless, so classes override it; System.out.println(obj) calls toString() automatically. The default equals() compares references — true only when both variables point at the very same object — so a class that wants value comparison must override it. Note that == on objects always compares references and is never overridable.
is-a versus has-a
Use extends only for is-a (a Manager is an Employee). When one class merely contains another — a Library has Book objects — store it as an instance variable. That is composition, and it is the right answer far more often than inheritance.
Hai kiểu, hai nhiệm vụ
Trong Employee e = new Manager("An", 8, 4); biến có kiểu khai báo (Employee) còn đối tượng có kiểu thật (Manager). Trình biên dịch chỉ nhìn kiểu khai báo: e.bonus() không biên dịch được nếu bonus chỉ có ở Manager. Lúc chạy, Java nhìn kiểu thật để quyết định thân hàm nào chạy, nên e.pay() chạy bản của Manager. Quyết định muộn đó gọi là liên kết động, còn việc một dòng lệnh chạy ra nhiều hành vi khác nhau gọi là đa hình.
Cái lợi nằm ở một mảng kiểu lớp cha chứa lẫn lộn nhiều lớp con: một vòng lặp gọi pay() là mỗi phần tử tự cư xử theo lớp của nó, và sau này thêm một lớp con mới thì vòng lặp không phải sửa một chữ.
Mọi lớp đều kế thừa Object
Mọi lớp đều kế thừa Object, và ở đây hai phương thức của nó đáng quan tâm. Bản toString() mặc định in ra tên lớp kèm một mã băm, gần như vô dụng, nên lớp nào cũng nên ghi đè; System.out.println(obj) tự động gọi toString(). Bản equals() mặc định so sánh tham chiếu — chỉ đúng khi hai biến trỏ vào đúng cùng một đối tượng — nên lớp nào muốn so sánh theo giá trị thì phải ghi đè. Lưu ý == trên đối tượng luôn so tham chiếu và không ghi đè được.
is-a và has-a
Chỉ dùng extends cho quan hệ là một (Manager là một Employee). Khi một lớp chỉ chứa lớp kia — một Library có nhiều Book — thì để nó thành biến thực thể. Đó là quan hệ thành phần, và trong thực tế nó đúng nhiều hơn kế thừa rất nhiều lần.
Employee, nên trình biên dịch chỉ cho phép gọi những gì Employee có. Nhưng lúc chạy, mỗi ô nhảy xuống thân hàm của lớp thật bên dưới. Vì vậy hai câu hỏi phải tách bạch: dòng này có biên dịch được không tra ở hàng trên, còn in ra cái gì tra ở hàng dưới. Trộn hai câu hỏi làm một là nguồn gốc của phần lớn đáp án sai trong chương.Lớp Point có hai toạ độ nguyên. Ghi đè toString và viết một phương thức so sánh theo giá trị. Cho biết hai đối tượng cùng toạ độ thì == và phương thức so sánh cho kết quả gì.
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
public boolean sameAs(Point other) {
return x == other.getX() && y == other.getY();
}
public String toString() {
return "(" + x + ", " + y + ")";
}
}Với Point p = new Point(2, 3); và Point q = new Point(2, 3);:
p == qchofalse. Hai lầnnewtạo ra hai vùng nhớ khác nhau, và==chỉ hỏi hai biến có trỏ vào cùng một chỗ không.p.sameAs(q)chotrue, vì nó so từng toạ độ.System.out.println(p)in ra(2, 3)nhờ bảntoStringđã ghi đè. Nếu không ghi đè, dòng đó in ra một chuỗi kiểuPoint@1b6d3586.
Chú ý other.getX(): x là private nhưng phạm vi private tính theo lớp chứ không theo đối tượng, nên viết thẳng other.x ở đây cũng hợp lệ. Dùng accessor vẫn là thói quen an toàn hơn khi làm bài.
== để so hai đối tượng rồi tưởng mình so giá trị. Với String, bẫy này còn quái hơn vì hai chuỗi hằng giống nhau thường dùng chung một vùng nhớ nên == tình cờ cho true, còn chuỗi ghép lúc chạy thì cho false — cùng một dòng code, hai kết quả. Cách chặn: đặt luật cứng cho mình, == chỉ dùng cho số và boolean, mọi so sánh đối tượng đều gọi equals. Bẫy thứ hai: gọi phương thức chỉ có ở lớp con qua biến kiểu lớp cha — đó là lỗi biên dịch, không phải lỗi lúc chạy, dù đối tượng thật đúng là lớp con.println(obj) gọi toString(). equals mặc định so tham chiếu, muốn so giá trị thì phải ghi đè. Và trước khi gõ extends, thử đọc thành câu X là một Y — không xuôi tai thì thứ bạn cần là biến thực thể, không phải kế thừa.Đọc xong rồi — làm thử ngay
Bài tập của chương Unit 3 — Class Creation gồm 14 câu trắc nghiệm và 8 đề tự luận. Đáp án hiện ngay khi chọn, miễn phí.