Java Bài 31: Ép Kiểu Trong OOP

Posted by

Chào mừng các bạn đã đến với bài học Java số 31, bài học về ép kiểu trong OOP. Đây là bài học trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.

Chắc các bạn còn nhớ, chúng ta đã nói về ép kiểu ở bài học số 6. Và hiển nhiên bạn đã biết khái niệm ép kiểu là gì rồi, bài này chúng ta không cần nhắc đến.

Và ép kiểu ở bài học đó chính là ép kiểu trên các dữ liệu nguyên thủy. Việc ép kiểu lúc bấy giờ được phân biệt làm hai trường hợp riêng biệt, đó là ép kiểu ngầm địnhép kiểu tường minh. Xem ra thì ép kiểu mà bạn đã biết cũng không có gì phức tạp lắm nhỉ. Vậy thì hôm nay, khi biết về OOP, chúng ta sẽ xem việc ép kiểu trên các dữ liệu không-phải-nguyên-thủy sẽ trông như thế nào nhé.

Ép Kiểu Ngầm Định

Vâng, trong OOP, chúng ta cũng có thể phân loại ép kiểu ra làm hai dạng, tường minhngầm định.

Ép kiểu ngầm định với các lớp trong OOP cũng tương tự như ép kiểu ngầm định với các dữ liệu nguyên thủy. Đó là nếu như không có sự mất mát dữ liệu, hay có thể nới rộng khả năng lưu trữ của dữ liệu, thì xem như hệ thống sẽ hoàn toàn ngầm định giúp chúng ta ép kiểu.

Thế nhưng, với OOP, chúng ta có hai trường hợp ép kiểu ngầm định sau đây, mời bạn cùng đi đến hai mục nhỏ bên dưới.

Ép Kiểu Ngầm Định Với Các Đối Tượng Cùng Một Lớp

Thực sự vấn đề này cũng quen thuộc thôi. Nó như là bạn khai báo hai biến int aint b, rồi bạn gán b = a, và kết quả là ab sẽ có cùng một giá trị. Với OOP thì sao, cũng vậy, nó sẽ như là bạn khai báo NhanVien aNhanVien b, rồi bạn gán b = a, kết quả là b với a sẽ mang cùng một nội dung.

Tuy vậy nhưng cũng không hẳn là vậy. Với việc gán hai biến nguyên thủy int vào int như trên thì hết sức bình thường. Nhưng việc bạn gán hai đối tượng NhanVien vào NhanVien thì được xem như một phép ép kiểu, vì sao vậy, chúng ta thử đến ví dụ bên dưới đây.

Giả sử có một lớp NhanVien cực kỳ đơn giản, lớp này chỉ có một thuộc tính ten và các phương thức getter/setter cho nó, và một phương thức giúp xuất thông tin ten ra console.

public class NhanVien {

	protected String ten;
	
	public String getTen() {
		return ten;
	}

	public void setTen(String ten) {
		this.ten = ten;
	}
	
	public void xuatThongTin() {
		System.out.println("Nhân viên: " + ten);
	}
}

Đến phương thức main(). Chúng ta sẽ khai báo hai đối tượng nhanVien1nhanVien2 từ lớp NhanVien này, và bạn hãy xem hệ thống gán, hay ép kiểu ngầm định hai đối tượng này cho nhau như thế nào với các dòng code sau nhé.

public class MainClass {

	public static void main(String[] args) {
		// Khai báo hai đối tượng nhanVien1 và nhanVien2 từ một lớp NhanVien
		NhanVien nhanVien1 = new NhanVien();
		NhanVien nhanVien2 = new NhanVien();
		
		// Set tên cho hai nhân viên
		nhanVien1.setTen("Hùng");
		nhanVien2.setTen("Trang");
		
		// Xuất thông tin hai đối tượng lần 1: bạn có thể đoán ra nội dung xuất đúng không 
		nhanVien1.xuatThongTin(); // In ra "Nhân viên: Hùng"
		nhanVien2.xuatThongTin(); // In ra "Nhân viên: Trang"
		
		// Gán hai đối tượng nhân viên, hệ thống cũng sẽ ép kiểu ngầm định nhanVien1 về nhanVien2
		nhanVien2 = nhanVien1;
		
		// Xuất thông tin hai đối tượng lần 2: kết quả không gì lạ, vì nhanVien2 sẽ mang nội dung giống với nhanVien1 
		nhanVien1.xuatThongTin(); // In ra "Nhân viên: Hùng"
		nhanVien2.xuatThongTin(); // In ra "Nhân viên: Hùng"
		
		// Thay đổi thông tin giá trị ten bên trong nhanVien2
		nhanVien2.setTen("Khải");
		
		// Xuất thông tin hai đối tượng lần 3: lạ chưa, cả hai đối tượng đều bị thay đổi giá trị thuộc tính
		nhanVien1.xuatThongTin(); // In ra "Nhân viên: Khải"
		nhanVien2.xuatThongTin(); // In ra "Nhân viên: Khải"
	}

}

Bạn chú ý vào comment “xuất thông tin hai đối tượng lần 3”. Trước khi xuất thông tin ở lần này cho cả hai đối tượng nhanVien1nhanVien2, thì chỉ có mỗi nhanVien2 là có sự thay đổi giá trị đến thuộc tính ten mà thôi, vậy cớ sự làm sao mà tennhanVien1 cũng bị chung số phận? Vì bạn nên nhớ rằng, trong OOP, khi bạn thực hiện phép gán hai đối tượng cho nhau, như ví dụ là phép gán nhanVien2 = nhanVien1, thì hệ thống sẽ ép kiểu dữ liệu, và cả tham chiếu, của cả hai đối tượng vào nhau, làm cho chúng giờ đây trở thành một. Điều này khác hoàn toàn với việc sử dụng phép gán ở kiểu dữ liệu nguyên thủy, bạn hãy ghi nhớ điều này nhé.

Ép Kiểu Ngầm Định Từ Lớp Con Sang Lớp Cha

Cũng giống như ép kiểu ngầm định dạng mở rộng khả năng lưu trữ ở các kiểu dữ liệu nguyên thủy. Hệ thống sẽ tự thực hiện ép kiểu khi mà có sự chuyển đổi dữ liệu từ kiểu dữ liệu có kích thước nhỏ sang kiểu dữ liệu có kích thước lớn hơn, như từ int sang float chẳng hạn. Thì hệ thống cũng sẽ làm tương tự vậy với OOP, nếu có sự chuyển đổi dữ liệu từ lớp con sang lớp cha.

Giờ giả sử chúng ta sửa lớp con của NhanVien, chính là lớp NhanVienFullTime. Sao cho lớp con này override lại phương thức xuatThongTin() của lớp cha. Code của lớp NhanVienFullTime này như sau.

public class NhanVienFullTime extends NhanVien {
	
	@Override
	public void xuatThongTin() {
		System.out.println("Nhân viên toàn thời gian: " + ten);
	}
}

Rồi. Như vậy chúng ta cùng xem ở phương thức main(), xem việc ép kiểu ngầm định diễn ra như thế nào nhé. Bạn có thể thấy rằng kết cục của phép gán, và ép kiểu giữa các lớp trong OOP đều mang đến kết quả là hai đối tượng sẽ trở thành một (vì cùng một tham chiếu). Sau khi gán, hễ bạn thay đổi giá trị của một lớp, thì lớp cùng trong phép gán kia cũng sẽ bị thay đổi giá trị tương tự.

public class MainClass {

	public static void main(String[] args) {
		// Khai báo hai đối tượng nhanVien1 và nhanVien2 từ một lớp NhanVien
		NhanVien nhanVien = new NhanVien();
		NhanVienFullTime nhanVienFullTime = new NhanVienFullTime();
		
		// Set tên cho hai nhân viên
		nhanVien.setTen("Hùng");
		nhanVienFullTime.setTen("Trang");
		
		// Xuất thông tin hai đối tượng lần 1 
		nhanVien.xuatThongTin(); 			// In ra "Nhân viên: Hùng"
		nhanVienFullTime.xuatThongTin(); 	// In ra "Nhân viên toàn thời gian: Trang"
		
		// Ép kiểu ngầm định từ NhanVienFullTime về NhanVien, hoàn toàn tự động
		nhanVien = nhanVienFullTime;
		
		// Xuất thông tin hai đối tượng lần 2 
		nhanVien.xuatThongTin(); 			// In ra "Nhân viên toàn thời gian: Trang"
		nhanVienFullTime.xuatThongTin(); 	// In ra "Nhân viên toàn thời gian: Trang"
		
		// Thay đổi thông tin giá trị ten bên trong nhanVien2
		nhanVien.setTen("Khải");
		
		// Xuất thông tin hai đối tượng lần 3
		nhanVien.xuatThongTin(); 			// In ra "Nhân viên toàn thời gian: Khải"
		nhanVienFullTime.xuatThongTin(); 	// In ra "Nhân viên toàn thời gian: Khải"
	}

}

Ép Kiểu Tường Minh

Bạn hoàn toàn có thể đoán được khi nào chúng ta cần thiết phải ép kiểu tường minh rồi đúng không nào. Đó là khi mà hệ thống phát hiện thấy bạn đang muốn chuyển dữ liệu từ kiểu dữ liệu có kích thước lớn hơn sang kiểu dữ liệu có kích thước nhỏ hơn. Với OOP thì từ lớp cha sang lớp con.

Chúng ta đến với ví dụ với phép gán ngược lại với ví dụ ngay trên đây.

public class MainClass {

	public static void main(String[] args) {
		// Khai báo hai đối tượng nhanVien1 và nhanVien2 từ một lớp NhanVien
		NhanVien nhanVien = new NhanVien();
		NhanVienFullTime nhanVienFullTime = new NhanVienFullTime();
		
		// Set tên cho hai nhân viên
		nhanVien.setTen("Hùng");
		nhanVienFullTime.setTen("Trang");
		
		// Xuất thông tin hai đối tượng lần 1 
		nhanVien.xuatThongTin(); 			// In ra "Nhân viên: Hùng"
		nhanVienFullTime.xuatThongTin(); 	// In ra "Nhân viên toàn thời gian: Trang"
		
		// Ép kiểu tường minh từ NhanVien sang NhanVienFullTime
		nhanVienFullTime = (NhanVienFullTime) nhanVien;
	}

}

Tại sao code lần này ít thế. Thực ra bạn không nên code nữa, có lỗi xảy ra ở dòng cuối cùng rồi. Dù cho sau khi bạn code, trình biên dịch không hề báo lỗi, nhưng nếu ngay bây giờ bạn thực thi ứng dụng, sẽ nhận được một thông báo lỗi như sau.

Ép kiểu lỗi runtime

Lỗi này có nghĩa là khi bạn ép kiểu tường minh từ nhanVien về nhanVienFullTime, lúc này trình biên dịch vẫn thấy có lý, vì chúng quan hệ cha con mà. Nhưng khi thực thi ở môi trường thực tế, thì lớp nhanVien vốn dĩ không biết đến nhanVienFullTime là gì, nên không thể thực hiện việc ép kiểu được.

Vậy thì ép kiểu tường minh đối với OOP là như thế nào? Thực ra, nếu như chúng ta có áp dụng tính đa hình, tức là ở lúc nào đó nhanVien phải “đặt mình” vào vai trò là một nhanVienFullTime, thì chúng mới hiểu nhau khi “cùng nhau sánh bước trong đường đời” phía trước. Code như thế này sẽ chạy tốt.

public class MainClass {

	public static void main(String[] args) {
		// Khai báo hai đối tượng nhanVien1 và nhanVien2 từ một lớp NhanVien
		NhanVien nhanVien = new NhanVien();
		NhanVienFullTime nhanVienFullTime = new NhanVienFullTime();
		
		// Set tên cho hai nhân viên
		nhanVien.setTen("Hùng");
		nhanVienFullTime.setTen("Trang");
		
		// Xuất thông tin hai đối tượng lần 1 
		nhanVien.xuatThongTin(); 			// In ra "Nhân viên: Hùng"
		nhanVienFullTime.xuatThongTin(); 	// In ra "Nhân viên toàn thời gian: Trang"
		
		// Ép kiểu tường minh từ NhanVien sang NhanVienFullTime, nhưng NhanVien phải có tính đa hình trước đó
		nhanVien = new NhanVienFullTime();
		nhanVienFullTime = (NhanVienFullTime) nhanVien;
		
		// Thay đổi thông tin giá trị ten bên trong nhanVien
		nhanVien.setTen("Khải");
		
		// Xuất thông tin hai đối tượng lần 2
		nhanVien.xuatThongTin(); 			// In ra "Nhân viên toàn thời gian: Khải"
		nhanVienFullTime.xuatThongTin(); 	// In ra "Nhân viên toàn thời gian: Khải"
	}

}

Bài học hôm nay chỉ có như vậy thôi. Cũng tương đối phức tạp đúng không nào. Tuy nhiên bạn đừng nên lo lắng quá, sự xuất hiện của ép kiểu trong OOP là không nhiều. Việc của bạn bây giờ là đọc và hiểu các tình huống ép kiểu của bài hôm nay. Để rồi sau này khi bạn rơi vào tình huống cụ thể, hoặc bạn xem source code ở đâu đó có thể hiện sự ép kiểu trong OOP như thế này, thì bạn sẽ nắm bắt vấn đề ngay thôi.

Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy đánh giá 5 sao nếu thấy thích bài viết, hãy comment bên dưới nếu có thắc mắc, hãy để lại địa chỉ email của bạn để nhận được thông báo mới nhất khi có bài viết mới, và nhớ chia sẻ các bài viết của Yellow Code Books đến nhiều người khác nữa nhé.

Bài Kế Tiếp

Chúng ta sẽ xem đến một trong những đặc tính cốt lõi tiếp theo của OOP, đó là tính Trừu tượng, hay còn được gọi với cái tên Abstraction.

Java Bài 32: Đang Được Viết Tiếp… →
Advertisements
Rating: 5.0/5. From 13 votes.
Please wait...

2 comments

Gửi phản hồi