Ép kiểu trong Java

1. Ép kiểu – Tại sao?

Ép kiểu (casting type) đơn giản chỉ là chuyển đổi giá trị từ kiểu dữ liệu này sang kiểu dữ liệu khác để gán vào một biến hoặc sử dụng để thực hiện các toán tử. Thực chất của ép kiểu là tăng hoặc giảm số lượng bit trong
vùng nhớ máy tính để thay đổi kiểu dữ liệu.

Các toán tử tính toán thường đồi hỏi các kiểu dữ liệu phải tương đồng nhau để nó thực thực hiện tính toán. Vì các toán tử đó thực hiện tính toán trên bit nên nó phải chuyển đổi các kiểu dữ liệu về các số bit giống nhau để tính toán. Do đó ép kiểu thường xảy ra mà đôi khi ta không cảm nhận được vì có những thứ máy tính làm nó tự động. Nó chỉ bắt buộc hay cảnh báo khi có nó không biết nên chọn giải pháp nào để ép.

2. Loại ép kiểu

Như ta đã biết ép kiểu là chuyển từ kiểu dữ liệu này sang kiểu dữ liệu khác. Kiểu dữ liệu khác có thể có kích thức lớn hơn, nhỏ hơn hoặc bằng với kiểu dữ liệu cũ nhưng cơ chế hoạt động của kiểu mới phải khác kiểu cũ. Xét về độ lớn của kiểu dữ liệu. Ta có hai khả năng là nới rộng (widening) và thu hẹp (narrowing)

Nới rộng (chuyển từ nhỏ sang lớn) là quá trình chuyển 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. Tức là số bit sẽ nới rộng ra dựa trên số bit đang có trong kiểu cũ. Vậy thì số bit cũ không ảnh hưởng vậy thì giá trị của nó không bị mất đi. Thường với kiểu nới rộng sẽ làm ít bị cảnh báo.

Thu hẹp (chuyển từ lớn đến nhỏ) là quá trình chuyển từ kiểu dữ liệu có kích thước lớn sang kiểu dữ liệu có kích thước nhỏ hơn. Số bit của kiểu dữ liệu sẽ bị bỏ đi bớt. Điều này sẽ bị mất mát giá trị nếu các bit mà ta bỏ đi có giá trị khác 0. Thường loại này hay bị cảnh báo là mất dữ liệu khi chuyển đổi.

Hình 1 – Ép kiểu “nới rộng” theo chiều mũi tên và “Thu hẹp” theo chiều ngược của mũi tên.

3. Ép kiểu Trong Kiểu Dữ Liệu

Java chia kiểu dữ liệu làm hai nhóm chính: kiểu nguyên thủykiểu tham chiếu. Khi ép kiểu với hai nhóm trên cũng có vài điểm
khác biệt cần lưu ý.

3.1. Với Kiểu Nguyên Thủy

Kiểu dữ liệu nguyên thủy được hình thành trên bit. Do đó ép kiểu đơn thuần chỉ tăng hoặc giảm số bit (riêng đối với kiểu số thực thì hay đổi cơ chế). Khi tăng hoặc giảm số bit sẽ ảnh hưởng đến mất mát thông tin khi bỏ đi bit.

Java có 8 kiểu dữ liệu nguyên thủy nó được tăng dần theo độ lớn của kiểu dữ liệu. Theo chiều tăng dần từ kiểu boolean ð … ð double được xem là loại ép kiểu nới rộng. Và chiều ngược lại là kiểu thu hẹp.

int num = 10;

// widening
long lnum = num;
System.out.println(lnum);       // 10

// narrowing
int numcast = (int)lnum;
System.out.println(numcast);        // 10

3.2. Với Đối Tượng

Ép kiểu trên đối tượng đòi hỏi ta có kiến thức về OOP (Object Orient Program – hướng đối tượng). Để ép được kiểu đối tượng thì đối tượng ép và đối tượng sau khi ép cần co mối qua hệ cha con với nhau.

Tương tự như kiểu dữ liệu nguyên thủy, ép kiểu trên đối tượng cũng có hai hướng đi: nới rộngthu hẹp. Tên của hai hướng đi trên được đặt là do dựa vào độ rộng vùng nhớ của biến đang trỏ đến.

// Class Person
public class Person {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" + "name=" + name + '}';
    }
}

// Class Student
public class Student extends Person {

    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" + "age=" + age + ", super=" + super.toString() + '}';
    }
}

Trong đoạn mã trên ta thấy có hai lớp Person, Student. Trong đó lớp Student là lớp con của Person. Nhìn vào mã nguồn ta thấy lớp Student mặc dù là lớp con nhưng xét về mặc bộ nhớ thì lớn con Student này sẽ rộng hơn lớp Person. Vì lớp Student có thêm thuộc tính age do đó JVM cần cấp thêm bộ nhớ cho nó.  

public class MainCasting {

    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Tien Nguyen");

        // đối tượng Student
        Student student = new Student();
        student.setAge(12);
        student.setName("Su Su");
    }
}

Hãy tập trung vào 2 biến chính trong đoạn mã trên. Biến person đang trỏ đến đối tượng Person là cha của biến student vì nó đang trỏ đến đối tượng Student. Bây giờ ta bắt đầu ép kiểu theo 2 hướng đã đề cập ở trên.

Hướng nới rộng là hướng chuyển biến đang chứa đối tượng cha sang đối tượng con.

// next line.
Person person1 = student;
// person is object create from person class.
// student is object create from student class.

Như ta biết đối tượng con sẽ lớn hơn đối tượng cha. Điều đó chứng minh qua từ khóa extents khi ta kế thừa lớp Person từ lớp Student đúng không? Vậy khi ta gán biến person = student điều này đồng nghĩa là biến person cha cần được nới rộng ra để chưa cho đủ đối tượng con. Do đó đây được xem là kiểu nới rộng khi ép kiểu từ đối tượng con sang đối tượng cha (thực chất là nới rộng vùng nhớ trong biến từ đối tượng cha).

Hướng thu hẹp là hướng ngược lại với nới rộng. Tức là biến một biến đang chứa đối tượng con sang đối tượng cha. Hay nói cách khác là ép kiểu đang ở đối tượng cha sang đối tượng con.

// narrowing
Student student1 = (Student)person;

Mã trên đồng nghĩa là biến student1 đang được khi báo kiểu dữ liệu Student. Sau đó chuyển đối tượng Person từ biến person để gán vào biến Student. Nó bắt biến student1 cần giảm vùng nhớ xuống để chứa đối tượng cha của nó. Điều này trên lý thiết sẽ mất vài thông tin. Chẳng hạn như biến age trong Student (mặc dù chưa có đối tượng nào trong biến student1 hiện tại nhưng vùng nhớ để giành cho nó là đủ cho Student).

Hình 2 – Lỗi sẽ được hiện thị ra khi ta chạy đoạn mã ép kiểu theo hướng thu hẹp.

Ta sẽ dễ dàng nhận được lỗi trên khi ép kiểu thu hẹp. Nhưng chúng ta cũng sẽ có cách để ép kiểu theo hướng này.

Person person2 = new Student();
person2.setName("Tien Nguyen");

Student student = (Student)person2;

System.out.println(student);

Đoạn mã trên ta đang muốn ép kiểu từ Perso sang Student. Nhưng đã nói ở trên đây là hướng ép kiểu thu hẹp. Do đó đầu tiên là ép kiểu nới rộng bằng dòng mã Person person2 = new Student(). Mặc dùng biến cho kiểu Person nhưng vùng nhớ là kiểu Student. Điều này hoàn toàn không có vấn đề gì khi gán Student student = (Student) person2. Kiểu thì có vẻ khác nhau như xét về vùng nhớ thì như nhau.

Hình 2: Minh họa ép kiểu Upcasting và Downcasting. Nguồn Internet.

Đối với ép kiểu dữ liệu trong kiểu dữ liệu nguyên thủy thường người ta gọi hai hướng ép kiểu chính là nới rộngthu hẹp. Nhưng đối với kiểu đối tượng thì ngoài gọi là nới rộng và thu hẹp thỉnh thoảng còn được gọi là downcastingupcasting.

Bạn có thể tham khảo ở https://github.com/tiennguyen2009/java-basic/tree/master/src/com/tiennguyen/chapter_2/casting

4. Tóm lược

Ép kiểu trong Java được chia làm 2 hướng chính nới rộng (widening),thu hẹp (narrowing) đối với kiểu dữ liệu nguyên thủy và ép trên (upcasting), ép xuống (downcasting) đối với đối tượng.

Với hướng ép kiểu nới rộng, ép trên thường được làm một cách tự động và dễ dàng. Ta hoàn toàn không thấy một dòng lỗi nào với hướng này. Nhưng với thu hẹp, ép xuống đôi khi ta sẽ gặp lỗi vì sẽ mất mát dữ liệu.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *