java方法覆盖_Java 方法覆盖详解

原标题:Java 方法覆盖详解

编译:ImportNew - sinofalcon ,

请不必担心 Oracle职业认证(OCP)Java SE 7 程序员认证会如何用Java方法覆盖为难你。

http://education.oracle.com/pls/web_prod-plq-dad/db_pages.getpage?page_id=5001&get_params=p_exam_id:1Z0-804

本文摘自《OCP Java SE 7 程序员II认证指南》,内容涉及Java方法覆盖和虚拟调用,包括考试中可能遇到的陷阱和技巧。

e6503de62729c9678a0c33b77484cc63.png

你庆祝节日或好事的方式和父母一样吗?还是稍有不同?也许庆祝同样的节日或事件,会用自己独特的方式。类似的,类能够继承其他类的行为。但是它们也能够重新定义继承的行为——也称方法覆盖。

方法覆盖是面向对象编程语言的特征,它使派生类能够定义从基类集成的方法实现,以扩展自己的行为。派生类能够通过定义具有相同方法原型或方法名称、数量和参数类型的实例方法,覆盖实例基类中定义的方法。被覆盖的方法也与多态方法作用相同。基类的静态方法不能覆盖,但能够用相同的原型定义隐藏在派生类。

能被派生类覆盖的方法叫做虚拟方法。但是注意:Java 已经弃用此词,在 Java 词汇中没有“虚拟方法”一说。该词用在其它面向对象语言中,如 C 和C++。虚拟方法调用是指调用基于对象引用的类型正确地被覆盖方法,而不是调用对象引用本身。虚拟方法在运行时确定,而非在编译时。

OCPJava SE 7 程序员认证II考试会考查方法覆盖;方法覆盖的正确语法;重载、覆盖和隐藏方法之间的区别;运用覆盖方法时的一般错误;以及虚拟方法调用。让我们从方法覆盖开始。

注:基类方法指被覆盖的方法、派生类方法指覆盖方法。

覆盖方法的需求

我们继承父母的行为,但会重新定义某些继承行为以便适应我们自身需要。同样地,派生类能继承基类的行为和属性,但仍然有所差异——用自己的方式定义新的变量和方法。派生类也能通过覆盖来为基类定义不同的行为。这里举一个例子,Book类定义一个方法issueBook(),把天数作为方法的参数。

class Book {

void issueBook(int days) {

if (days > 0)

System.out.println("Book issued");

else

System.out.println("Cannot issue for 0 or less days");

}

}

接下来是另一个类,CourseBook,它继承了Book类。该类需要覆盖issueBook(),因为如果只是为了引用,那么就CourseBook不能发行。同样,CourseBook不能发行超过14天。我们来看看是怎样用覆盖issueBook()方法来完成。

class CourseBook extends Book {

boolean onlyForReference;

CourseBook(boolean val) {

onlyForReference = val;

}

@Override #1

void issueBook(int days) { #2

if (onlyForReference)

System.out.println("Reference book");

else

if (days < 14)

super.issueBook(days); #3

else

System.out.println("days >= 14");

}

}

#1 注解:@Override

#2 覆盖基类Book中的OverridesissueBook()

#3 调用Book中的issueBook()

(#1)处的代码用了注解 @Override,告知编译器该方法覆盖了基类的一个方法。尽管这个注释是非强制的,但如果你错误地覆盖一个方法,该注释会非常有用。(#2)定义issueBook()方法与类Book中相同的名字和方法参数。(#3)调用类Book中定义的issueBook()方法,然而,它不是强制的。要看派生类是否要执行基类中同样的代码。

注:每当你打算覆盖派生类中的方法时,请使用注释@Override。如果一个方法不能被覆盖或实际上在重载而不是覆盖一个方法,它就会给你警告。

下面的例子能够用于测试先前的代码:

class BookExample {

public static void main(String[] args) {

Book b = new CourseBook(true);

b.issueBook(100); #A

b = new CourseBook(false);

b.issueBook(100); #B

b = new Book(); #C

b.issueBook(100); #D

}

}

#A 输出 “Reference book”

#B 输出 “days >= 14”

#C b此时指向Book的一个实例

#D 输出 “Book issued”

图1 展现了类BookExample的编译和执行过程,第一步和第二步如下:

第一步:编译时在方法检查中使用引用类型。

第二步:运行时在方法调用中使用实例类型。

63be8d6e3f1fc9dbd7c06a7de8697b04.png

图1 为了编译b.issueBook(),编译器只指向类Book的定义。为了执行b.issueBook(),Java运行时环境( JRE )使用类CourseBook中的issueBook()实际实现的方法。

现在让我们探讨怎样在派生类中正确地覆盖基类方法。

方法覆盖的正确语法

我们以覆盖review方法为例,如下所示:

class Book {

synchronized protected List review(int id,

List names) throws Exception { #A

return null;

}

}

class CourseBook extends Book { #B

@Override

final public ArrayList review(int id,

List names) throws IOException { #C

return null;

}

}

#A 基类Book中的review方法

#B CourseBook继承了Book

#C 派生类CourseBook中被覆盖的方法review

图2显示了方法声明的构成:访问修饰符、非访问修饰符、返回类型、方法名称、参数列表,以及能抛出的异常的列表(方法声明与方法签名不同)。该图就基类Book中定义的review方法和类CourseBook覆盖方法review()各自标识的部分也进行了比较。

a94ecf62e28bdca18193baea11965fb0.png

图2 比较方法声明基类方法和覆盖方法的组件

表1:方法组件和覆盖方法可接受值的比较

99af7e9b4426dc8ccd06db79914ce90d.png

考点提示:表1所列关于覆盖方法异常的规则只应用于检查异常。覆盖方法能抛出未检查的异常(运行时异常或错误),即使覆盖方法没有抛出。未检查的异常不是方法原型的部分,编译器不负责检查。

第6章包括对覆盖和覆盖方法排除异常详细的解释。我们来过一下几个重要并且很有可能出现在考题中的无效代码组合。

注:尽管这是最好的练习,我故意没有在方法覆盖的定义前面加上注解@Override,因为你可能不会在考试中碰到。

访问修饰符

派生类能分配同样的或更多的访问权限,但不能分配派生类中覆盖方法更小的访问权限:

class Book {

protected void review(int id, List names) {}

}

class CourseBook extends Book {

void review(int id, List names) {} #A

}

#A不能编译;派生类覆盖方法不能用更小的访问权限

非访问修饰符

派生类不能覆盖标记为final的基类方法。

class Book {

final void review(int id, List names) {}

}

class CourseBook extends Book {

void review(int id, List names) {} #A

}

#A 不能编译;标记了final的方法不能覆盖

参量列表和协变量返回类型

覆盖方法范围子类被覆盖方法返回类型,叫协变量返回类型。覆盖方法、基类和派生类中方法的参数列表必须完全一样。如果试着在参量列表中用协变量类型,你将会重载方法而非覆盖它们。例如:

class Book {

void review(int id, List names) throws Exception { #1

System.out.println("Base:review");

}

}

class CourseBook extends Book {

void review(int id, ArrayList names) throws IOException { #A

System.out.println("Derived:review");

}

}

#1 参数list—int和List

#A 参数list—int和ArrayList

(#1)基类 Book 中review()收到一个类型列表List对象。派生类CourseBook中review()方法收到一个子类型列表(ArrayList事项实现了 List)。这些方法没有被覆盖——它们被重载了:

class Verify {

public static void main(String[] args)throws Exception {

Book book = new CourseBook(); #1

book.review(1, null); #A

}

}

#1 引用变量指向CourseBook目标

#A 调用Book的review方法;输出“Base:review”

(#1)处的代码 Book 的引用变量指向CourseBook对象。编译过程从基类 Book 分配review()方法执行到引用变量book。因为类CourseBook中review方法没有覆盖类Book中review方法,至于是调用类Book中review()方法还是类CourseBook中review()方法,JRE 不会犯丁点迷糊。它会直接调用类Book中review()方法。

考点提示:引用变量类型,重载方法选择。这个选择在编译时间做出。

抛出异常

重载方法必须声明不抛出异常、相同异常或基类声明的子类型异常或编译失败。然而,该规则不应用于错误类或运行时异常。例如:

class Book {

void review() throws Exception {}

void read() throws Exception {}

void close() throws Exception {}

void write() throws NullPointerException {}

void skip() throws IOException {}

void modify() {}

}

class CourseBook extends Book {

void review() {} #A

void read() throws IOException {} #B

void close() throws Error {} #C

void write() throws RuntimeException {} #D

void skip() throws Exception {} #E

void modify() throws IOException {} #F

}

#A 编译通过;声明不抛出异常。

#B 编译通过;声明抛出IO异常(一个子类异常)。

#C 编译通过;覆盖方法能声明抛出任何错误。

#D 编译通过;覆盖方法能声明抛出任何运行时异常。

#E 编译失败;声明抛出异常(IO异常的超类)。覆盖方法不能声明抛出比被覆盖方法更多的异常。

#F 编译失败;声明抛出IO异常。覆盖方法不能声明抛出检查异常,如果被覆盖方法没有声明。

考点提示:覆盖方法能声明抛出运行时异常或错误,即使被覆盖类没有声明。

为了记住先前的知识点,我们来用怪物与异常作类比。图3展现了有趣的记忆方法,当被覆盖方法不声明抛出checked异常和声明抛出时,覆盖方法能够列出的异常(怪物)。

665eb12932c51d6883f959b7635d17b4.png

图3 异常与怪物对比。当覆盖方法声明抛出受检异常(怪物),覆盖方法能声明抛出none、同样的异常或级别较低的受检异常。覆盖方法能声明抛出任何错误或运行时异常。

可以覆盖或虚拟调用基类的全部方法吗?

简单的回答是不行。你只能覆盖基类的以下方法:

方法访问派生类

非静态基类方法

可访问基类的方法

派生类中方法的可访问性依赖访问修饰符。例如,基类定义的私有方法不能被派生类使用。同样,基类默认访问方法不能被另一个包中的派生类使用。一个类不能覆盖不能访问的方法。

只有非静态方法能够被覆盖

如果派生类定义一个与基类中相同名字和原型的静态方法,它将基类方法隐藏起来,并且不覆盖它。你不能覆盖静态方法。例如:

class Book {

static void printName() { #A

System.out.println("Book"); #A

} #A

}

class CourseBook extends Book {

static void printName() { #B

System.out.println("CourseBook"); #B

} #B

}

#A 基类中的静态方法

#B 派生类中的静态方法

类CourseBook的printName()方法隐藏了类Book 的printName()方法中。没有覆盖它。因为静态方法固定在编译时,调用哪个printName()方法要看引用变量的类型。

class BookExampleStaticMethod {

public static void main(String[] args) {

Book base = new Book();

base.printName(); #A

Book derived = new CourseBook();

derived.printName(); #B

}

}

#A 输出“Book”

#B 输出“Book”

区分方法覆盖、重载和隐藏

方法覆盖、重载和隐藏容易混淆。图4对这些方法在Book和CourseBook类进行了区分。左侧是类定义,右侧是UML图。

68e40ac80b7842fca8e8a86ffc51fca1.png

图4中辨析基类和派生类中的方法覆盖、方法重载和方法隐藏。

考点提示:当一个类集成另一个类时,它能够重载、覆盖或隐藏基类的方法。类不能覆盖或隐藏自己的方法——只能重载自己的方法。

派生类覆盖或隐藏基类中的静态或非静态方法,下面我们用“Twist in the Tale”练习来检查派生类中定义静态或非静态方法的正确代码(练习答案列在文末)。

“Twist in the Tale”练习简要说明

每章(本文摘录出处)包含若干“Twist in the Tale”练习。为了这些练习,我试着修改已经包含在章节中的例子,标题“Twist in the Tale”的意思是指经过修改或改进的代码。这些练习强调了如何通过细微的调整改变代码的行为,一定会激励你在考试中认真检查所有代码。

我收录这些练习的主要理由是,在真正的考试中你可能会被要求回答几个看起来一模一样问题。但仔细检查,你会发现这些问题之间差别细微,正是这些差异改变了代码行为和正确答案项。

Twist in the Tale

我们来修改类 Book 和CourseBook的代码,并在两个类中定义多组静态和非静态方法print()如下:

(a)

class Book{

static void print(){}

}

class CourseBook extends Book{

static void print(){}

}

(b)

class Book{

static void print(){}

}

class CourseBook extends Book{

void print(){}

}

(c)

class Book{

void print(){}

}

class CourseBook extends Book{

static void print(){}

}

(d)

class Book{

void print(){}

}

class CourseBook extends Book{

void print(){}

}

你的任务是选取其中一个,然后在系统中进行编译,看看是否正确。在实际考试中,你需要验证(没有编译器)代码片段是否能够编译通过:

覆盖 print 方法

隐藏 print方法

编译错误

可以覆盖或虚拟调用基类构造器吗?

简单的回答是“不”。构造器不能被派生类继承。因为只有被继承的方法能被覆盖,所以构造器不能被派生类覆盖。如果考题要你覆盖基类构造器,这是在给你下套,你懂的。

考点提示:构造器不能被覆盖,因为基类构造器不能被派生类继承。

“Twist in the Tale”答案

目的:区分重载、覆盖和隐藏方法。

答案解析:(a)编译成功。类CourseBook的静态方法print()隐藏了基类Book中静态方法print()。

class Book {

static void print() {}

}

class CourseBook extends Book {

static void print() {}

}

实例方法能够覆盖基类的方法,但静态方法不能这么做。当派生类定义一个与基类具有相同原型的静态方法,它会将其隐藏。静态方法不具有多态性。

(b)不能编译。基类Book的静态方法print()不能被派生类CourseBook中的实例方法print()隐藏。

class Book {

static void print() {}

}

class CourseBook extends Book {

void print() {}

}

(c)无法编译。基类 Book 中的实例方法print()不能被派生类CourseBook静态方法print()覆盖。

class Book{

void print() {}

}

class CourseBook extends Book {

static void print() {}

}

(d)编译成功。类CourseBook中的实例方法print()覆盖了基类 Book 中的实例方法print():

class Book{

void print() {}

}

class CourseBook extends Book {

void print() {}

}

祝你好运,成功取得认证!

看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值