1. Thuộc tính: getters và setters

Khi một dự án lớn được phát triển bởi hàng tá lập trình viên cùng lúc, các vấn đề thường nảy sinh nếu họ xử lý dữ liệu được lưu trữ trong các trường lớp theo cách khác.

Có thể mọi người không nghiên cứu chi tiết tài liệu của lớp hoặc có lẽ nó không mô tả mọi trường hợp. Do đó, thường xảy ra các tình huống khi dữ liệu bên trong của đối tượng có thể bị "hỏng", khiến đối tượng không hợp lệ.

Để tránh những tình huống này, theo thông lệ, tất cả các trường của lớp đều được đặt ở chế độ riêng tư trong Java . Chỉ các phương thức của lớp mới có thể sửa đổi các biến của lớp. Không có phương thức nào từ các lớp khác có thể truy cập trực tiếp vào các biến.

Nếu bạn muốn các lớp khác có thể lấy hoặc thay đổi dữ liệu bên trong các đối tượng của lớp mình, bạn cần thêm hai phương thức vào lớp của mình — một phương thức get và một phương thức set. Ví dụ:

Mã số Ghi chú
class Person
{
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }

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


privatetrường tên



Khởi tạo trường thông qua hàm tạo


getName()— Phương thức này trả về giá trị của trường tên




setName()— Phương thức này thay đổi giá trị của trường tên

Không có lớp nào khác có thể trực tiếp thay đổi giá trị của trường tên. Nếu ai đó cần lấy giá trị của trường tên, họ sẽ phải gọi phương getName() thức trên một Personđối tượng. Nếu một số mã muốn thay đổi giá trị của trường tên, nó sẽ cần gọi phương setName() thức trên một Personđối tượng.

Phương thức này getName()còn được gọi là " getter cho trường tên" và setName()phương thức này được gọi là " setter cho trường tên".

Đây là một cách tiếp cận rất phổ biến. Trong 80-90% mã Java, bạn sẽ không bao giờ thấy các biến công khai trong một lớp. Thay vào đó, chúng sẽ được khai báo private(hoặc protected) và mỗi biến sẽ có các phương thức getter và setters công khai.

Cách tiếp cận này làm cho mã dài hơn, nhưng đáng tin cậy hơn.

Truy cập trực tiếp vào một biến lớp cũng giống như quay đầu ô tô của bạn qua hai vạch vàng : dễ dàng hơn và nhanh hơn, nhưng nếu mọi người đều làm như vậy thì mọi thứ sẽ trở nên tồi tệ hơn cho mọi người.

Giả sử bạn muốn tạo một lớp mô tả một điểm ( x, y). Đây là cách một lập trình viên mới vào nghề sẽ làm điều đó:

class Point
{
   public int x;
   public int y;
}

Đây là cách một lập trình viên Java có kinh nghiệm sẽ làm điều đó:

Mã số
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y;
   }
}

Mã có dài hơn không? Không còn nghi ngờ gì nữa.

Nhưng bạn có thể thêm xác thực tham số cho getters và setters. Ví dụ: bạn có thể đảm bảo rằng xyluôn lớn hơn 0 (hoặc không nhỏ hơn 0). Ví dụ:

Mã số Ghi chú
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x < 0 ? 0 : x;
      this.y = y < 0 ? 0 : y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x < 0 ?  0 : x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y < 0 ? 0 : y;
   }
}


2. Tuổi thọ của đối tượng

Bạn đã biết rằng các đối tượng được tạo bằng newtoán tử, nhưng các đối tượng bị xóa như thế nào? Chúng không tồn tại mãi mãi. Không có đủ bộ nhớ cho điều đó.

Trong nhiều ngôn ngữ lập trình, chẳng hạn như C++, có một deletetoán tử đặc biệt để xóa đối tượng. Nhưng nó hoạt động như thế nào trong Java?

Trong Java, mọi thứ được sắp xếp hơi khác một chút. Java không có toán tử xóa. Điều này có nghĩa là các đối tượng không bị xóa trong Java? Không, chúng đã bị xóa, tất nhiên. Nếu không, các ứng dụng Java sẽ nhanh chóng hết bộ nhớ và sẽ không có bất kỳ cuộc thảo luận nào về việc các chương trình chạy liên tục trong nhiều tháng.

Trong Java, việc xóa đối tượng hoàn toàn tự động. Máy Java tự xử lý việc xóa các đối tượng. Quá trình này được gọi là thu gom rác và cơ chế thu gom rác được gọi là bộ thu gom rác ( GC ).

Vậy làm thế nào để máy Java biết khi nào cần xóa một đối tượng?

Bộ thu gom rác chia tất cả các đối tượng thành "có thể truy cập" và "không thể truy cập". Nếu có ít nhất một tham chiếu đến một đối tượng, nó được coi là có thể truy cập được. Nếu không có biến tham chiếu đến một đối tượng, thì đối tượng đó được coi là không thể truy cập được và được coi là rác, có nghĩa là nó có thể bị xóa.

Trong Java, bạn không thể tạo một tham chiếu đến một đối tượng hiện có — bạn chỉ có thể gán các tham chiếu mà bạn đã có. Nếu chúng ta xóa tất cả các tham chiếu đến một đối tượng, thì nó sẽ bị mất vĩnh viễn.

Tài liệu tham khảo thông tư

Logic đó nghe có vẻ tuyệt vời cho đến khi chúng ta bắt gặp một phản ví dụ đơn giản: giả sử chúng ta có hai đối tượng tham chiếu lẫn nhau (lưu trữ các tham chiếu đến nhau). Không có đối tượng nào khác lưu trữ tham chiếu đến các đối tượng này.

Những đối tượng này không thể được truy cập từ mã, nhưng chúng vẫn được tham chiếu.

Đây là lý do tại sao trình thu gom rác chia các đối tượng thành có thể truy cập và không thể truy cập thay vì "được tham chiếu" và "không được tham chiếu".

đối tượng có thể tiếp cận

Đầu tiên, các đối tượng còn sống 100% được thêm vào danh sách có thể truy cập. Ví dụ: luồng hiện tại ( Thread.current()) hoặc bảng điều khiển InputStream ( System.in).

Sau đó, danh sách các đối tượng có thể truy cập sẽ mở rộng để bao gồm các đối tượng được tham chiếu bởi tập hợp các đối tượng có thể truy cập ban đầu. Sau đó, nó lại được mở rộng để bao gồm các đối tượng được tham chiếu bởi tập mở rộng này, v.v.

Điều đó có nghĩa là nếu có một số đối tượng chỉ tham chiếu đến nhau, nhưng không có cách nào để tiếp cận chúng từ các đối tượng có thể truy cập, thì những đối tượng đó sẽ được coi là rác và sẽ bị xóa.


3. Thu gom rác

Phân mảnh bộ nhớ

Một điểm quan trọng khác liên quan đến việc xóa đối tượng là sự phân mảnh bộ nhớ. Nếu bạn liên tục tạo và xóa các đối tượng, bộ nhớ sẽ sớm bị phân mảnh nặng nề: các vùng bộ nhớ bị chiếm dụng sẽ được xen kẽ với các vùng bộ nhớ trống.

Kết quả là, chúng ta có thể dễ dàng rơi vào tình huống không thể tạo một đối tượng lớn (ví dụ: một mảng có một triệu phần tử), vì không có nhiều bộ nhớ trống. Nói cách khác, có thể có bộ nhớ trống, thậm chí rất nhiều, nhưng có thể không có khối bộ nhớ trống lớn liền kề

Tối ưu hóa bộ nhớ (chống phân mảnh)

Máy Java giải quyết vấn đề này theo một cách cụ thể. Nó trông giống như thế này:

Bộ nhớ được chia thành hai phần. Tất cả các đối tượng được tạo (và xóa) chỉ trong một nửa bộ nhớ. Khi đến lúc dọn sạch các lỗ hổng trong bộ nhớ, tất cả các đối tượng trong nửa đầu sẽ được sao chép sang nửa sau. Nhưng chúng được sao chép ngay cạnh nhau để không có lỗ hổng.

Quá trình trông đại khái như thế này:

Bước 1: Sau khi tạo đối tượng

Thu gom rác trong Java

Bước 2: Xuất hiện các “lỗ hổng”

Thu gom rác trong Java 2

Bước 3: Loại bỏ các "lỗ hổng"

Thu gom rác trong Java 3

Và đó là lý do tại sao bạn không cần xóa các đối tượng. Máy Java chỉ cần sao chép tất cả các đối tượng có thể truy cập vào một vị trí mới và giải phóng toàn bộ vùng bộ nhớ nơi các đối tượng từng được lưu trữ.