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

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

模块 2:Java 核心
第 2 级 , 课程 2
可用

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

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

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
哄哄 级别 17,Nanping,China
7 十二月 2024
越来越难了 朋友们 , 要一直做题不然都没暗物质了
zzzz 级别 25,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
向上是拓宽,向下是窄化