Upcasting and downcasting in java

Hôm nay mình xin chia sẻ một cơ chế khá hay trong java đó chính là upcasting and downcasting object, từ đó mình cũng sẽ đi sâu và chứng minh rõ việc sử dụng tính đa hình của hướng đối tương. Thêm nữa là đi phân tích mối liên quan của object reference giữa thời điểm compiler và runtime.

1. Downcasting

class A {
	void makeNoise() {
		System.out.println("generic noise");
	}
}

class D extends A {
	void makeNoise() {
		System.out.println("bark");
	}

	void play() {
		System.out.println("roll over");
	}
}

Nhìn vào quan hệ giữa các class A và D, việc khai một object Animal có thể thực hiện đơn gian như sau: A a = new D(); Với việc khai báo: A a = new D(); được goi là tính đa hình trong tứ bất tử của java. Điều gi xả ra khi sử dụng object a gọi một phương thức chỉ có ở trong lớp D => Chúng có thể gọi được (bằng việc casting down về kiểu D) nhưng nếu chúng ta gọi trực tiếp như sau: a.play() // error compiler trình biên dịch sẽ báo lỗi là "class A doesn't have a play()".

Vậy tại sao trình biên dịch báo lỗi?

Một câu hỏi khá thu vị phải không. Vì trình biên dich chỉ compile theo kiểu dữ liệu (hay còn gọi là là kiểu trả về) chứ biên dịch không quan tâm tới đối tượng tham chiếu của biến reference là cái gi. Trong trường hơp trên thì a compiler sẽ hiểu a có kiểu là A nên khi gọi sang một hàm trong B thì sẽ không gọi được, vi phạm trình biện dịch. Để làm hết lỗi compiler thì ta thực hiện dòng code sau:

A a = new D();
if (a instanceof D) {
   D d = (D) a;  // casting down
   d.play();
}

với việc sử dụng casting down về một class kế thừa hay còn gọi là subclass lúc này type của a đã được thay đổi về type subclass D. Biến d lúc này bằng kiểu A ép về D => là kiểu D nên việc gọi tới method play() là hoàn toàn hợp lệ với compiler và khi runtime ok. Tiếp theo mình chia sẻ một khía cạnh khác mà compiler pass nhưng runtime lỗi. =)) 2. Mối liên hệ compiler và runtime của object reference

class A {
}

class D extends A {
}

public class MyApplication {
	public static void main(String[] args) {
		A animal = new A();
		D d = (D) animal; //compiles pass but fails runtime
	}
}

Cũng là downcasting từ kiểu A về kiểu D hoàn toàn success với compiler khi mà chỉ dựa vào so sánh kiểu dữ liệu. Nhưng khi ta chạy đoạn code trên thì sinh lỗi "java.lang.ClassCastException".

Vậy tại sao khi compiler success mà runtime lại lỗi?

Đó cũng là điều mình muốn làm rõ đẻ các bạn hiểu hơn về tính đa hình được compiler đối xử như thế nào. Trong trình biên dịch compiler chỉ có thể làm tất cả đó chính là kiểm tra hai kiểu dữ liệu khi gán cho nhau xem đó có là "same inheritance tree" hay là KHÔNNG. Nếu đúng là same inheritance tree thì là pass qua compiler. Đây là điểm rất quan trọng bạn nhé..^^ vd:

A a = new A();
D d = (D) a;
String s = (String) a;  // error compiler casting

compiler bão lỗi ngay vì nhận thấy String và Animal không phải là same inheritance tree. Còn đối với runtime thì một luật bất thành văn đó chính là không thể: casting object của subclass về object superclass. Vì vậy khi runtime mới sinh ra lỗi "java.lang.ClassCastException".

3. Upcasting Trong phần này mình sẽ đi phân tích cơ chế làm việc của upcasting. Trước tiên xem vd sau:

class A {
}

class D extends A {
}

class DogTest {
	public static void main(String[] args) {
		D d = new D();
		A a1 = d;  // upcast ok with implicit cast
		A a2 = (A) d; // upcast ok with explicit cast
	}
}

vd trên sử dụng upcasting D về A sẽ là pass cho cả compiler và runtime.

Vì sao object D lại có thẻ casting về object A?

Tại vì calss D có quan hệ IS-A (quan hệ IS-A như thế nào mình sẽ không nói ở đây vì nó quá phổ thông rồi) đối với class A, vì là quan hệ IS-A => same inheritance tree . Đó là những gi mình chia sẻ hôm nay, hi vọng sẽ giúp các bạn hiển hơn phần nào. Cảm ơn bạn đã theo rõi bài viết của mình..!!