CodeGym /课程 /Java 核心 /访问修饰符、方法重写和实现抽象方法

访问修饰符、方法重写和实现抽象方法

Java 核心
第 5 级, 课程 1
可用

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

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

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

“哇!现在我懂了。”

评论 (25)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
哄哄 级别 1,Nanping,China
7 十二月 2024
越来越难了 朋友们 , 要一直做题不然都没暗物质了
zzzz 级别 3,China,China
18 五月 2024
在Java中,当你重写一个方法时,子类方法的返回值类型可以是被重写方法(父类中的方法)的返回值类型的子类型。这就意味着,你可以窄化返回值类型,但不能拓宽它。这种特性被称为协变返回类型。 例如,如果父类的方法返回一个Object类型的值,子类重写该方法时可以返回Object的任何子类,比如String。但是,如果父类的方法返回一个String,子类就不能重写该方法并返回Object,因为这将是一种拓宽返回值类型的行为。
云中桥 级别 41,China,China
25 五月 2023
day7
章 级别 28,China,Hong Kong
24 二月 2023
mark
远方 级别 15,United States of America,United States
18 七月 2022
“想象一下你在写遗嘱”,我可真是谢谢你哈哈哈
Y.F Fang 级别 15,Xian,China
17 七月 2022
我对重写时,返回值类型的转换要求这么理解: 方法重写过程里,返回值的类型是否合法和是否符合前头的父类,子类转化规则有关, 1、返回值类型可以沿着子类的方向,也就是窄化。因为在返回的时候,可以保证接受变量和返回值之间,要么类型同,要么有父子关系。 2、返回值不允许沿着父类方向转化,因为一个父类可能被很多子类继承。极端一点——返回值设置为object类,而它的子类有string, 子类A,两者无关。一个A类变量在接受object类返回值后,有可能接收到一个和自己没有关系的string类,从而导致错误。
阿狼 级别 32,Zhengzhou,China
16 六月 2022
day 13
H 级别 23,China,China
21 三月 2022
这翻译的真得好难让人理解
老楊-新思維IT教育 级别 2,Japan
17 三月 2022
多态有两条线,一条是往父类方向的,一条是往子类方向的。 往父类方向的被称为向上转型,也就是父类通过子类完成实例化操作,对象为父类,而赋值的内容为new的子类。 往子类方向的被称为向下转型,也就是子类通过父类完成实例化操作,对象为子类,但是此时没有new的问题,因为父类必须通过子类完成了向上转型之后才能向下转型,因此,此处赋值的内容为向上转型完毕的父类对象。 子承父业是一条顺时针的线,是大的接收小的范围,就像long接收int一样,所以不需要做任何的处理,只要父类通过子类实例化即可 表达式:A a = new B(); 父承子业是一条逆时针的线,是小的接收大的范围,就像int接收long一样,因此需要强制转换父类对象为子类对象。 表达式:B b = (B)a;
a_a 级别 22
3 三月 2022
如果赋值操作使我们在继承链中向上移动(靠近 Object 类),那么我们正在处理拓宽转换(也称为向上转换)。如果我们沿着对象类型的链向下移动,那么这就是窄化转换(也称为向下转换)。
a_a 级别 22
3 三月 2022
这课程是不是自相矛盾,还是翻译问题
枫行 级别 41,China,China
1 十月 2023
这句话没有任何问题啊
Qin-1999 级别 22
12 一月 2024
向上是拓宽,向下是窄化