“我將向您介紹 «訪問修飾符»。我之前講過一次,但重複是學習的支柱。”

您可以控制其他類對您的類的方法和變量的訪問(可見性)。訪問修飾符回答了“誰可以訪問這個方法/變量?”這個問題。您只能為每個方法或變量指定一個修飾符。

1) « public » 修飾符。

可以從程序中的任何位置訪問標有public修飾符的變量、方法或類。這是最高程度的開放:沒有限制。

2) «私有»修飾符。

標有private修飾符的變量、方法或類只能在聲明它的類中訪問。標記的方法或變量對所有其他類都是隱藏的。這是最高級別的隱私:只有您的班級才能訪問。此類方法不會被繼承,也無法被覆蓋。此外,它們不能在後代類中訪問。

3)  «默認修飾符»。

如果一個變量或方法沒有用任何修飾符標記,那麼它被認為是用“default”修飾符標記的。具有此修飾符的變量和方法對聲明它們的包中的所有類可見,並且僅對這些類可見。此修飾符也稱為“”或“包私有”訪問,暗示對變量和方法的訪問對包含該類的整個包開放。

4) «受保護»修飾語。

此級別的訪問權限比package略寬。標有protected修飾符的變量、方法或類可以從其包(如“package”)和所有繼承的類中訪問。

這張表說明了一切:

可見性類型 關鍵詞 使用權
你的班 你的包裹 後裔 所有課程
私人的 私人的 是的
包裹 (無修飾符) 是的 是的
受保護 受保護 是的 是的 是的
民眾 民眾 是的 是的 是的 是的

有一種方法可以輕鬆記住這張表。想像一下,您正在寫一份遺囑。你把你所有的東西分成四類。誰可以使用你的東西?

誰有權訪問 修改器 例子
只有  私人的 個人日記
家庭 (無修飾符) 家庭照片
家庭和繼承人 受保護 家族莊園
大家 民眾 回憶錄

“這很像想像同一個包中的類是一個家庭的一部分。”

“我還想告訴你一些關於覆蓋方法的有趣細微差別。”

1) 抽象方法的隱式實現。

假設您有以下代碼:

代碼
class Cat
{
 public String getName()
 {
  return "Oscar";
 }
}

而你決定創建一個繼承這個類的Tiger類,並為新類添加一個接口

代碼
class Cat
{
 public String getName()
 {
   return "Oscar";
 }
}
interface HasName
{
 String getName();
 int getWeight();
}
class Tiger extends Cat implements HasName
{
 public int getWeight()
 {
  return 115;
 }

}

如果您只是實現 IntelliJ IDEA 告訴您實現的所有缺失方法,稍後您可能最終會花費很長時間來尋找錯誤。

原來Tiger類有一個繼承自Cat的getName方法,將作為HasName接口的getName方法的實現。

“我看不出有什麼可怕的。”

“這還不算太糟糕,它很可能是一個容易出現錯誤的地方。”

但情況可能更糟:

代碼
interface HasWeight
{
 int getValue();
}
interface HasSize
{
 int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
 public int getValue()
 {
  return 115;
 }
}

事實證明,您不能總是從多個接口繼承。更準確地說,你可以繼承它們,但你不能正確地實現它們。看例子。這兩個接口都要求您實現 getValue() 方法,但不清楚它應該返回什麼:重量還是尺寸?這是非常不愉快的處理。

“我同意。你想實現一個方法,但你不能。你已經從基類繼承了一個同名方法。它壞了。”

“不過有個好消息。”

2)擴大知名度。繼承類型時,可以擴展方法的可見性。這是它的樣子:

Java代碼 描述
class Cat
{
 protected String getName()
 {
  return "Oscar";
 }
}
class Tiger extends Cat
{
 public String getName()
 {
  return "Oscar Tiggerman";
 }
}
我們已將該方法的可見性從 擴展protectedpublic.
代碼 為什麼這是“合法的”
public static void main(String[] args)
{
 Cat cat = new Cat();
 cat.getName();
}
一切都很好。在這裡我們甚至不知道在子類中已經擴展了可見性。
public static void main(String[] args)
{
 Tiger tiger = new Tiger();
 tiger.getName();
}
這裡我們調用可見性被擴展的方法。

如果這不可能,我們總是可以在 Tiger 中聲明一個方法:
public String getPublicName()
{
super.getName(); //調用受保護的方法
}

換句話說,我們不是在談論任何安全違規行為。

public static void main(String[] args)
{
 Cat catTiger = new Tiger();
 catTiger.getName();
}
如果在基類 ( Cat )中調用方法所需的所有條件都得到滿足,那麼在後代類型 ( Tiger ) 中調用該方法肯定會滿足。因為對方法調用的限制很弱,而不是很強。

“我不確定我是否完全理解,但我會記住這是可能的。”

3) 縮小返回類型。

在重寫的方法中,我們可以將返回類型更改為窄引用類型。

Java代碼 描述
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;
 }
}
我們覆蓋了方法getMyParent,現在它返回一個Tiger對象。
代碼 為什麼這是“合法的”
public static void main(String[] args)
{
 Cat parent = new Cat();

 Cat me = new Cat();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
一切都很好。在這裡我們甚至不知道 getMyParent 方法的返回類型在子類中被拓寬了。

«舊代碼»是如何工作的。

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

 Tiger me = new Tiger();
 me.setMyParent(parent);
 Tiger myParent = me.getMyParent();
}
這裡我們調用返回類型被縮小的方法。

如果這不可能,我們總是可以在 Tiger 中聲明一個方法:
public Tiger getMyTigerParent()
{
return (Tiger) this.parent;
}

換句話說,不存在安全違規和/或類型轉換違規。

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

 Cat me = new Tiger();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
這裡一切正常,儘管我們將變量的類型擴展到基類 (Cat)。

由於覆蓋,調用了正確的 setMyParent 方法。

並且在調用 getMyParent 方法時沒有什麼可擔心的,因為返回值,雖然是 Tiger 類,仍然可以毫無問題地分配給基類(Cat)myParent 變量。

Tiger 對象可以安全地存儲在 Tiger 變量和 Cat 變量中。

“是的。明白了。當覆蓋方法時,如果我們將我們的對像傳遞給只能處理基類並且對我們的類一無所知的代碼,那麼您必須知道這一切是如何工作的。

“完全正確!那麼最大的問題是為什麼我們在覆蓋方法時不能縮小返回值的類型?”

“很明顯,在這種情況下,基類中的代碼將停止工作:”

Java代碼 問題的解釋
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";
 }
}
我們重載了 getMyParent 方法並縮小了其返回值的類型。

這裡一切都很好。

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

 Cat me = new Tiger();
 Cat myParent = me.getMyParent();
}
然後這段代碼將停止工作。

getMyParent 方法可以返回對象的任何實例,因為它實際上是在 Tiger 對像上調用的。

而且我們在分配之前沒有檢查。因此,Cat 類型的 myParent 變量完全有可能存儲一個 String 引用。

“很棒的例子,阿米戈!”

在 Java 中,在調用方法之前,不會檢查對像是否有這樣的方法。所有檢查都發生在運行時。對缺失方法的 [假設] 調用很可能會導致程序嘗試執行不存在的字節碼。這最終會導致致命錯誤,並且操作系統會強行關閉該程序。

“哇。現在我知道了。”