Java Bài 26: Từ Khóa final Trong Lập Trình Hướng Đối Tượng

Posted by

Nội dung bài viết

Chào mừng các bạn đã đến với bài học Java thứ 26, bài học về từ khóa final trong OOP. Đây là bài viết trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.

Chắc bạn còn nhớ bài học về hằng số, khi đó bạn đã biết, để khai báo một hằng, bạn phải dùng từ khóa final. Sang đến các bài học về OOP, hằng số vẫn được dùng với ý nghĩa nguyên vẹn nếu như bạn khai báo một thuộc tính của lớp với từ khóa final này.

Vậy thì, ngoài ý nghĩa là một hằng số ra, final còn làm được gì trong các mối quan hệ OOP này. Mời bạn cùng xem tiếp bài học hôm nay để hiểu rõ hơn về final nhé.

Biến final & Thuộc Tính final

Như mình có nói, từ khóa final khi dùng cho biến trong một phương thức, cũng có ý nghĩa tương tự như khi dùng từ khóa này với thuộc tính của lớp. Nó đều mang giá trị là một hằng số không thể thay đổi được.

Dưới đây là một ví dụ của việc khai báo final cho một biến, kiến thức cổ xưa này chắc bạn cũng đã nắm rất kỹ rồi.

protected float tinhChuVi() {
    final float PI = 3.14f;
	return 2 * PI * banKinh;
}

Còn về việc khai báo final cho một thuộc tính, thì cũng cổ xưa không kém, bạn đã từng làm quen rất nhiều khi khai báo PI trong lớp HinhTron ở các bài học trước. Tuy nhiên, bạn nên nhớ rằng, các khai báo khả năng truy cập vào các thuộc tính final hay không final là hoàn toàn tương tự nhau nhé.

class HinhTron {
	
	private final float PI = 3.14f;
	protected float banKinh;
	
	// Constructor
	public HinhTron(float banKinh) {
		this.banKinh = banKinh;
	}
 
    protected float tinhChuVi() {
        return 2 * PI * banKinh;
    }
 
    protected float tinhDienTich() {
        return PI * banKinh * banKinh;
    }
}

Và như mình cũng có khuyên bạn khi nói về hằng số, rằng bạn nên viết hoa hết các ký tự biến hay thuộc tính với khai báo final, nó giúp code của chúng ta rõ ràng và mạch lạc hơn.

Thuộc Tính final Trống

Phần này mình muốn nói thêm về final khi dùng cho biến hay thuộc tính. Đó là khi bạn khai báo chúng là final, bạn không cần thiết phải chỉ định ngay giá trị cho thuộc tính hay biến đó, mà có thể để trống, để sau này khi có giá trị cụ thể, thì bạn gán vào các thuộc tính hay biến final đó sau, và chỉ gán một lần duy nhất thôi nhé. Điều này giúp cho final tưởng như rất cứng nhắc này lại trở nên linh động hơn một chút.

Ví dụ sau mô phỏng cho việc set giá trị cho biến final PI sau khi chúng ta tìm thấy một giá trị PI chính xác hơn bằng phương thức.

protected final float tinhChuVi() {
	final float PI;
	PI = tinhPI();
	return 2 * PI * banKinh;
}

Còn ví dụ sau mô phỏng việc set giá trị cho thuộc tính final PI sau khi tìm thấy giá trị PI chính thức, nhưng hơi khác với ví dụ về biến trên kia một chút, rằng với thuộc tính final, thì bạn chỉ được phép gán giá trị sau cho final ở constructor mà thôi.

class HinhTron {
	
	private final float PI;
	protected float banKinh;
	
	// Constructor
	public HinhTron(float banKinh) {
		PI = (float) tinhPi();
		this.banKinh = banKinh;
	}
 
    protected float tinhChuVi() {
        return 2 * PI * banKinh;
    }
 
    protected float tinhDienTich() {
        return PI * banKinh * banKinh;
    }
    
    // Bạn đừng quan tâm quá đến code của phương thức này
    private double tinhPi() {
    	return Math.PI;
    }
}

Phương Thức final

Cũng dễ dàng suy luận thôi. Nếu như với thuộc tính, khi bị chỉ định là final, thì có nghĩa là “chốt” giá trị, không thể thay đổi được nữa. Thì với phương thức, khi bạn chỉ định final cho nó, thì xem như phương thức đó không thể override lại được, cũng tương đương với việc bạn không thay đổi được phương thức đó từ lớp con.

Quay lại lop HinhTron, giả sử các phương thức tinhChuVi()tinhDienTich() đều được khai báo là final.

class HinhTron {
	
	private final float PI = 3.14f;
	protected float banKinh;
	
	// Constructor
	public HinhTron(float banKinh) {
		this.banKinh = banKinh;
	}
 
    protected final float tinhChuVi() {
        return 2 * PI * banKinh;
    }
 
    protected final float tinhDienTich() {
        return PI * banKinh * banKinh;
    }
}

Vậy thì ở lớp HinhTru kế thừa từ HinhTron, bạn có thể thấy rằng việc HinhTru dùng lại các phương thức tinhChuVi()tinhDienTich() từ HinhTron là hoàn toàn bình thường.

public class HinhTru extends HinhTron {

	public float chieuCao;
	
	// Constructor
	public HinhTru(float banKinh, float chieuCao) {
		super(banKinh);
		this.chieuCao = chieuCao;
	}
	
	public float tinhTheTich() {
		return tinhDienTich() * chieuCao;
    }
}

Nhưng như mình có nói, lỗi chỉ thực sự xảy ra khi bạn override lại một phương thức final nào đó mà thôi. Ví dụ như nếu bạn xây dựng thêm em này ở HinhTru thì hệ thống mới báo lỗi.

@Override
protected float tinhChuVi() {
	return super.tinhChuVi();
}

Và với việc thiết lập một phương thức là final này, như bạn đã biết, các lập trình viên sẽ tránh không cho các lớp khác kế thừa từ phương thức nào đó của một lớp. Việc làm này vẫn giúp chia sẻ phương thức đó cho các lớp con kế thừa (miễn đừng khai báo private), nhưng sẽ không cho phép bất cứ sự sửa chữa nào, đảm bảo sự nguyên vẹn ban đầu của phương thức. Bạn đã thấy sự vi diệu của OOP chưa nào.

Vâng, bạn có thể chỉ định final cho bất cứ phương thức nào. Nhưng, bạn nên lưu ý rằng, bạn không thể chỉ định final cho một constructor nhé, vì constructor có bao giờ bị override đâu nào.

Lớp final

Tác dụng của final đến với lớp tương tự như tác dụng của em ấy đến với phương thức. Đó là bạn sẽ không thể kế thừa từ bất cứ lớp nào được khai báo là final.

Giả sử lớp HinhTron có khai báo final cho lớp như thế này, thì lớp HinhTru kế thừa từ HinhTron mà chúng ta đã biết sẽ báo lỗi “nguyên con”.

final class HinhTron {
	
	// ...
}

Nếu bạn xuất bản một lớp, và chỉ cho người khác sử dụng chúng mà thôi, không cho ai có quyền kế thừa để override bất kỳ phương thức nào, thì cứ đặt lớp đó là final nhé.

Chúng ta kết thúc bài học final nhẹ nhàng ở đây. Tuy nhiên bài học khá hữu dụng trong việc vận dụng một từ khóa final để mang lại một số ý nghĩa về bảo vệ thông tin của một đối tượng. Nó sẽ hữu ích khi bạn muốn xây dựng các gói thư viện Java và xuất bản nó cho nhiều người dùng. Khi đó, việc cho phép một lớp khác có quyền kế thừa, hay override một phương thức nào đó của lớp hay không, là quyền ở bạn.

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ẽ nói thêm về việc gói ghém, hay bảo vệ dữ liệu trong các lớp của Java qua các phương thức gettersetter nhé.

Advertisements
Rating: 5.0. From 8 votes.
Please wait...

One comment

Gửi phản hồi