“接下来我会讲解“访问修饰符”。我以前也说过这个概念,但复习是学习的主要方法。
您可以控制其他类对类的方法和变量的访问权限(可见性)。访问修饰符回答“谁可以访问此方法/变量?”问题。你只能为每个方法或变量分配一个修饰符。
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 语言代码 | 说明 |
---|---|
|
|
|
将方法的可见性从 protected 扩展为 public 。 |
代码 | 为什么这是“合法的” |
---|---|
|
一切顺利。我们甚至都不知道可见性在后代类中得到了扩展。 |
|
我们在此调用其可见性得到扩展的方法。
如果无法调用,我们总是可以在 Tiger 中声明一个方法: 换句话说,我们不是在谈论任何安全违规行为。 |
|
如果满足调用基类 (Cat) 中方法的全部条件,那么它们肯定满足在后代类型 (Tiger) 上调用方法的条件。因为方法调用的限制并不是强制性的。 |
“我不确定我是不是全懂了,但我会记住这是可能的。”
3) 窄化返回类型。
在一种重写方法中,我们可以将返回类型更改为缩窄的引用类型。
Java 语言代码 | 说明 |
---|---|
|
|
|
我们重写了方法 getMyParent ,现在它返回一个 Tiger 对象。 |
代码 | 为什么这是“合法的” |
---|---|
|
一切顺利。在这里,我们甚至不了解 getMyParent 方法的返回类型在后代类中被拓宽了。
“旧代码”过去如何工作的,现在又如何运行。 |
|
我们在此调用其返回类型得到拓宽的方法。
如果无法调用,我们总是可以在 Tiger 中声明一个方法: 换句话说,不存在安全冲突和/或类型转换冲突。 |
|
尽管我们将变量的类型缩小为基类 (Cat),但是一切仍可正常运行。
由于重写,正确的 setMyParent 方法被调用。 完全不用担心何时调用 getMyParent 方法,因为返回值(尽管属于 Tiger 类)仍可以正确无误地赋给基类 (Cat) 的 myParent 变量。 Tiger 对象可以安全地存储在 Tiger 变量和 Cat 变量中。 |
“对。明白了。当重写方法时,如果我们将对象传递给只能处理基类并且对类一无所知的代码,则必须知道所有这些工作原理。”
“的确如此。那么最大的问题就是,为什么重写方法时不能缩小返回值的类型?”
“很明显,在这种情况下,基类中的代码将停止运行:”
Java 语言代码 | 问题说明 |
---|---|
|
|
|
重载 getMyParent 方法并缩小了返回值的类型。
一切都正常。 |
|
然后这个代码会停止运行。
getMyParent 方法会返回 Object 的任何实例,因为它实际上在 Tiger 对象上调用。 我们在赋值之前没进行检查。因此,Cat 类型 myParent 变量完全可能存储一个 String 引用。 |
“这个例子举得好,阿米戈!”
在 Java 中,调用方法之前,它都不会检查对象是否存在这样的方法。所有的检查都在运行时进行。对缺失方法的[假设]调用很可能导致程序尝试执行不存在的字节码。这会导致出现致命错误,而操作系统可能会强制关闭这个程序。
“哇!现在我懂了。”
GO TO FULL VERSION