JAVA - Lớp và các đối tượng

Posted by NGTHFONG - 阮・青・風 On Wednesday, June 2, 2010 0 コメント
Đây là bài giảng về các lớp. Trong lập trình hướng đối tượng, lớp là khái niệm cơ bản, bởi vì lớp miêu tả tính chất của các đối tượng, lớp là "kiểu mẫu" để tạo ra các đối tượng. Trong bài này chúng ta sẽ học cách xây dựng lớp và ứng dụng đối tượng của lớp. Bên cạnh đó, bài giảng sẽ đề cập đến một số vấn đề trong thực tế lập trình bằng ngôn ngữ Java. Sau khi học xong bài này, chúng ta có thể tự viết được chương trình trong Java. 


1. Lớp để làm gì?

Trong lập trình hướng đối tượng chúng ta sử dụng các đối tượng.
Các đối tượng được đặc trưng bởi:

    •    các thuộc tính 
    •    các thao tác mà có thể được tiến hành trên đối tượng (nói cách khác là các thông điệp được chuyển đến hay các phương pháp mà có thể được gọi)

Các đối tượng trong một chương trình phản ánh các đối tượng trong thế giới thực, chúng có thể là cụ thể hay trừu tượng.

Ví dụ, trong một chương trình mô phỏng giao thông chúng ta nên miêu tả xe hơi. Một chiếc xe hơi (với tư cách là đối tượng trong chương trình) có một số các thuộc tính sau:

    •    trọng lượng
    •    chiều cao
    •    tốc độ

Nó có thể đáp ứng được một số yêu cầu như:

    •    khởi động
    •    dừng lại
    •    tăng tốc độ
    •    quẹo trái, vân vân

Hai chiếc xe có thể được thể hiện bằng hai đối tượng trong chương trình, mỗi đối tượng được thể hiện bằng giá trị của các thuộc tính của nó.

Làm thế nào chúng ta biết được các đối tượng xe hơi có những thuộc tính gì? Làm thế nào chúng ta biết các đối tượng đó hiểu những thông điệp nào?
Tất cả những điều này được cụ thể trong định nghĩa về lớp xe hơi. Chương trình của chúng ta phải cung cấp những thông tin đó hoặc nhập những thông tin đó từ một nguồn nào đó.


Lớp là bản mô tả những thuộc tính và thao tác không đổi của một nhóm các đối tượng giống nhau

Vì vậy chúng ta có thể viết một cách tượng trưng:


Lớp Xe hơi

có các thuộc tính về:

       trọng lượng
       chiều cao
       vận tốc hiện tại

các yêu cầu (các thao tác, thông điệp):

       khởi động
       dừng lại
       tăng tốc
       rẽ trái


Nhưng từ đâu mà có các đối tương - xe hơi trong chương trình của chúng ta? Chúng ta phải tạo ra các đối tượng đó.


Đối tượng được tạo ra bởi biểu thức new


Biểu thức new viết như sau:

    new TênLớp(các tham số)

  ở đây:  các tham số (các biểu thức, giữa chúng có dấu phẩy) xác định các giá trị ban đầu của tất cả hoặc một vài thuộc tính.

Làm sao mà biết được có những tham số nào, và phải đưa chúng ra theo thứ tự nào? Mỗi lớp có định nghĩa các thao tác đặc biệt, gán giá trị ban đầu cho đối tượng, các thao tác đó có thể được dùng để tạo ra các đối tương đó.
 
Giả sử, trong lớp Xe_hơi có định nghĩa một thao tác gán giá trị cho các thuộc tính bằng tham số theo thứ tư: trọng lương, chiều cao, vận tốc hiện tại.

Chúng ta có thể tạo các đối tương như sau:: 

carA = new  Car(500, 1.5, 0);   
carB = new Car(1000, 2.2, 60);

Hai chiếc xe có thể được thể hiện bằng hai đối tượng trong chương trình, mỗi đối tượng được thể hiện bằng giá trị của các thuộc tính của nó.


Xe hơi A (biến carA trong chương trình)Xe hơi B (biến carB trong chương trình)
trọng lương = 500
chiều cao = 1.5
vận tốc hiện tại = 0
trọng lương = 1000
chiều cao = 2.2
vận tốc hiện tại = 60

Chúng ta có thể thực hiện các thao tác trên dối tượng, gửi các thông điệp đến chúng.


Các thông điệp được gửi đến đối tượng bằng cách sử dụng toán tử dấu chấm.

Ví dụ:

carA.khởi_động();

carB.dừng_lại();

Một ví dụ khác, cặp số nguyên.


Lớp Pair

    thuộc tính:

        số_thứ_nhất
        số thứ_hai

    thao_tác:
        thao_tác_ban_đầu  // gán hai số cụ thể cho cặp
        add                    // cộng hai cặp
        show                  // in cặp (hiển thị các thành phần ra bảng nhắc câu lệnh)



Chúng ta có thể tạo vài đối tương lớp Pair và thực hiện vài thao tác như sau:






Pair a = new Pair(1,2); 
Pair b = new Pair(3,4); 
Pair c = a.add(b);     
c.show();              

Như vậy:


Có thể thấy định nghĩa của lớp quy định:

    •    các thuộc tính của các đối tượng của lớp
    •    các thông điệp có thể được gửi tới các đối tượng
    •    các thao tác đặc biệt giúp tạo ra và khởi động các đối tượng




Trong hầu hết các ngôn ngữ hướng đối tượng (bao gồm cả Java):

    •    các thuộc tính chung được gọi là fields (miền)
    •    các thông điệp (yêu cầu) được gọi là method (phương pháp, phương thức)
    •    các thao tác đặc biệt sử dụng để khởi động các đối tượng được gọi là constructor (tạm dịch là phương thức khởi tạo)

Do vậy một định nghĩa của một lớp cung cấp các định nghĩa về:

    •    miền
    •    phương thức
    •    constructor

Lớp là một khuôn mẫu miêu tả sự tạo ra các đối tượng (constructor), các thuộc tính (field) và cách thức giao tiếp với các đối tượng (method).


Trong Java từ khóa class được sử dụng để định nghĩa các lớp. Định nghĩa này được đặt giữa các dấu ngoặc. Mã định nghĩa lớp được gọi là class body.


        [ public ] class Tên_lớp {

                // định nghĩa field
                // định nghĩa constructor
                // định nghĩa method
       }
trong đó:

    •    từ khóa public là tùy chọn (vì thế nó được đặt trong ngoặc vuông). Nó cụ thể sự truy cập vào lớp này đối với các chương trình khác (một lớp được               định nghĩa với từ khóa public cho sự truy cập từ mọi nơi).
    •    Tên_lớp là tên của lớp. Nó phải tuân theo các hạn định quy định về các từ định danh và (theo các quy định gọi tên) nó phải bắt đầu bằng một chữ               cái viết hoa.


Ví dụ về các định nghĩa lớp:

public class Pair {// định nghĩa về lớp thể hiện các cặp số nguyên
// class body 

} 

public class Car  { 
// class body 
} 


class TestPair { 
// class body 
} 



các Field (miền) và method (phương pháp) của một lớp được gọi là members (các thành viên).

        Thành viên của một lớp = miền + phương pháp





2. Định nghĩa các thuộc tính

Các miền của một lớp xác định các thành phần cấu tạo nên đối tượng của lớp.
Ví dụ, các đối tượng thể hiện các cặp số nguyên bao gồm hai số nguyên.

Điều này phải được ghi lại trong định nghĩa của lớp Pair. Một cách đơn giản để làm việc này là khai báo biến thể của loại đầy đủ.

public class Pair {

        int a;
        int b;

// định nghĩa constructor và method…

}

Những khái niệm như thế quy định rằng mỗi đối tượng của lớp Pair chứa hai số nguyên – nói cách khác nó bao gồm hai phần tử là số nguyên. Các từ định danh a và b là các tên tùy ý chọn cần thiết để truy cập vào hai số này.
Các miền của lớp thường được khai báo với modifier private (cá nhân), nghĩa là chúng chỉ có thể được truy cập từ các đối tượng trong lớp mà chúng xác định..



Định nghĩa các miền của một lớp

[public] class AClassName {

    [field_modifier] type_name variable_name [khởi tạo];
    //....    


Chú ý:
    •    dấu ngoặc vuông thể hiện các phần tử tùy chọn của định nghĩa
    •    field_modifier thông thường là private
    •    khởi tạo có một dạng phổ biến: nó là một biểu thức bắt đầu bằng một dấu =,  phần này sẽ được tìm hiểu sau.

Ví dụ: 

public class Pair { 
    private int a; 
    private int b; 
    // ... 
} 

Các miền của một lớp có thể là các đối tượng (các biến thể quy định đối tượng – tham chiếu). Hãy xem xét định nghĩa sau của lớp Book, miêu tả các quyển sách.

public class Book { 
    private String author; 
    private String title;   
    private double price;   
    // .... 
} 

Biến thể của loại String là các tham chiếu. Chúng biểu thị các đối tượng đầy đủ - các chuỗi.

Định nghĩa của lớp Pair nói rằng mỗi đối tượng của nó chứa hai phần tử - là các số nguyên. Sauk hi một đối tượng được tại ra và khởi động nó sẽ bao gồm hai phần tử - các số nguyên với một giá trị nào đó. Một đối tượng khác của lớp này bao gồm các số khác, (có thể) với các giá trị khác so với đối tượng ban đầu.


r



Class Pair
Lớp Pair
Private
Cá nhân/riêng
Class field
Miền lớp
creation
Tạo ra
Specify number and type of components
Chỉ rõ/ quy định số lượng và loại thành phần
object
Đối tượng
Each object has its own elements
Mỗi đối tượng có các phần tử riêng của nó

Chuyện gì sẽ xảy ra nếu các giá trị của các thuộc tính được thiết lập không phải bởi một constructor hay bất cứ cách nào khác?

Theo mặc định, trong quá trình tạo ra đối tượng, các miền của lớp nhận giá trị là zero:
    •    đối với loại số nguyên điều đó có nghĩa là số nguyên 0
    •    đối với loại floating point điều đó có nghĩa là số thực 0.0
    •    đối với loại boolean điều đó có nghĩa là giá trị false
    •    đối với các tham chiếu đó là giá trị null (tham chiếu không chỉ bất cứ đối tượng nào).




3. Định nghĩa các thao tác (phương thức)

Định nghĩa lớp quy định cụ thể các thông điệp được hiểu bởi một đối tượng.
Khái niệm về phương pháp tương tự như khái niện đã biết về hàm.


Một phương pháp – tương tự như hàm, là một chuỗi riêng biệt các câu lệnh, được ghi lại trong mã nguồn có thể được gọi lặp đi lặp lại tại các thời điểm khác nhau của một chương trình.

Các phương pháp được dùng chủ yếu để tiến hành các thao tác trên đối tượng.


Do đó phương pháp – khác với hàm là thường được gọi cho các đối tượng cụ thể đang tồn tại.


Việc gọi một phương pháp cho các đối tượng được thực hiện với sự trợ giúp của toán tử dấu chấm. Nếu chữ p biểu thị một đối tượng thuộc lớp Pair (p là tham chiếu đến đối tượng của lớp Pair), và nếu phương pháp show được xác định trong lớp này thì việc gọi phương pháp cho đối tượng p được thể hiện như sau:

p.show();

“gọi một phương pháp cho một đối tượng” nghĩa là “gửi một thông điệp đến một đối tượng” hay “đưa ra một yêu cầu cho đối tượng”.


gọi phương pháp show cho đối tượng p
=
gửi thông điệp show cho đối tượng p
=
yêu cầu hiển thị đối tượng p


Cú pháp của định nghĩa của một phương pháp là như sau:

[access_modifier] result_type method_name (parameter_list) {
}

Chú ý:

    •    dấu ngoặc móc thể hiện các phần tử tùy chọn
    •    mã nguồn đặt giữa các dấu ngoặc được gọi là method body


Access modifier quy định liệu một phương pháp có thể được gọi từ bên ngoài lớp mà nó xác định hay không. Cụ thể:

    •    từ bổ sung public thể hiện rằng phương pháp có thể được gọi từ các lớp khác
    •    trong khi từ private thể hiện phương pháp chỉ có thể được gọi từ trong lớp mà nó xác định.


Tên của phương pháp bắt đầu với các chữ cái viết thường (theo như các khái niệm trong tiếng Hungary), ví dụ: count, setPrice, getAuthor.
Các phương pháp cho phép mọi người truy cập được cụ thể với từ bổ sung public. Các phương pháp mà thực hiện các thao tác cục bộ quan trọng đối với các phương pháp khác trong lớp thì cần được khai báo private.
Danh sách các tham số chứa các khai báo của tham số được phân tách bởi các dấu phẩy. Danh sách này cũng có thể là danh sách trống.

Một phương pháp có thể trả lại một giá trị. Định nghĩa của phương pháp sau đó phải khai báo loại kết qủa và dùng câu lệnh return để kết thúc (câu lệnh này sẽ trả lại dữ liệu loại cụ thể). Nếu phương pháp không trả lại bất cứ giá trị nào thì kiểu của kết quả được đặc trưng với từ khóa void. Nó sẽ tự kết thúc sau khi chạy tất cả các câu lệnh trong method body hay sau khi không có đối số nào được trả lại.

Câu lệnh return có dạng sau:



        return [ biểu thức ];


Ví dụ, một phương pháp trả lại tổng của hai số nguyên có thể có dạng sau:

int sum(int x, int y) { 
    int z = x + y; 
    return z; 
} 

hoặc: 

int sum(int x, int y) { 
    return x + y; 
} 

Bằng cách gọi phương pháp sum có các đối số là các tham số x và y. Việc chạy phương pháp chính là cộng hai số và trả lại kết quả. Loại kết quả được quy đinh trong định nghĩa của phương pháp.

Một ví dụ khác:

void say(String s) { 
    System.out.println(s); 
} 

Việc gọi phương pháp say dẫn đến việc in chuỗi đã cho với tư các là đối số ra bảng nhắc câu lệnh. Nó không trả lại bất cứ một giá trị nào nhưng loại kết quả phải được quy định (với từ khóa void).

Trong Java các đối số được đưa và trong phương pháp bằng giá trị.

Điều này có nghĩa là bên trong method body (cấu trúc phương pháp) chúng ta điều hành trên bản sao của đối số chứ không phải là bản thân đối số. Do vậy những sửa đổi của đối số được đưa và có tính chất cục bộ. Chúng không ảnh hưởng đến đối số gốc. 

Do đó sau khi gọi phương pháp sau:

void incr(int x) {
    ++x;
}

sau đó gọi phương pháp này:

int z = 1;
incr(z);
System.out.println(z);

với biến z = 1 được đưa vào trong đối số, tham số x bên trong cấu trúc phương pháp có giá trị là 2. Tuy nhiên sau khi hoàn thành việc gọi phương pháp và trả lại sự kiểm soát lên điểm gọi, biến z vẫn lưu giá trị là 1.




4. Định nghĩa các phương thức khởi tạo

Việc tạo đối tượng là một thao tác đặc biệt được tiến hành bằng biểu thức new.

Đây thực chất là việc gọi một constructor, phương thức khởi tạo, của lớp.


Một constructor được sử dụng để khởi tạo một miền đối tượng.
Constructor là một loại hàm đặc biệt mà:

    •    có tên giống hệt tên lớp
    •    không có giá trị trả lại
    •    có một danh sách các tham số (danh sách này có thể trống)


Tương tự như định nghĩa phương pháp, định nghĩa constructor có thể được bắt đầu bằng một từ bổ sung truy cập quy định cụ thể liệu constructor có thể được gọi từ các lớp khác hay không.


Cú pháp của định nghĩa constructor như sau:

[public] lớp tên_lớp {

    // định nghĩa constructor
    [ từ bổ sung_truy cập] tên_lớp(danh_sách_các_tham_số) {
        // constructor body
    }    


Lớp Pair có thể có các constructor sau: 






public class Pair {
    private int a;
    private int b;
    
    public Pair(int x, int y) {   // Các miền a và b được
        a = x;                          // khởi tạo với
        b = y;                          // các giá trị của đối số 
    }
    ...
}

hoặc: 






public class Pair {
    private int a, b;

    public Pair(int x) {   // Constructor có một tham số 
        a = x;                  // khởi tạo cả hai miền
 b = x;
    }
    ...
}

Một lớp có thể có nhiều constructor với các danh sách tham số khác nhau. Trường hợp này tương đương với quá tải phương pháp.
Bằng cách sử dụng các constructor trên chúng ta có thể dễ dàng tạo ra các đối tượng của lớp Pair được khởi tạo với các giá trị đã cho:

Pair p1 = new Pair(10,11);  // a pair 10, 11 
Pair p2 = new Pair(2);         //a pair 2, 2


Constructor luôn được gọi bằng biểu thức new...

Một constructor đặc biệt là constructor không có tham số.
Nó sẽ được tự động thêm vào định nghĩa của lớp khi không có constructor nào được xác định. Cấu trúc của constructor mặc định là trống. Do vậy nếu chúng ta không tạo ra một constructor trong lớp thì constructor mặc định sẽ được gọi thông qua việc tạo trường hợp của lớp.

Chú ý: Constructor mặc định không được tạo ra nếu có một constructor khác xác định trong lớp.




5. Ví dụ định nghĩa lớp

Giả sử chúng ta cần một chương trình để quản lý một của hàng sách. Cửa hàng này bán những ấn phẩm như sách, báo, đĩa compact. 
Mỗi ấn phẩm có:
    
    một tiêu đề
    một nhà xuất bản    
    năm xuất bản
    số đăng ký (như ISBN, ISSN)
    giá
    số bản in

Tất cả những thuộc tính này là các miền của lớp.






public class Publication {
  
  private String title;
  private String publisher;
  private int year;
  private String ident;
  private double price;
  private int quantity;
...
}
Mỗi ấn phẩm là một đối tượng trong chương trình. Các đối tượng phải được khởi động theo cách nào đó. Để đảm bảo việc này chúng ta phải cung cấp một constructor, nó sẽ khởi động các lớp của đối tượng với những tham số đã cho.





public class Publication {

   private String title;
 private String publisher;
 private int year;
 private String ident;
 private double price;
 private int quantity;

 public Publication(String t, String pb, int y, String i, double pr, int q) 
 {
  title = t;      // miền tên sách được khởi tạo với giá trị của tham số t  
  publisher = pb; // miền nhà xuất bản được khởi tạo với giá trị của tham số pb
  year = y;       // vân vân ...
  ident = i;
  price = pr;
  quantity = q;
 }
 ...
}
Bây giờ chúng ta có thể tạo ra một đối tượng thể hiện một quyển sách, ví dụ như có tên là “Cats”, xuất bản bởi nhà xuất bản “Dog & Son”, có giá là 21.0 EUR.





Publication b = new Publication("Cats", "Dog & Sons", 2002, "ISBN6789", 21.0, 0);
Chúng ta có thể làm gì với các ấn phẩm?
Chúng ta có thể mua và bán chúng. Chúng ta có thể yêu cầu các thông tin liên quan đến ấn phẩm như tên sách, nhà xuất bản, năm xuất bản, thông tin xác minh hay chúng ta có thể tìm hiểu xem có bao nhiên bản copy trong kho. Có thể xảy ra trường hợp giá của ấn phẩm thay đổi vì thế chúng ta phải có cách để sửa đổi đối tượng thể hiện điều này.






public class Publication {
  
  ...

  // methods:

  public String getTitle() {
    return title;
  }

  public String getPublisher() {
    return publisher;
  }

  public int getYear() {
    return year;
  }

  public String getIdent() { 
    return ident;
  }
  
  public double getPrice() {
    return price;
  }

  public void setPrice(double p) {
    price = p;
  }

  public int getQuantity() {
    return quantity;
  }

  public void buy(int n) {
    quantity += n;
  }  
  
  public void sell(int n) {
    quantity -= n;
  }
}

Chúng ta kiểm tra lớp Publication trong một lớp khác tên là TestPub (trong phương pháp của nó là main). 






class PubTest {
 
  public static void main(String[] args) {
   
    // instance creation
    Publication b = new Publication("Cats", "Dog & Sons", 2002,
                                    "ISBN6789", 21.0, 0);

    int n = 10;                        // mua 10 bản
    b.buy(n);
    double cost = n * b.getPrice();    // tính giá
    System.out.println("To buy " + n + " copies:");
    System.out.println(b.getTitle());
    System.out.println(b.getPublisher()); 
    System.out.println(b.getYear());
    System.out.println(b.getIdent());    
    System.out.println("---------------\n: " + cost + " EUR was spent");
                                       // sell 4 copies and check how many was left 
    b.sell(4); 
    System.out.println("---------------");
    System.out.println("There are " + b.getQuantity() + " copies left");    
  }
}

Sau đây là dữ liệu đầu ra:


To buy 10 copies:
Cats
Dog & Sons
2002
ISBN6789
---------------
210.0 EUR was spent
---------------
There are 6 copies left





6. Đối tượng và tham chiếu

Chúng ta xem tiếp một phiên bản có thể của lớp Pair. Những đối tượng của nó thể hiện các cặp số nguyên - mỗi đối tượng được tạo nên từ hai phần tử là số nguyên (thành phần thứ nhất và thứ hai của một cặp). Những gì chúng ta có thể làm với các đối tượng của lớp Pair được cụ thể bởi một loạt các phương pháp của lớp này. Giả sử xa hơn nữa rằng những phương pháp sau được xác định trong lớp Pair (nghĩa là các đối tượng của nó hiểu những thông điệp sau):
    
    •    set - thiết lập giá trị của cặp (cho cả hai thành phần)
    •    show – in cặp (hiển thị các thành phần ra bảng nhắc câu lệnh)






public class Pair {
  private int a;
  private int b;
 
  public Pair() { 
  }
 
 
  public Pair(int x, int y) { 
    a = x;
    b = y;
  }
 
   
  public void set(int x, int y) { 
    a = x;
    b = y;
  }
 
  
  public void show() {
    System.out.println("( " + a + "," + b + " )");
  }

}
Để thiết lập giá trị cho một cặp và in giá trị đó ra dấu nhắc câu lệnh chúng ta phải làm gì?

    Trước hết, phải tạo ra một biến thể tham chiếu về một đối tượng mà thể hiện một cặp.
    Thứ hai, phải tạo ra một đối tượng của lớp Pair.
    Thứ ba, phải gửi thông điệp (set và show) đến đối tượng này.

Sau đây là chương trình:





public class PairSetAndShow {

  public static void main(String[] args) {

    Pair pair1 = new Pair(); // 1
    pair1.set( 1, 2 );       // 2
    pair1.show();            // 3

  }
}



Kết quả:

( 1 , 2 )

Giải thích:

        •    trong dòng được biểu thị bằng số 1 chúng ta khai báo biến thể pair 1 và tạo ra một đối tượng của lớp Pair với sự trợ giúp của biểu thức new. Có thể dễ dàng thấy      rằng, biểu thức mới bao gồm từ khoá new và tên của một lớp được đặt trong ngoặc. Thông tin cần thiết để tao ra một đối tượng có thể được thêm vào giữa các dấu ngoặc (với tư cách là đối số). Ở đây chúng ta không đưa vào bất cứ thông tin nào. Sau bài tiếp theo chúng ta sẽ hiểu tại sao biểu thức mới lại có dạng thức như vậy.
        •    lúc này chúng ta tdẫ có đối tượng và cặp biến thể mà có thể giúp thực hiện những thao tác trên đối tượng này.
        •    trong dòng 2 chúng ta gửi thông điệp set tới đối tượng tham chiếu bởi biến thể pair1. Để gửi thông điệp chúng ta phải chọn một đối tượng mà sẽ nhận thông điệp.                   Do vậy ký tự dấu chấm (.) được sử dụng, gọi là bộ chọn trong ngữ cảnh này. Tên sau chuỗi “pair1” là tên của thông điệp (“set”) được gửi đến đối tượng tham chiếu               bởi biến thể pair1. Các đối số tới thông điệp trong dấu ngoặc được nêu cụ thể. Đối số đầu tiên là giá trị mà sẽ được gắn cho thành phần đầu tiên trong pair. Tương                   tự, đối số thứ hai sẽ bắt đầu với thành phần thứ hai. Do đó biểu thức pair1.set(1,2); thiết lập giá trị của thành phần thứ nhất của đối tượng (cặp số nguyên) được                   tham chiếu bởi biến thể pair1.là 1 và thiết lập giá trị của cái thứ hai là 2.
        •    trong dòng thứ 3 đối tượng thể hiện bằng pair1 nhận được message show. Kết quả là cặp này được in ra bảng nhắc câu lệnh ở dạng (giá trị của thành phần đầu tiên,               giá trị của thành phần thứ hai) – trong miền hợp này cho kết quả là: (1, 2).

Biểu thức “đối tượng tham chiếu bởi biến thể pair1” nghĩa là gì? Tại sao chúng ta k nói “một đối tượng pair1” trong khi có thể nói “một số nguyên x”? Biến thể pair1 thực sự là gì?

Nhưng chúng ta đã thấy, nó được khai báo. Những có những công thức lạ tạo ra một đối tượng. 

Chuyện gì sẽ xảy ra nếu chúng ta chỉ viết:

Pair pair1;
pair1.set(1,2);
pair1.show();

Như đã trình bày trước đây, các khai báo biến thể tham chiếu về các đối tượng được xem như các khai báo biến thể loai cơ bản.

int x; // khai báo của biến thể loại int
pair p; // khai báo của biến thể p tham chiếu một đối tượng của lớp Pair

Tuy nhiên, có một sự khác biết khó thấy về nghĩa giữ hai khai báo này.
Khai báo của biến thể x chỉ rõ ổ nhớ lưu một số nguyên (4 byte). Do vậy x đồng nghĩa với một đơn vị dữ liệu - một số nguyên.
Sau khi gắn x=4, giá trị 4 được lưu vào vị trí trong ổ nhớ biểu hiện bởi x. Tình huống này được mô tả thông qua hình sau:


r


1. phân vùng bộ nhớ cho biến thể x    
2. giá trị 4 được lưu trong vùng nhớ xác định

Một khai báo của biến thể số x tạo ra một “đối tượng” trong ổ nhớ - là một số nguyên (trước khi giá trị của nó được thiết lập đầy đủ, nó có một giá trị mặc định nào đó - thường là 0).

Bằng cách khai báo một biến thể có tham chiếu hoàn toàn khác đến một đối tượng thuộc một lớp nào đó thì tình huống là hoàn toàn khác. Những khai báo đó không tạo ra bất cứ một đối tượng nào (nó không chỉ định vùng lưu giữ đối tượng).

Đối tượng phải được tạo ra bằng cách sử dụng biểu thức 
new.

Áp dụng cách này sẽ dẫn đến việc xác định không gian bộ nhớ cho các đối tượng. Bộ nhớ được xác định trong phần động (thay đổi trong quá trình thực hiện chương trình) được gọi là heap (vùng lưu giữ đặc biệt).
Giá trị của biểu thức mưói là địa chỉ trong bộ nhớ của đối tượng được tạo ra. Địa chỉ này có thể được lưu trong biến thể mà sau đó sẽ dùng để điều khiển đối tượng.
Do đó, khai báo:

Pair p;

không tạo ra bất cứ một đối tượng nào của lớp Pair.

Và nếu không có đối tượng thì chúng ta không thể gửi bất cứ một thông điệp nào cả. Đó là lý do tại sao p.set(…) và p.show() là những lệnh sai.
Biến thể p không lưu đối tượng nào.
Tuy nhiên nó có thể giữ địa chỉ được gọi là tham chiếu đến một đối tượng.


Tham chiếu là một giá trị biểu thị địa chỉ của một đối tượng trong bộ nhớ.


Một đối tượng của lớp Pair có thể được tạo ra bằng cách sử dụng biểu thức mới Pair(). Bằng cách lưu giá trị của đối tượng vào biến thể p, chúng ta có thể vận dụng đối tượng này.

Pair p;
p = new Pair();

Hình sau giải thích vấn đề trên:


r 

null     = Vô giá trị 
heap   = Vùng lưu trữ đặc biệt heap

ở đây:

        1.    Việc phân vùng bộ nhớ của biến thể p lưu giữ một tham chiếu đến một đối tượng. Giá trị của tham chiếu là null có nghĩa là nó không quy chiếu đến bất cứ đối tượng                đang tồn tại nào.
        2.    Việc tính toán biểu thức new dẫn đến việc phân bổ vùng lưu trữ cho đối tượng của lớp Pair (sử dụng một địa chỉ biến thể nào đó, ở đây là: 1304). Kích cỡ của cùng phân            bổ đủ lớn để lưu hai số nguyên (các thành phần cấu thành của một cặp). Hiện tại cả hai thành phần đều có giá trị là 0.
        3.    Giá trị của biểu thức mới là tham chiếu (hay địa chỉ 1304). Giá trị được lưu giũa trong vùng bộ nhớ phân chia cho biến thể p.
        4.    Biến thể p có giá trị tham chiếu đến đối tượng của lớp Pair. Trong bước hai đối tượng này được phân bổ trên vũng lưu trữ dưới địa chỉ 1304.

    Do đó biến thể p có tham chiếu đến đối tượng của lớp Pair.
    Và như đã nói từ trước, biến thể p có thể quy chiếu đến một đối tượng của lớp Pair. Nhưng không phải nó luôn giữ tham chiếu đến một đối tượng đang tồn tại. Sau khi khai báo, nhưng trước khi bắt đầu, một biến thể không có bất cứ tham chiếu nào vì chưa có đối tượng nào được tạo ra.

    Lúc này câu hỏi đặt ra: biến thể p thuộc kiểu nào? nói chung: loại của biến thể biểu thị đối tượng là gì?

    Trong ngôn ngữ Java, bên cạnh các kiểu số và boolean còn có thêm một kiểu nữa: kiểu quy chiếu.



    Tất cả các biến thể khai báo với tên kiểu trong vùng tên kiểu là kiểu quy chiếu. Những biến thể đó có thể có các tham chiếu đến các đối tượng đang có hay chúng không có bất cứ sự tham chiếu nào.


    Nếu một biến thể thuộc kiểu quy chiếu không có tham chiếu nào đến một đối tượng thì giá trị của nó là null, đây là một từ khoá trong Java.

    Do đó, giá trị được chấp nhận cho các biến thể loại quy chiếu là sự tham chiếu đến các đối tượng hay null.

    Tương tự như, 1 là nguyên dạng kiểu int – null là một nguyên dạng thuộc kiểu quy chiếu.

    Các tham chiếu có thể so sánh được:

        •    == được dùng để so sánh bằng
        •    != được dùng để so sánh khác

    Giá trị của một tham chiếu khác hay giá trị null có thể được gắn cho một biến thể thuộc loại quy chiếu.
    Cần nhớ rằng các thao tác trên tiến hành trên các biến thể biểu thị đối tượng (quy chiếu) hoạt động trên chính các tham chiếu chứ không phải trên đối tượng mà chúng quy chiếu. Các thao tác trên đối tượng được tiến hành bằng các phương pháp gọi hàm (sử dụng toán tử dấu chấm).

    Giả sử chúng ta muốn tiến hành các thao tác tương tự trên hai phânf tử dữ liệu là số nguyên và hai phân tử khác là đối tượng của lớp Pair:

        1.    thiết lập giá trị của phần tử dữ liệu đầu tiên và giá trị của phần tử dữ liệu thứ 2.
        2.    chỉ định cho biến thể biểu thị phần tử dữ liệu thứ hai giá trị của biến thể biểu thị phần tử dữ liệu thứ nhất.
        3.    sửa dổi giá trị của phần tử dữ liệu thứ hai
        4.    so sánh các biến thể biểu thị cả hai phần tử

    Ngoài ra trong cả hai trường hợp chúng ta giới thiệu phần tử dữ liệu thứ 3 mà giá trị sẽ được thiết lập cho giá trị của phần tử dữ liệu thứ hai. Sau đó chúng ta so sánh các biến thể biểu thị những phần tử này.

    Sau đây là chương trình:






    public class Difference {
    
      public static void main(String[] args) {
        
        // Operations on variables of primitive types
        int x, y, z;
        x = 3;
        y = 4;
        x = y;
        y = 5;
        z = 5;
        System.out.println("x = " + x);
        System.out.println("y = " + y);
        System.out.println("z = " + z);
        if (x == y) System.out.println ("x and y are equal."); 
        else  System.out.println ("x and y are not equal.");
        if (y == z) System.out.println ("y and z are equal."); 
        else  System.out.println ("y and z are not equal.");
    
        // Analogous operations on variables of reference type
        Pair px = new Pair(), py = new Pair(), pz = new Pair();
        px.set( 3, 3 );
        py.set( 4, 4 );
        pz.set( 5, 5 );
        px = py;
        py.set( 5, 5 );
        System.out.print("Pair px: "); px.show();
        System.out.print("Pair py: "); py.show();
        System.out.print("Pair pz: "); pz.show();
        if (px == py) System.out.println ("px and py equal."); 
        else  System.out.println ("px and py are not equal.");
        if (py == pz) System.out.println ("py and pz equal."); 
        else  System.out.println ("py and pz are not equal.");
    
      }
    }

    Kết quả:


    x = 4
    y = 5
    z = 5
    x and y are not equal.
    y and z are equal.
    Para px: ( 5 , 5 )
    Para py: ( 5 , 5 )
    Para pz: ( 5 , 5 )
    px and py are equal.
    py and pz are not equal. 

    Dữ liệu đầu ra của chương trình có thể gây ngạc nhiên cho những ai không hiểu sự khác biệt giữa các thao tác trên biến thể loại cơ bản và loại quy chiếu. Những nhận xét sau đây sẽ làm rõ hơn dữ liệu đầu ra của chương trình:

        •    Biểu thức new Pair () là một tham chiểu đến đối tượng vừa được tạo ra của lớp Pair. Những tham chiếu đó được lưu trong các biến thể lớp Pair.
        •    gọi phương pháp set () thiết lập giá trị của phần tử dữ liệu trong một đối tượng của lớp Pair. Đối tượng đó được truy cập bằng cách sử dụng một tham chiếu đến đối               tượng (px.set(…)).
        •    Chỉ định py=px dẫn đến việc sao chép tham chiếu py (giá trị quy chiếu đến tượng có các phần tử [3,3]) đến biến thể px (biến thể quy chiếu đến đối tượng có các phần tử           [4, 4]). Sauk hi chỉ định, px và px đều quy chiếu đến một đối tượng (là một cặp với các phần tử [3, 3]. Cặp có các phần tử [4, 4] không thể truy cập được nữa.
        •    Các giá trị mới của các phần tử của cặp biểu thị bằng py được thiết lập bằng cách sử tham chiếu py (py.set(5,5)). Nhưng cả py và px biểu thị một cặp (đối tượng), vì vậy           việc gọi phương pháp show () in ra các dữ liệu ra giống nhau (các phần tử của cặp [5,5]). 
        •    Kết quả so sánh của py và pz chứng minh sự thật trên: py và pz quy chiếu đến các đối tượng khác nhau (giá trị của các biến thể này khác nhau), do vậy kết quả của               phép so sánh là sai mặc dù giá trị của các phần tử của hai đối tượng là bằng nhau [5,5]. 

    Chuyện gì xảy ra với đối tượng có giá trị của các phần tử là [4,4] mà lúc đầu được quy chiếu bởi biến thể py? Đối tượng này được xác định trên vùng lưu trữ heap với biểu thức py=new Pair(), vì thế nó chiếm một vùng trong bộ nhớ. Sau đó giá trị của các phần tử được sửa đổi (py.set(4,4)) nên những giá trị này được lưu vào trong vùng bộ nhớ đó. Tiếp theo giá trị của biến thể px được gắn với biến thể py. Do đó không có sự khác biệt trong đối tượng nằm bên trái của chương trình. Đối tượng này không thể truy cập được nữa vì các đối tượng chỉ có thể được truy cập bằng các tham số. Chúng ta có phải lo lắng vì điều này hay không? Nếu có nhiều đối tượng như thế này trong bộ nhớ liệu có gây ra tràn bộ nhớ hay không?

    Câu trả lời là không.

    Những đối tượng không được sử dụng (không được quy chiếu bởi bất cứ biến thể nào) sẽ bị loại bỏ ra khỏi bộ nhớ một cách tự động bởi trình thu nhặt rác.




    Những đối tượng không được sử dụng (không được quy chiếu bởi bất cứ biến thể nào) sẽ bị loại bỏ ra khỏi bộ nhớ một cách tự động bởi trình thu nhặt rác (garbage collecting).


    Chúng ta tóm tắt như sau:.



        •    Các đối tượng được tạo ra bằng cách sử dụng biểu thức new…

        •    Các đối tượng được vận hành bằng cách sử dụng các tham chiếu và gọi phương pháp.

        •    Các tham chiếu không phải là đối tượng – chúng quy chiếu đến đối tượng.

        •    Các biến thể loại tham chiếu phải được khai báo trước khi chúng được sử dụng trong chương trình.

        •    Các khai báo của tham số không tạo ra bất cứ đối tượng nào.

    Thanks for reading. Did you enjoy this post?

    :)) ;)) ;;) :D ;) :p :(( :) :( :X =(( :-o :-/ :-* :| 8-} :)] ~x( :-t b-( :-L x( =))

    Comment by facebook?

    Post a Comment

    Viết cảm nhận của bạn ở đây ... Leave your feelings here ....

    Recent comments