Bài 15: Các Tính Chất Của Java OOPs

Bài 15: Các Tính Chất Của Java OOPs

 Tính đóng gói trong java

Tính đóng gói trong java là kỹ thuật ẩn giấu thông tin không liên quan và hiện thị ra thông liên quan. Mục đích chính của đóng gói trong java là giảm thiểu mức độ phức tạp phát triển phần mềm.
Đóng gói cũng được sử dụng để bảo vệ trạng thái bên trong của một đối tượng. Bởi việc ẩn giấu các biến biểu diễn trạng thái của đối tượng. Việc chỉnh sửa đối tượng được thực hiện, xác nhận thông qua các phương thức. Hơn nữa, việc ẩn giấu các biến thì các lớp sẽ không chia sẻ thông tin với nhau được. Điều này làm giảm số lượng khớp nối có thể có trong một ứng dụng.

1: Lợi ích của đóng gói trong java

Bạn có thể tạo lớp read-only hoặc write-only bằng việc cài đặt phương thức setter hoặc getter. 

Bạn có thể kiểm soát đối với dữ liệu. Giả sử bạn muốn đặt giá trị của id chỉ lớn hơn 100 bạn có thể viết logic bên trong lớp setter.

Ví dụ về đóng gói trong java
Hãy xem ví dụ sau về đóng gói trong java với một lớp chỉ có một trường và các phướng thức setter và getter của nó. 
File: Student.java
public class Student {
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
File: Test.java
class Test {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("Hai");
        System.out.println(s.getName());
    }
}
Kết quả:
Hai

Tính kế thừa trong java

Kế thừa trong java là sự liên quan giữa hai class với nhau, trong đó có class cha (superclass) và class con (subclass). Khi kế thừa class con được hưởng tất cả các phương thức và thuộc tính của class cha. Tuy nhiên, nó chỉ được truy cập các thành viên public và protected của class cha. Nó không được phép truy cập đến thành viên private của class cha. 

Tư tưởng của kế thừa trong java là có thể tạo ra một class mới được xây dựng trên các lớp đang tồn tại. Khi kế thừa từ một lớp đang tồn tại bạn có sử dụng lại các phương thức và thuộc tính của lớp cha, đồng thời có thể khai báo thêm các phương thức và thuộc tính khác.


1: Cú pháp của kế thừa trong java 

Sử dụng từ khóa extends để kế thừa.
class Subclass-name extends Superclass-name {  
    //methods and fields
 }
Ví dụ về kế thừa trong java
class Employee {
    float salary = 1000;
}
 
class Programmer extends Employee {
    int bonus = 150;
}
 
public class InheritanceSample1 {
    public static void main(String args[]) {
        Programmer p = new Programmer();
        System.out.println("Programmer salary is: " + p.salary);
        System.out.println("Bonus of Programmer is: " + p.bonus);
    }
}
Kết quả:
Programmer salary is: 1000.0
Bonus of Programmer is: 150
Trong ví dụ trên class Programmer là con của class Employee, nên nó được phép truy cập đến trường salary của class cha.

2: Các kiểu kế thừa trong java

Có 3 kiểu kế thừa trong java đó là đơn kế thừa, kế thừa nhiều cấp, kế thừa thứ bậc. 

Khi một class được kế thừa từ nhiều class đươc gọi là đa kế thừa. Trong java, đa kế thừa chỉ được support thông qua interface, như đã được nói đến trong bài interface trong java


Ví dụ về đơn kế thừa:

File: TestInheritance1.java
class Animal {
    void eat() {
        System.out.println("eating...");
    }
}
 
class Dog extends Animal {
    void bark() {
        System.out.println("barking...");
    }
}
 
public class TestInheritance1 {
    public static void main(String args[]) {
        Dog d = new Dog();
        d.bark();
        d.eat();
    }
}
Output:
barking...
eating...

Ví dụ về kế thừa nhiều cấp

File: TestInheritance2.java
class Animal {
    void eat() {
        System.out.println("eating...");
    }
}
 
class Dog extends Animal {
    void bark() {
        System.out.println("barking...");
    }
}
 
class BabyDog extends Dog {
    void weep() {
        System.out.println("weeping...");
    }
}
 
public class TestInheritance2 {
    public static void main(String args[]) {
        BabyDog d = new BabyDog();
        d.weep();
        d.bark();
        d.eat();
    }
}
Output:
weeping...
barking...
eating...

Ví dụ về kế thừa thứ bậc:

File: TestInheritance3.java
class Animal {
    void eat() {
        System.out.println("eating...");
    }
}
 
class Dog extends Animal {
    void bark() {
        System.out.println("barking...");
    }
}
 
class Cat extends Animal {
    void meow() {
        System.out.println("meowing...");
    }
}
 
public class TestInheritance3 {
    public static void main(String args[]) {
        Cat c = new Cat();
        c.meow();
        c.eat();
        // c.bark(); // compile error
    }
}

Output:
meowing...
eating...

Câu hỏi: Tại sao đa kế thừa không được support trong java?

Để giảm thiểu sự phức tạp và đơn giản hóa ngôn ngữ, đa kế thừa không được support trong java. 

Hãy suy xét kịch bản sau: Có 3 lớp A, B, C. Trong đó lớp C kế thừa từ các lớp A và B. Nếu các lớp A và B có phương thức giống nhau và bạn gọi nó từ đối tượng của lớp con, như vậy khó có thể xác đinh được việc gọi phương thức của lớp A hay B. 

Vì vậy lỗi khi biên dịch sẽ tốt hơn lỗi khi runtime, java sẽ print ra lỗi "compile time error" nếu bạn cố tình kế thừa 2 class. ?

class A {
    void msg() {
        System.out.println("Hello");
    }
}
 
class B {
    void msg() {
        System.out.println("Welcome");
    }
}
 
public class C extends A,B {
 public static void main(String args[]) {
        C obj = new C();
        obj.msg();
    }
}

Output:
Compile Time Error

Tính đa hình trong java

Đa hình trong java (Polymorphism) là một khái niệm mà chúng ta có thể thực hiện một hành động bằng nhiều cách khác nhau. Polymorphism được cấu tạo từ 2 từ Hy Lạp: poly và morphs. Trong đó "poly" có nghĩa là nhiều và "morphs" có nghĩa là hình thể. Vậy polymorphism có nghĩa là nhiều hình thể. 

Có hai kiểu của đa hình trong java, đó là đa hình lúc biên dịch (compile) và đa hình lúc thực thi (runtime). Chúng ta có thể thực hiện đa hình trong java bằng cách nạp chồng phương thức và ghi đè phương thức. 

Nếu bạn nạp chồng phương thức static trong java, đó là một ví dụ về đa hình lúc biên dịch. Trong bài này, chúng ta tập trung vào đa hình lúc runtime trong java.

1: Đa hình lúc runtime trong java

Đa hình lúc runtime là quá trình gọi phương thức đã được ghi đè trong thời gian thực thi chương trình. Trong quá trình này, một phương thức được ghi đè được gọi thông qua biến tham chiếu của một lớp cha. 
Trước khi tìm hiểu về đa hình tại runtime, chúng ta cùng tìm hiểu về Upcasting.

Upcasting là gì?

Khi biến tham chiếu của lớp cha tham chiếu tới đối tượng của lớp con, thì đó là Upcasting. Ví dụ:
class A{}  
class B extends A{}  

A a=new B();//upcasting

Ví dụ về đa hình lúc runtime trong java

ví dụ 1: Chúng ta tạo hai lớp Bike và Splendar. Lớp Splendar kế thừa lớp Bike và ghi đè phương thức run() của nó. Chúng ta gọi phương thức run bởi biến tham chiếu của lớp cha. Khi nó tham chiếu tới đối tượng của lớp con và phương thức lớp con ghi đè phương thức của lớp cha, phương thức lớp con được gọi lúc runtime. 
Khi việc gọi phương thức được quyết định bởi JVM chứ không phải Compiler, vì thế đó là đa hình lúc runtime.

class Bike {
    void run() {
        System.out.println("running");
    }
}
 
public class Splender extends Bike {
    void run() {
        System.out.println("running safely with 60km");
    }
 
    public static void main(String args[]) {
        Bike b = new Splender();// upcasting
        b.run();
    }
}
Output:
running safely with 60km

Ví dụ 2: Bank:

Giả sử Bank là một lớp cung cấp chức năng xem thông tin tỷ lệ lãi suất. Nhưng mỗi ngân hàng có một lãi xuất khác nhau, ví dụ các ngân hàng SBI, ICICI và AXIS có tỷ lệ lãi suất lần lượt là 8%, 7% và 9%.
class Bank {
    int getRateOfInterest() {
        return 0;
    }
}
class SBI extends Bank {
    int getRateOfInterest() {
        return 8;
    }
}
class ICICI extends Bank {
    int getRateOfInterest() {
        return 7;
    }
}
class AXIS extends Bank {
    int getRateOfInterest() {
        return 9;
    }
}
public class Test2 {
    public static void main(String args[]) {
        Bank b;
        b = new SBI();
        System.out.println("SBI Rate of Interest: " + b.getRateOfInterest());
        b = new ICICI();
        System.out.println("ICICI Rate of Interest: " + b.getRateOfInterest());
        b = new AXIS();
        System.out.println("AXIS Rate of Interest: " + b.getRateOfInterest());
    }
}
Output:
SBI Rate of Interest: 8
ICICI Rate of Interest: 7
AXIS Rate of Interest: 9

2: Đa hình lúc runtime trong Java với thành viên dữ liệu

Phương thức bị ghi đè không là thành viên dữ liệu, vì thế đa hình tại runtime không thể có được bởi thành viên dữ liệu. Trong ví dụ sau đây, cả hai lớp có một thành viên dữ liệu là speedlimit, chúng ta truy cập thành viên dữ liệu bởi biến tham chiếu của lớp cha mà tham chiếu tới đối tượng lớp con. Khi chúng ta truy cập thành viên dữ liệu mà không bị ghi đè, thì nó sẽ luôn luôn truy cập thành viên dữ liệu của lớp cha.
class Bike{  
    int speedlimit=90;  
   }  
   class Honda3 extends Bike{  
    int speedlimit=150;  
     
    public static void main(String args[]){  
     Bike obj=new Honda3();  
     System.out.println(obj.speedlimit);//90  
   }  
Output: 90

3: Đa hình lúc runtime trong Java với kế thừa nhiều tầng

Ví dụ 1:
class Animal {
    void eat() {
        System.out.println("eating");
    }
}
 
class Dog extends Animal {
    void eat() {
        System.out.println("eating fruits");
    }
}
 
class BabyDog extends Dog {
    void eat() {
        System.out.println("drinking milk");
    }
 
    public static void main(String args[]) {
        Animal a1, a2, a3;
        a1 = new Animal();
        a2 = new Dog();
        a3 = new BabyDog();
        a1.eat();
        a2.eat();
        a3.eat();
    }
}
Output:
eating
eating fruits
drinking Milk

Ví dụ 2:
class Animal {
    void eat() {
        System.out.println("animal is eating...");
    }
}
 
class Dog extends Animal {
    void eat() {
        System.out.println("dog is eating...");
    }
}
 
class BabyDog1 extends Dog {
    public static void main(String args[]) {
        Animal a = new BabyDog1();
        a.eat();
    }
}
Output:
Dog is eating
Vì BabyDog1 không ghi đè phương thức eat(), nên phương thức eat() của lớp Dog được gọi.

Nạp chồng phương thức (method overloading)

Nạp chồng phương thức trong java xảy ra nếu một lớp có nhiều phương thức có tên giống nhau nhưng khác nhau về kiểu dữ liệu hoặc số lượng các tham số. 

Giả sử bạn phải thực hiện tính tổng của các số đã cho với bất kỳ số lượng các đối số, nếu bạn viết phương thức a(int, int) cho 2 tham số, b(int, int, int) cho 3 tham số điều này có thể gây khó hiểu cho các lập trình viên khác về ý nghĩa của các phương thức đó vì chúng có tên khác nhau.

1: Lợi ích của nạp chồng phương thức

sử dụng nạp chồng phương thức giúp tăng khả năng đọc hiểu chương trình.

2: Các cách nạp chồng phương thức

Có 2 cách nạp chồng phương thức trong java 
  • Thay đổi số lượng các tham số 
  • Thay đổi kiểu dữ liệu của các tham số

3: Nạp chồng phương thức: thay đổi số lượng các tham số

Trong ví dụ này, chúng ta cần tạo 2 phương thức, phương thức add() đầu tiên thực hiện việc tính tổng của 2 số, phương thức thứ hai thực hiện việc tính tổng của 3 số. Sử dụng phương thức static để gọi hàm thông qua tên class thay vì phải tạo thể hiên của lớp.
class Adder{  
    static int add(int a,int b){return a+b;}  
    static int add(int a,int b,int c){return a+b+c;}  
}  
class TestOverloading1{  
    public static void main(String[] args){  
        System.out.println(Adder.add(11,11));  
        System.out.println(Adder.add(11,11,11));  
    }
}  
Kết quả:
22
33

4: Nạp chồng phương thức: thay đổi kiểu dữ liệu của các tham số

Trong ví dụ này, chúng ta sẽ tạo ra 2 phương thức có kiểu dữ liệu khác nhau. Phương thức add() đầu tiên nhận 2 đổi số có kiểu giá trị là integer, phương thức thứ hai nhận 2 đổi số có kiểu giá trị là double.
class Adder{  
    static int add(int a, int b){return a+b;}  
    static double add(double a, double b){return a+b;}  
}  
class TestOverloading2{  
    public static void main(String[] args){  
        System.out.println(Adder.add(11,11));  
        System.out.println(Adder.add(12.3,12.6));  
    }
}  
Kết quả:
22
24.9

5: Các câu hỏi về nạp chồng phương thức trong java

Câu hỏi 1: Tại sao không thể nạp chồng phương thức bằng cách chỉ thay đổi kiểu trả về của phương thức? 
Trong java, không thể nạp chồng phương thức bằng cách chỉ thay đổi kiểu trả về của phương thức bởi vì không biết phương thức nào sẽ được gọi.
Ví dụ:
class Adder{  
    static int add(int a,int b){return a+b;}  
    static double add(int a,int b){return a+b;}  
}  
class TestOverloading3{  
    public static void main(String[] args){  
        System.out.println(Adder.add(11,11));//ambiguity  
    }
}  

Kết quả:
Compile Time Error: method add(int,int) is already defined in class Adder

Câu hỏi 2: Có thể nạp chồng phương thức main() không?
Có, bạn có thể nạp chồng n phương thức main. Nhưng JVM chỉ gọi phương thức main() có tham số truyền vào là một mảng String. 
Ví dụ:
public class TestOverloading4 {
    public static void main(String[] args) {
        System.out.println("main with String[]");
    }
 
    public static void main(String args) {
        System.out.println("main with String");
    }
 
    public static void main() {
        System.out.println("main without args");
    }
}
Kết quả:
main with String[]

6: Nạp chồng phương thức và sự thay đổi kiểu giá trị

Kiểu dữ liệu của đối số truyền vào được thay đổi sang kiểu dữ liệu khác (tự động ép kiểu) nếu giá trị của đối số đó không phù hợp với kiểu dữ liệu của tham số đã được đinh nghĩa. Để hiểu khái niệm này hãy xem ảnh sau:
Kiểu byte có thể được ép sang các kiểu short, int, long, float hoặc double. Kiểu dữ liệu short có thể được ép sang các kiểu int, long, float hoặc double. Kiểu dữ liệu char có thể được ép sang các kiểu int, long, float or double...
Ví dụ 1:
public class OverloadingCalculation1 {
    void sum(int a, long b) {
     System.out.println(a + b);
    }
   
    void sum(int a, int b, int c) {
     System.out.println(a + b + c);
    }
   
    public static void main(String args[]) {
     OverloadingCalculation1 obj = new OverloadingCalculation1();
     // kiểu integer tham số 2 sẽ được thay đổi thành kiểu long
     obj.sum(20, 20);  
     obj.sum(20, 20, 20);
    }
   }
Kết quả:
40
60
Ví dụ 2: nếu không có kiểu đối số nào phù hợp, chuyển đổi kiểu sẽ không được thực hiện.
public class OverloadingCalculation2 {
    void sum(int a, int b) {
     System.out.println("int arg method invoked");
    }
   
    void sum(long a, long b) {
     System.out.println("long arg method invoked");
    }
   
    public static void main(String args[]) {
     OverloadingCalculation2 obj = new OverloadingCalculation2();
     // phương thứ sum() có đối số int sẽ được gọi
     obj.sum(20, 20);
    }
   }
Kết quả:
int arg method invoked

Ví dụ 3: không có kiểu đối số nào phụ hợp trong phương thức và mỗi phương thức thay đổi số đối số tương tự nhau.  sẽ không xác định được phương thức nào được gọi.
public class OverloadingCalculation3 {
    void sum(int a, long b) {
     System.out.println("a method invoked");
    }
   
    void sum(long a, int b) {
     System.out.println("b method invoked");
    }
   
    public static void main(String args[]) {
     OverloadingCalculation3 obj = new OverloadingCalculation3();
     // không xác định được phương thức nào được gọi
     obj.sum(20, 20);
    }
   }
Kết quả: Compile Time Error

Ghi đè phương thức trong java

Ghi đè phương thức trong java xảy ra nếu lớp con có phương thức giống lớp cha. Nói cách khác, nếu lớp con cung cấp sự cài đặt cụ thể cho phương thức đã được cung cấp bởi một lớp cha của nó được gọi là ghi đè phương thức (method overriding) trong java.

1: Sử dụng ghi đè phương thức trong java

  • Ghi đè phương thức được sử dụng để cung cấp cài đặt đặc biệt của một phương thức mà đã được định nghĩa ở lớp cha. 
  • Ghi đè phương thức được sử dụng cho đa hình runtime.

2: Các nguyên tắc ghi đè phương thức trong java

  • Phương thức phải có tên giống với lớp cha. 
  • Phương thức phải có tham số giống với lớp cha. 
  • Lớp con và lớp cha có mối quan hệ kế thừa.

Ví dụ ghi đè phương thức trong java

Trong ví dụ này, chúng ta định nghĩa phương thức run() trong lớp con giống như đã được định nghĩa trong lớp cha, nhưng được cài đặt rõ ràng trong lớp con. Tên và tham số của phương thức là giống nhau, 2 lớp cha và con có quan hệ kế thừa.
class Vehicle {
    void run() {
        System.out.println("Vehicle is running");
    }
}
 
public class Bike2 extends Vehicle {
    void run() {
        System.out.println("Bike is running safely");
    }
 
    public static void main(String args[]) {
        Bike2 obj = new Bike2();
        obj.run();
    }
}
Kết quả:
Bike is running safely

Ví dụ thực tế về ghi đè phương thức trong java

Giả sử Bank là một lớp cung cấp chức năng xem thông tin tỷ lệ lãi suất. Nhưng mỗi ngân hàng có một lãi xuất khác nhau, ví dụ các ngân hàng SBI, ICICI và AXIS có tỷ lệ lãi suất lần lượt là 8%, 7% và 9%.

Dưới đây là cài đặt cho ví dụ trên: ?
class Bank {
    int getRateOfInterest() {
        return 0;
    }
}
 
class SBI extends Bank {
    int getRateOfInterest() {
        return 8;
    }
}
 
class ICICI extends Bank {
    int getRateOfInterest() {
        return 7;
    }
}
 
class AXIS extends Bank {
    int getRateOfInterest() {
        return 9;
    }
}
 
public class Test2 {
    public static void main(String args[]) {
        SBI s = new SBI();
        ICICI i = new ICICI();
        AXIS a = new AXIS();
        System.out.println("SBI Rate of Interest: " + s.getRateOfInterest());
        System.out.println("ICICI Rate of Interest: " + i.getRateOfInterest());
        System.out.println("AXIS Rate of Interest: " + a.getRateOfInterest());
    }
}
Kết quả:
SBI Rate of Interest: 8
ICICI Rate of Interest: 7
AXIS Rate of Interest: 9

Câu hỏi về ghi đè phương thức trong java 

Có ghi đè được phương thức static không? 

Không, phương thức static không thể ghi đè được, bằng chứng là đa hình runtime, vấn đề này sẽ được học trong bài sau. 

Tại sao không ghi đè được phương thức static? 

Vì phương thức static được ràng buộc với class còn phương thức instance được ràng buộc với đối tượng. Static thuộc về vùng nhớ class còn instance thuộc về vùng nhớ heap. 

Có ghi đè phương thức main được không? 

Không, vì main là phương thức static.







Đăng nhận xét

0 Nhận xét

myadcash