"Tôi sẽ nói với bạn về « công cụ sửa đổi truy cập ». Tôi đã nói về chúng một lần trước đây, nhưng sự lặp lại là trụ cột của việc học."

Bạn có thể kiểm soát quyền truy cập (khả năng hiển thị) mà các lớp khác có đối với các phương thức và biến của lớp bạn. Công cụ sửa đổi truy cập trả lời câu hỏi «Ai có thể truy cập phương thức/biến này?». Bạn chỉ có thể chỉ định một công cụ sửa đổi cho mỗi phương thức hoặc biến.

1) công cụ sửa đổi « công khai ».

Một biến, phương thức hoặc lớp được đánh dấu bằng công cụ sửa đổi công khai có thể được truy cập từ bất kỳ đâu trong chương trình. Đây là mức độ mở cao nhất: không có hạn chế.

2) công cụ sửa đổi « riêng tư ».

Một biến, phương thức hoặc lớp được đánh dấu bằng công cụ sửa đổi riêng tư chỉ có thể được truy cập trong lớp nơi nó được khai báo. Phương thức hoặc biến được đánh dấu bị ẩn khỏi tất cả các lớp khác. Đây là mức độ riêng tư cao nhất: chỉ lớp học của bạn mới có thể truy cập được. Các phương thức như vậy không được kế thừa và không thể bị ghi đè. Ngoài ra, chúng không thể được truy cập trong lớp con.

3)  « Công cụ sửa đổi mặc định ».

Nếu một biến hoặc phương thức không được đánh dấu bằng bất kỳ công cụ sửa đổi nào, thì nó được coi là được đánh dấu bằng công cụ sửa đổi "mặc định". Các biến và phương thức với công cụ sửa đổi này được hiển thị cho tất cả các lớp trong gói nơi chúng được khai báo và chỉ cho các lớp đó. Công cụ sửa đổi này còn được gọi là quyền truy cập " gói " hoặc " gói riêng ", gợi ý thực tế rằng quyền truy cập vào các biến và phương thức được mở cho toàn bộ gói chứa lớp.

4) công cụ sửa đổi « được bảo vệ ».

Mức truy cập này rộng hơn một chút so với gói . Một biến, phương thức hoặc lớp được đánh dấu bằng công cụ sửa đổi được bảo vệ có thể được truy cập từ gói của nó (như "gói") và từ tất cả các lớp kế thừa.

Bảng này giải thích tất cả:

Loại hiển thị từ khóa Truy cập
Lớp của bạn Gói của bạn hậu duệ Tất cả các lớp học
Riêng tư riêng tư Đúng KHÔNG KHÔNG KHÔNG
Bưu kiện (không có sửa đổi) Đúng Đúng KHÔNG KHÔNG
được bảo vệ được bảo vệ Đúng Đúng Đúng KHÔNG
Công cộng công cộng Đúng Đúng Đúng Đúng

Có một cách để dễ dàng ghi nhớ bảng này. Hãy tưởng tượng rằng bạn đang viết di chúc. Bạn đang chia mọi thứ của mình thành bốn loại. Ai được sử dụng những thứ của bạn?

Ai có quyền truy cập bổ nghĩa Ví dụ
chỉ  tôi riêng tư Nhật ký cá nhân
Gia đình (không có sửa đổi) Nhưng bưc ảnh gia đinh
Gia đình và những người thừa kế được bảo vệ Bất động sản gia đình
Mọi người công cộng hồi ức

"Giống như tưởng tượng rằng các lớp học trong cùng một gói là một phần của một gia đình."

"Tôi cũng muốn cho bạn biết một số sắc thái thú vị về các phương pháp ghi đè."

1) Thực hiện ngầm định một phương thức trừu tượng.

Giả sử bạn có đoạn mã sau:

Mã số
class Cat
{
 public String getName()
 {
  return "Oscar";
 }
}

Và bạn đã quyết định tạo một lớp Tiger kế thừa lớp này và thêm giao diện cho lớp mới

Mã số
class Cat
{
 public String getName()
 {
   return "Oscar";
 }
}
interface HasName
{
 String getName();
 int getWeight();
}
class Tiger extends Cat implements HasName
{
 public int getWeight()
 {
  return 115;
 }

}

Nếu bạn chỉ triển khai tất cả các phương thức còn thiếu mà IntelliJ IDEA yêu cầu bạn triển khai, thì sau này, bạn có thể sẽ mất nhiều thời gian để tìm lỗi.

Nó chỉ ra rằng lớp Tiger có một phương thức getName kế thừa từ Cat, phương thức này sẽ được coi là triển khai phương thức getName cho giao diện HasName.

"Tôi không thấy có gì khủng khiếp về điều đó."

"Cũng không tệ lắm, có khả năng phạm sai lầm."

Nhưng nó có thể còn tồi tệ hơn:

Mã số
interface HasWeight
{
 int getValue();
}
interface HasSize
{
 int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
 public int getValue()
 {
  return 115;
 }
}

Hóa ra không phải lúc nào bạn cũng có thể kế thừa từ nhiều giao diện. Chính xác hơn, bạn có thể kế thừa chúng, nhưng bạn không thể triển khai chúng một cách chính xác. Nhìn vào ví dụ. Cả hai giao diện đều yêu cầu bạn triển khai phương thức getValue(), nhưng không rõ nó sẽ trả về cái gì: trọng lượng hay kích thước? Điều này là khá khó chịu để phải đối phó với.

"Tôi đồng ý. Bạn muốn triển khai một phương thức, nhưng bạn không thể. Bạn đã kế thừa một phương thức có cùng tên từ lớp cơ sở. Nó bị hỏng."

"Nhưng có một tin tốt."

2) Mở rộng tầm nhìn. Khi kế thừa một kiểu, bạn có thể mở rộng khả năng hiển thị của một phương thức. Cái này nó thì trông như thế nào:

mã Java Sự miêu tả
class Cat
{
 protected String getName()
 {
  return "Oscar";
 }
}
class Tiger extends Cat
{
 public String getName()
 {
  return "Oscar Tiggerman";
 }
}
Chúng tôi đã mở rộng khả năng hiển thị của phương thức từ protectedsang public.
Mã số Tại sao đây là «hợp pháp»
public static void main(String[] args)
{
 Cat cat = new Cat();
 cat.getName();
}
Mọi thứ đều tuyệt vời. Ở đây, chúng tôi thậm chí không biết rằng khả năng hiển thị đã được mở rộng trong lớp con cháu.
public static void main(String[] args)
{
 Tiger tiger = new Tiger();
 tiger.getName();
}
Ở đây chúng tôi gọi phương thức có khả năng hiển thị đã được mở rộng.

Nếu điều này là không thể, chúng ta luôn có thể khai báo một phương thức trong Tiger:
public String getPublicName()
{
super.getName(); // gọi phương thức được bảo vệ
}

Nói cách khác, chúng tôi không nói về bất kỳ vi phạm bảo mật nào.

public static void main(String[] args)
{
 Cat catTiger = new Tiger();
 catTiger.getName();
}
Nếu tất cả các điều kiện cần thiết để gọi một phương thức trong lớp cơ sở ( Cat ) được thỏa mãn, thì chúng chắc chắn thỏa mãn để gọi phương thức ở loại con ( Tiger ). Bởi vì các hạn chế đối với cuộc gọi phương thức là yếu, không mạnh.

"Tôi không chắc mình đã hoàn toàn hiểu, nhưng tôi sẽ nhớ rằng điều này là có thể."

3) Thu hẹp loại trả về.

Trong một phương thức được ghi đè, chúng ta có thể thay đổi kiểu trả về thành kiểu tham chiếu thu hẹp.

mã Java Sự miêu tả
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Tiger getMyParent()
 {
  return (Tiger) this.parent;
 }
}
Chúng tôi đã ghi đè phương thức getMyParentvà bây giờ nó trả về một Tigerđối tượng.
Mã số Tại sao đây là «hợp pháp»
public static void main(String[] args)
{
 Cat parent = new Cat();

 Cat me = new Cat();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Mọi thứ đều tuyệt vời. Ở đây chúng ta thậm chí không biết rằng kiểu trả về của phương thức getMyParent đã được mở rộng trong lớp con cháu.

«mã cũ» đã hoạt động và hoạt động như thế nào.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Tiger me = new Tiger();
 me.setMyParent(parent);
 Tiger myParent = me.getMyParent();
}
Ở đây chúng ta gọi phương thức có kiểu trả về đã được thu hẹp.

Nếu điều này là không thể, chúng ta luôn có thể khai báo một phương thức trong Tiger:
public Tiger getMyTigerParent()
{
return (Tiger) this.parent;
}

Nói cách khác, không có vi phạm bảo mật và/hoặc vi phạm truyền kiểu.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Và mọi thứ hoạt động tốt ở đây, mặc dù chúng tôi đã mở rộng loại biến thành lớp cơ sở (Cat).

Do ghi đè nên phương thức setMyParent chính xác được gọi.

Và không có gì phải lo lắng khi gọi phương thức getMyParent , vì giá trị trả về, dù là của lớp Tiger, vẫn có thể được gán cho biến myParent của lớp cơ sở (Cat) mà không gặp vấn đề gì.

Các đối tượng Tiger có thể được lưu trữ an toàn trong cả biến Tiger và biến Cat.

"Vâng. Hiểu rồi. Khi ghi đè các phương thức, bạn phải biết tất cả những thứ này hoạt động như thế nào nếu chúng ta chuyển các đối tượng của mình sang mã chỉ có thể xử lý lớp cơ sở và không biết gì về lớp của chúng ta. "

"Chính xác! Sau đó, câu hỏi lớn là tại sao chúng ta không thể thu hẹp loại giá trị trả về khi ghi đè một phương thức?"

"Rõ ràng là trong trường hợp này, mã trong lớp cơ sở sẽ ngừng hoạt động:"

mã Java Giải thích vấn đề
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Object getMyParent()
 {
  if (this.parent != null)
   return this.parent;
  else
   return "I'm an orphan";
 }
}
Chúng tôi đã quá tải phương thức getMyParent và thu hẹp loại giá trị trả về của nó.

Mọi thứ đều ổn ở đây.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 Cat myParent = me.getMyParent();
}
Sau đó, mã này sẽ ngừng hoạt động.

Phương thức getMyParent có thể trả về bất kỳ thể hiện nào của một Đối tượng, bởi vì nó thực sự được gọi trên một đối tượng Tiger.

Và chúng tôi không có kiểm tra trước khi chuyển nhượng. Do đó, hoàn toàn có khả năng biến myParent kiểu Cat sẽ lưu trữ một tham chiếu Chuỗi.

"Ví dụ tuyệt vời, Amigo!"

Trong Java, trước khi một phương thức được gọi, không có kiểm tra xem đối tượng có phương thức đó hay không. Tất cả các kiểm tra xảy ra trong thời gian chạy. Và một cuộc gọi [giả thuyết] đến một phương thức bị thiếu rất có thể sẽ khiến chương trình cố thực thi mã byte không tồn tại. Điều này cuối cùng sẽ dẫn đến một lỗi nghiêm trọng và hệ điều hành sẽ buộc phải đóng chương trình.

"Chà. Giờ thì tôi biết rồi."