“接下来我会讲解“访问修饰符”。我以前也说过这个概念,但复习是学习的主要方法。

您可以控制其他类对类的方法和变量的访问权限(可见性)。访问修饰符回答“谁可以访问此方法/变量?”问题。你只能为每个方法或变量分配一个修饰符。

1)public”修饰符。

标记 public 修饰符的变量、方法或类可从程序中的任意位置访问。这是最广泛的访问级别:不存在任何限制。

2)private”修饰符。

标记 private 修饰符的变量、方法或类只能在其被声明的类中访问。这个标记的方法或变量对所有其他类都不可见。这是最高的隐私级别:只有你的类可以访问。这样的方法不会被继承,也无法被重写。此外,它们还不能在后代类中访问。

3) “Default”修饰符。

如果变量或方法未标有任何修饰符,则被视为标有“default”访问修饰符。带此修饰符的变量和方法对于声明它们的包中的所有类都是可见的,也只对这些类可见。这个修饰符也被称为“package”或“package private”访问,暗示包含类的整个包访问变量和方法。

4)protected”修饰符。

这是比 package 稍微广泛一点的访问级别。标记 protected 修饰符的变量、方法或类可从包以及所有继承的类访问。

该表解释了一切:

可见性类型 关键字 访问权限
你的类 你的包 后代 所有类
Private private
Package (无修饰符)
Protected protected
Public public

有一种方法可轻松记住这张表。想象一下你在写遗嘱。你将所有事物分为四类。谁可以使用你的东西?

谁有访问权限 修饰符 示例
仅 自己 private 个人日志
家人 (无修饰符) 家庭相片
家人及后代 protected 家庭房地产
每个人 public 回忆录

“它就像一个家庭中同一个包内的这些类。”

“我还想告诉你关于重写方法的细微差别。”

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";
 }
}
将方法的可见性从 protected 扩展为 public
代码 为什么这是“合法的”
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(); //调用 protected 方法
}

换句话说,我们不是在谈论任何安全违规行为。

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 方法会返回 Object 的任何实例,因为它实际上在 Tiger 对象上调用。

我们在赋值之前没进行检查。因此,Cat 类型 myParent 变量完全可能存储一个 String 引用。

“这个例子举得好,阿米戈!”

在 Java 中,调用方法之前,它都不会检查对象是否存在这样的方法。所有的检查都在运行时进行。对缺失方法的[假设]调用很可能导致程序尝试执行不存在的字节码。这会导致出现致命错误,而操作系统可能会强制关闭这个程序。

“哇!现在我懂了。”