码出高效:Java开发手册-第2章(9)

简介: 本章开始讲解面向对象思想,并以Java 为载体讲述面向对象思想在具体编程语言中的运用与实践。当前主流的编程语言有50 种左右,主要分为两大阵营:面向对象编程与面向过程编程。面向对象编程(Object-Oriented Programming,OOP)是划时代的编程思想变革,推动了高级语言的快速发展和工业化进程。OOP 的抽象、封装、继承、多态的理念使软件大规模化成为可能,有效地降低了软件开发成本、维护成本和复用成本。面向对象编程思想完全不同于传统的面向过程编程思想,使大型软件的开发就像搭积木一样隔离可控、高效简单,是当今编程领域的一股势不可......

2.4.7 覆写

      多态中的override,本书翻译成覆写。如果翻译成重写,那么与重构意思过于接近;如果翻译成覆盖,那么少了“写”这个核心动词。如果父类定义的方法达不到子类的期望,那么子类可以重新实现方法覆盖父类的实现。因为有些子类是延迟加载的,甚至是网络加载的,所以最终的实现需要在运行期判断,这就是所谓的动态绑定。动态绑定是多态性得以实现的重要因素,元空间有一个方法表保存着每个可以实例化类的方法信息,JVM 可以通过方法表快速地激活实例方法。如果某个类覆写了父类的某个方法,则方法表中的方法指向引用会指向子类的实现处。代码通常是用这样的方式来调用子类的方法,通常这也被称作向上转型:

Father father = new Son();

// Son 覆写了此方法

father.doSomething();

      向上转型时,通过父类引用执行子类方法时需要注意以下两点:

(1)无法调用到子类中存在而父类本身不存在的方法。

      (2)可以调用到子类中覆写了父类的方法,这是一种多态实现。

      想成功地覆写父类方法,需要满足以下4 个条件:

(1)访问权限不能变小。访问控制权限变小意味着在调用时父类的可见方法无法被子类多态执行,比如父类中方法是用public 修饰的,子类覆写时变成private。设想如果编译器为多态开了后门,让在父类定义中可见的方法随着父类调用链路下来,执行了子类更小权限的方法,则破坏了封装。如下代码所示,在实际编码中不允许将方法访问权限缩小:

class Father {

      public void method() {

             System.out.println("Father's method");

      }

}

class Son extends Father {

      // 编译报错,不允许修改为访问权限更严格的修饰符

      @override

      private void method() {

             System.out.println("Son's method");

      }

}

      (2)返回类型能够向上转型成为父类的返回类型。虽然方法返回值不是方法签名的一部分,但是在覆写时,父类的方法表指向了子类实现方法,编译器会检查返回值是否向上兼容。注意,这里的向上转型必须是严格的继承关系,数据类型基本不存在通过继承向上转型的问题。比如int 与Integer 是非兼容返回类型,不会自动装箱。再比如,如果子类方法返回int,而父类方法返回long,虽然数据表示范围更大,但是它们之间没有继承关系。返回类型是Object 的方法,能够兼容任何对象,包括class、enum、interface 等类型。

(3)异常也要能向上转型成为父类的异常。异常分为checked 和unchecked 两种类型。如果父类抛出一个checked 异常,则子类只能抛出此异常或此异常的子类。而unchecked 异常不用显式地向上抛出,所以没有任何兼容问题。

(4)方法名、参数类型及个数必须严格一致。为了使编译器准确地判断是否是覆写行为,所有的覆写方法必须加@Override 注解。此时编译器会自动检查覆写方法签名是否一致,避免了覆写时因写错方法名或方法参数而导致覆写失败。例如,AbstractCollection 的clear 方法,当覆写此方法时,写成c1ear,注意是数字的1,这会导致定义了两个不同的方法。此外,@Override 注解还可以避免因权限控制可见范围导致的覆写失败。如图2-7 所示,Father 和Son 属于不同的包,它们的method() 方法无权限控制符修饰,是默认仅包内可见的。Father 的method 的方法在Son 中是不可见的。所以,Son 中定义的method 方法是一个“新方法”,如果加上@Override,则会提示:Method does not override method from its superclass。

4.jpg

图2-7 Father 和Son 的覆写关系

      综上所述,方法的覆写可以总结成容易记忆的口诀:“一大两小两同”。

  •       一大:子类的方法访问权限控制符只能相同或变大。
  •       两小:抛出异常和返回值只能变小,能够转型成父类对象。子类的返回值、抛出异常类型必须与父类的返回值、抛出异常类型存在继承关系。
  •       两同:方法名和参数必须完全相同。

      根据这个原则,再看一个编译和运行都正确的覆写示例代码:

class Father {

      protected Number doSomething(int a, Integer b, Object c) throws

             SQLException {

             System.out.println("Father's doSomething");

             return new Integer(7);

      }

}

class Son extends Father {

      /**

       * 1. 权限扩大,由protected 到public(一大)

       * 2. 返回值是父类的Number 的子类 (两小)

       * 3. 抛出异常是SQLException 的子类

       * 4. 方法名必须严格一致 (两同)

       * 5. 参数类型与个数必须严格一致

       * 6. 必须加@Override

       */

      @Override

      public Integer doSomething(int a, Integer b, Object c) throws

             SQLClientInfoException {

             if(a == 0) {

                    throw new SQLClientInfoException();

             }

             return new Integer(17);

      }

}

      覆写只能针对非静态、非final、非构造方法。由于静态方法属于类,如果父类和子类存在同名静态方法,那么两者都可以被正常调用。如果方法有final 修饰,则表示此方法不可被覆写。

      如果想在子类覆写的方法中调用父类方法,则可以使用super 关键字。在上述示例代码中,在Son 的doSomething 方法体里可以使用super.doSomething(a,b,c) 调用父类方法。如果与此同时在父类方法的代码中写一句this.doSomething(),会得出什么样的运行结果呢?

public class Father {

      protected void doSomething() {

             System.out.println("Father's doSomething");

             this.doSomething();

      }

      public static void main(String[] args) {

             Father father = new Son();

             father.doSomething();

      }

}

class Son extends Father {

      @Override

      public void doSomething() {

             System.out.println("Son's doSomething");

             super.doSomething();

      }

}

      在经过了一系列的父子方法循环调用后,JVM 崩溃了,发生了StackOverflowError,如图2-8所示。

5.jpg

图2-8 覆写产生的StackOverflowError

相关文章
|
人工智能 前端开发 Java
2025年WebStorm高效Java开发全指南:从配置到实战
WebStorm 2025不仅是一款强大的JavaScript IDE,也全面支持Java开发。本文详解其AI辅助编程、Java特性增强及性能优化,并提供环境配置、高效开发技巧与实战案例,助你打造流畅的全栈开发体验。
978 4
|
前端开发 JavaScript Java
Java 开发中 Swing 界面嵌入浏览器实现方法详解
摘要:Java中嵌入浏览器可通过多种技术实现:1) JCEF框架利用Chromium内核,适合复杂网页;2) JEditorPane组件支持简单HTML显示,但功能有限;3) DJNativeSwing-SWT可内嵌浏览器,需特定内核支持;4) JavaFX WebView结合Swing可完美支持现代网页技术。每种方案各有特点,开发者需根据项目需求选择合适方法,如JCEF适合高性能要求,JEditorPane适合简单展示。(149字)
1470 1
|
10月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
安全 Java 领域建模
Java 17 探秘:不容错过的现代开发利器
Java 17 探秘:不容错过的现代开发利器
1292 0
|
11月前
|
消息中间件 人工智能 Java
抖音微信爆款小游戏大全:免费休闲/竞技/益智/PHP+Java全筏开源开发
本文基于2025年最新行业数据,深入解析抖音/微信爆款小游戏的开发逻辑,重点讲解PHP+Java双引擎架构实战,涵盖技术选型、架构设计、性能优化与开源生态,提供完整开源工具链,助力开发者从理论到落地打造高留存、高并发的小游戏产品。
|
JavaScript 安全 前端开发
Java开发:最新技术驱动的病人挂号系统实操指南与全流程操作技巧汇总
本文介绍基于Spring Boot 3.x、Vue 3等最新技术构建现代化病人挂号系统,涵盖技术选型、核心功能实现与部署方案,助力开发者快速搭建高效、安全的医疗挂号平台。
504 3
|
安全 Java 数据库
Java 项目实战病人挂号系统网站设计开发步骤及核心功能实现指南
本文介绍了基于Java的病人挂号系统网站的技术方案与应用实例,涵盖SSM与Spring Boot框架选型、数据库设计、功能模块划分及安全机制实现。系统支持患者在线注册、登录、挂号与预约,管理员可进行医院信息与排班管理。通过实际案例展示系统开发流程与核心代码实现,为Java Web医疗项目开发提供参考。
521 2
|
11月前
|
存储 Java 关系型数据库
Java 项目实战基于面向对象思想的汽车租赁系统开发实例 汽车租赁系统 Java 面向对象项目实战
本文介绍基于Java面向对象编程的汽车租赁系统技术方案与应用实例,涵盖系统功能需求分析、类设计、数据库设计及具体代码实现,帮助开发者掌握Java在实际项目中的应用。
412 0