Như đã trình bày ở Tổ chức học Core Java
Mình xin bắt đầu với topic đầu tiên: Object Orientation(OO) trong java.
Trước khi bắt đầu mình có 1 vài chú ý nho nhỏ :
- Rất nhiều từ mình sẽ không dịch sang tiếng việt mà giữ nguyên tiếng anh VD: Object, Class, Encapsulation…
Vì sao lại thế chắc các bạn cũng hiểu. - Không thảo luận những vấn đề không liên quan đến topic: VD: đang nói về Object Orientation lại có bạn hỏi về Exception, Thread…
- Những đóng góp ý kiến, các câu hỏi Why, When luôn luôn được chào đón và giúp các bạn hiểu sâu hơn về topic, đừng chỉ dừng lại ở What, How (nó là cái gì, dùng ntn). VD: Thảo luận về OO, nên đặt ra các câu hỏi như: tại sao nên dùng Encapsulation, Inheritance? Khi nào thì dùng nó, khi nào không nên dùng?
OK, Let’s start
Java là một ngôn ngữ Object Orientation, do đó các bạn muốn code java, nhất thiết phải tuân thủ đúng kỹ thuật của một ngôn ngữ OO. Nó bao gồm 4 thành phần:
- Encapsulation(đóng gói)
- Inheritance(Kế thừa)
- Polymorphism(Đa hình)
- Abstraction(Trừu tượng)
Ngoài ra trong topic mình sẽ đề cập đến một số vấn đề liên quan: Overriding, Overloading, Reference variable, Interface, Static, Contructor.
- Encapsulation
Encapsulation là gì? hãy lấy 1 ví dụ:
class BadOO {
public int size;
public int weight;
}
class ExploitBadOO {
public static void main (String [] args) {
BadOO b = new BadOO();
b.size = -5; // Legal but bad!!
}
}
Bạn là một thành viên trong nhóm code một dự án. Bạn code 1 class tên BadOO, một người khác trong team sử dụng class của bạn trong class của họ ExploitBadOO. Bạn sẽ không thể kiểm soát được nếu như cho họ toàn quyền quyết định dùng class của bạn. Trong VD trên, class bạn tạo ra không muốn size < 0, nhưng bạn của bạn rất dẽ dàng làm cho nó < 0:
BadOO b = new BadOO();
b.size = -5; // Legal but bad!!
Câu hỏi đặt ra ở đây là gì, làm thế nào bạn có thể kiểm soát cái mà bạn tạo ra?
Vâng, đó là câu trả lời cho Encapsulation:
Mục đích chính của Encapsulation chính là kiểm soát tất cả những khả năng gây lỗi khi người khác sử dụng code của bạn
Ngoài ra các bạn search sẽ thấy đcmột vài lợi ích khác, nhưng theo mình nó chỉ từ cái mày mà ra.
Vậy sử dụng Encapsulation như thế nào?
- Tất cả các biến trong Class phải được bảo vệ (sử dụng access modifier: protected, private).
- Tạo ra các method để truy suất các biến đó: như vậy muốn sử dụng biến phải thông qua các method này.
- Vớii các method, java khuyến cáo sử dụng theo chuẩn JavaBean chính là các hàm get, set.
Ví dụ trên sau khi áp dụng Encapsulation:
class BadOO {
private int size;
private int weight;
public int getSize() {
return size;
}
public void setSize(int size) {
if (size >= 0) {
this.size = size;
} else {
throw new IllegalArgumentException("Size can not be negative value!");
}
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
class ExploitBadOO {
public static void main (String [] args) {
BadOO b = new BadOO();
b.setSize(-5); // will throw exception
}
}
- Inheritance
Inheritance - Tính Kế thừa có ở mọi nơi trong Java, dù là những chương trình java nhỏ nhất đến các project phức tạp.
Khi bạn tạo ra 1 class, mặc địch nó được kế thừa từ class Object, đó là tại sao class bạn tạo ra luôn có sẵn một số method vd như toString(), equals(), hashcode() …
Tại sao java lại làm như vậy?
Ví dụ hàm equals(), những người tạo ra java chắc chắn rằng method này là vô cùng phổ biến cho những người viết java để so sánh giá trị 2 thực thể (Instances). Giả sử class Object không có hàm equals() thì sao? Khi đó hàng trăm, hàng triệu các lập trình viên sẽ phải thiết kế hàm equals riêng của họ.
Đó là một trong 2 nguyên nhân chính tạo ra Inheritance trong java:
- Code reuse (Tái sử dụng code)
- Để sử dụng cho polymorphism (đa hình)
a. Code Reuse
VD:
class Angle {
public void draw() {
System.out.println("drawing Angle");
}
// more code
}
class Rectangle extends Angle {
public void paint() {
System.out.println("painting Rectangle");
}
// more code
}
class Test {
public static void main (String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.draw();
rectangle.paint();
}
}
Outputs:
drawing Angle
painting Rectangle
Class Rectangle kế thừa Class Angle, do đó nó có thể sử dụng đc Method draw() của Angle.
Tính code reuse ở đây thể hiện rằng method draw() có thể được sử dụng ở bất cứ class nào miễn là nó kế thừa từ Angle.
b. Sử dụng trong Polymorphism
Chi tiết về Polymorphism mình sẽ thảo luân sau ở phần dưới.
Cũng từ ví dụ trên, mình thêm 1 class Triangle:
class Angle {
public void draw() {
System.out.println("drawing Angle");
}
// more code
}
class Rectangle extends Angle {
public void paint() {
System.out.println("painting Rectangle");
}
// more code
}
class Triangle extends Angle {
public void doXXX() {
System.out.println("do XXX");
}
// more code
}
Và 1 class test:
class Test {
public static void main (String[] args) {
Rectangle rectangle = new Rectangle();
Triangle triangle = new Triangle();
drawAngle(rectangle);
drawAngle(triangle);
}
public static void drawAngle(Angle angle) {
angle.draw();
}
}
Output:
drawing Angle
drawing Angle
Các ban thấy, cả 2 Ractangle và Triangle đều có thể là param truyền vào method drawAngle() được định nghĩa là kiểu Angle.
Ý nghĩa: method drawAngle(Angle angle) không quan tâm chính xác kiểu của parameter(đối số) truyền vào là gì, miễn là nó là một Angle, bởi vì nó chỉ cần sử dụng chức năng draw() của Angle.
1 số topic liên quan đến Inheritance:
3 Polymorphism
Tính polymorphism (đa hình) là gì? Hiểu đơn giản nó nghĩa là “nhiều hình thức” thể hiện.
Cùng một hành động có thể có ý nghĩa khác nhau với những đối tượng khác nhau.
Ví dụ: hành động eat(ăn) là khác nhau đối với cat(con mèo), hay dog(con chó).
Polymorphism trong java cũng có thể hiểu như vậy, cụ thể hơn, polymorphism trong java bao gồm:
- Polymorphism với class (chính là kết hợp với inheritance ở phần trên).
- Polymorphism với method (chính là khái niệm Method Override)
A. Polymorphism với class:
Đây chính là phần kết hợp với Inheritance mà mình đã nói trước đó.
Mình cho thêm 1 ví dụ khác:
class Developer {
public void code() {
System.out.println("Developer: Coding...");
}
}
class JavaDeveloper extends Developer {
public void code() {
System.out.println("JavaDeveloper: Coding...");
}
}
class PHPDeveloper extends Developer {
public void code() {
System.out.println("PHPDeveloper: Coding...");
}
}
public class TestClass {
public static void main(String[] args) {
Developer dev = new Developer();
Developer javaDev = new JavaDeveloper();
Developer phpDev = new PHPDeveloper();
writeCode(dev);
writeCode(javaDev);
writeCode(phpDev);
}
public static void writeCode(Developer dev) {
dev.code();
}
}
Output:
Developer: Coding...
JavaDeveloper: Coding...
PHPDeveloper: Coding...
Polymorphism ở đây là gì? đối số kiểu Developer truyền vào method writeCode() có thể có nhiều hình thức, lúc thì là JavaDeveloper, lúc thì là PHPDeveloper, thậm chí cũng có thể là Developer.
Bây giờ, mình muốn phân tích sâu hơn về đoạn code này, các bạn sẽ hiểu Java làm gì với chúng:
public static void main(String[] args) {
Developer dev = new Developer();
Developer javaDev = new JavaDeveloper();
Developer phpDev = new PHPDeveloper();
writeCode(dev);
writeCode(javaDev);
writeCode(phpDev);
}
- Trước hết là khái niệm Reference Variable.
Reference Variable: Biến Reference là gì?
Biến Reference là biến lưu trữ giá trị địa chỉ ô nhớ của Object mà nó trỏ đến.
Một biến Reference được định nghĩa 1 lần duy nhất, kiểu của nó(type) là cố định và không bao giờ thay đổi.
Ví dụ:
Developer dev;
Tạo ra một biến Reference tên là dev, kiểu của nó là Developer.
Khi khai báo như trên, giá trị của biến dev chưa được gán giá trị nào cả.
Nếu ta thêm 1 đoạn code:
dev = new Developer();
Bản chất của dòng lệnh này bao gồm 2 phần:
Phần 1: Tạo ra 1 Instance của Developer và các thuộc tính của nó thông qua hàm Constructor và lưu trữ Instance vào bộ nhớ.
Phần 2: Địa chỉ của ô nhớ lưu trữ Instance này được gán cho biến Reference(dev) bằng lệnh gán (dev =); Ta gọi "dev trỏ tới Developer Object"
Ta có thể thay thế 2 lệnh trên bằng 1 lệnh duy nhất: Developer javaDev = new JavaDeveloper();, bản chất chúng không có gì khác. Mình chỉ tách chúng ra để cho các bạn dễ hiểu hơn.
Tương tự như vậy:
Developer javaDev = new JavaDeveloper();
Tạo ra 1 Biến Reference là javaDev, 1 Instance của JavaDeveloper, và javaDev trỏ đến JavaDeveloper Object;
Developer phpDev = new PHPDeveloper();
Tạo ra 1 Biến Reference là phpDev, 1 Instance của PHPDeveloper, và phpDev trỏ đến PHPDeveloper Object;
Bây h ta phân tích tiếp hàm:
public static void writeCode(Developer dev) {
dev.code();
}
Đối số truyền vào là 1 biến Reference có kiểu là Developer, đó là tại sao cả 3 biến dev, javDev, phpDev đều có thể truyền được vào hàm vì chúng đều có kiểu Developer.
Vậy lệnh dev.code(); làm gì?
Lệnh này sẽ chạy method code() của Object mà biến Reference trỏ đến, như vậy với mỗi biến truyền vào, Object của nó là khác nhau và nó sẽ chạy khác nhau. Đó là vì sao in ra 3 dòng:
Developer: Coding...
JavaDeveloper: Coding...
PHPDeveloper: Coding...
updated 11h 3/8…
Giả sử ta thêm 1 biến Reference nữa và gán giá trị của nó cho javaDev:
code:
Developer javaDev = new JavaDeveloper();
Developer newDev = javaDev;
Java sẽ làm gì với lệnh Developer newDev = javaDev; Nếu các bạn đã hiểu rõ biến Reference là gì chắc cũng đoán ra.
Câu lệnh trên sẽ tạo ra 1 biến Reference mới newDev, và giá trị của nó được gán bằng giá trị của javaDev.
Như vậy, chúng ta có 2 biến Reference cùng trỏ đến 1 JavaDeveloper Object.
Nếu ta so sánh bằng biểu thức (javaDev == newDev) sẽ trả về true. Giờ chắc các bạn hiểu tại sao == dùng để so sánh Reference .
Ta lại thêm 1 đoạn code để gán lại giá trị của javaDev:
Developer javaDev = new JavaDeveloper();
Developer newDev = javaDev;
javaDev = new JavaDeveloper();
Sau câu lệnh thứ 3: Ta có thêm 1 JavaDeveloper Object mới, và biến javaDev giờ sẽ trỏ đến Object mới này.
Như vậy ta đang có javaDev trỏ đến Object mới, newDev vẫn trỏ đến Object cũ, giữa javaDev và newDev không hề có mối quan hệ nào nào cả.
Biểu thức (javaDev == newDev) lúc này sẽ trả về false vì 2 biến Reference giờ trỏ đến 2 Object khác nhau.
Còn 2 phần nữa khá hay về Reference Copy, và Binding variable, method mình sẽ viết sau
1 Số topic liên quan đến Polymorphism:
http://daynhauhoc.com/t/java-question-reference-variable-type
Update 2h 5/8
B. Polymorphism với method
Tính Polymorphism với method chính là khái niệm Overriding Method trong java.
Quay lại ví dụ trên, method code() đã được JavaDeveloper và PHPDeveloper override lại.
Khi đó lời gọi hàm dev.code() sẽ phụ thuộc vào Object mà biến Reference trỏ đến mà chạy hàm thích hợp.
Chắc khái niệm Override ai cũng quen thuộc rồi nên mình sẽ không nói nhiều ở đây. Thay vào đó mình sẽ nói đến vấn đề Reference Copy khá hay.
Reference Copy
Các bạn xem đoạn code:
class Developer {
private String name;
public Developer(String name) {
this.name = name;
}
public void showMyName() {
System.out.println("My name: " + this.name);
}
public void setName(String name) {
this.name = name;
}
}
public class TestClass {
public static void main(String[] args) {
Developer dev = new Developer("John");
dev.showMyName(); // show name John
changeDev(dev);
dev.showMyName(); // what's name?
}
public static void changeDev(Developer dev) {
dev = new Developer("Peter");
}
}
Theo mọi người output sẽ là gì?
CÓ người sẽ đoán là:
My name: John
My name: Peter
Không, rất tiệc kết quả là:
My name: John
My name: John
Tại sao lại như vậy? đây chính là khái niêm Reference copy.
Tất cá các Reference của bất cứ 1 Object nào truyền vào 1 method sẽ tạo ra 1 bản copy của Reference đó. Method sẽ sử dụng Reference copy chứ không dùng Reference gốc.
Ta sẽ phân tích lại đoạn code:
public static void changeDev(Developer dev) {
dev = new Developer("Peter");
}
- Biến dev được truyền vào ở lời gọi hàm changeDev(dev); là 1 biến Reference, nó đang trỏ đến một Developer Object với name = “John”.
- Sau lời gọi method changeDev(dev), tạo ra 1 biến Reference mới cùng trỏ đến Developer Object trên. Sau đó sẽ truyền vào method.
- Lệnh dev = new Developer(“Peter”); trong method sẽ tạo ra 1 Developer Object mới với name = “Peter”, biến Reference copy sẽ trỏ đến Object mới này.
- Kết thúc hàm, biến Reference copy sẽ được giải phóng (java có cơ chế GC sẽ tự giải phóng những biến này)
- Biến Reference dev vẫn đang trỏ đến Developer Object cũ và in ra với tên là “John”,
Bây giờ, ta lại sửa 1 chút hàm changeDev thành:
public static void changeDev(Developer dev) {
dev.setName("Peter");
}
Điều gì sẽ xảy ra?
Output:
My name: John
My name: Peter
Lúc này biến Reference copy đang trỏ vào Developer Object với name = “John”.
lời gọi hàm setName(“Peter”); sẽ gọi đến hàm của Object mà biến Reference copy trỏ đến, tức là set lại giá trị name của Developer Object = “Peter”.
Ra khỏi hàm, biến Reference copy cũng bị giải phóng, nhưng Object nó trỏ đến đã bị thay đổi. Đó là tại sao in ra “My name: Peter”
…updating…