广州番禺Python, Java小班周末班培训
第四期线下Java, Python小班周末班已经开课了,培训的课程有Python爬虫,Python后端开发,Python办公自动化,Python大数据分析, Java中高级工程师培训。授课详情请点击:http://chipscoco.com/?cate=6
8.2.1 方法的重写
重写是指子类对从父类继承下来的方法,使用不同的逻辑来重新实现。问题来了,为什么子类需要重写父类的方法?因为从父类继承的方法无法满足子类的需求,所以才要修改。举个不甚恰当的例子,据说狗这种生物天生有一个迷惑行为,好学猫抓老鼠,是谓狗拿耗子,但是子类柯基犬居然不再狗拿耗子了,喜欢狗拿萝莉。现在将这个例子用Java语言实现出来:
class Dog{ public void catch_(){ System.out.println("狗拿耗子,多管闲事"); } } class Corgi extends Dog{ // 重写继承而来的catch_方法,柯基犬已经厌恶了学猫抓耗子 public void catch_(){ System.out.println("勾搭萝莉,成为舔狗"); } } public class HelloJava{ public static void main(String[] args) { Dog busyDog = new Dog(); busyDog.catch_(); Dog corgi = new Corgi(); corgi.catch_(); } }
在以上代码中,子类Corgi的catch_方法就是对父类Dog方法的重写。读者在理解了何为重写以后,还需掌握Java中的方法重写规则:
(1) 子类方法的函数名,参数列表,返回值类型须与父类方法一致。
(2) 子类方法的访问权限须大于等于父类方法的访问权限
(3) 只能重写继承而来的对象方法,所以类方法和private方法均无法被重写
以上三条即为Java方法重写的主要规则,在后面讲解异常时,会再添加一条与异常相关的方法重写规则。读者在重写父类方法时,还可以利用Java提供的@Override注解来标记当前的方法是重写方法。加上这个注解一来可以提升代码的可读性,一来Java编译器在扫描到这个注解时,会检查该方法是否是重写方法。如果发现其父类中并没有该方法时,会编译报错。现在为Corgi类的catch_方法添加@Override注解:
class Dog{ public void catch_(){ System.out.println("狗拿耗子,多管闲事"); } } class Corgi extends Dog{ // 重写继承而来的catch_方法,柯基犬已经厌恶了狗拿耗子 @Override public void catch_(){ System.out.println("勾搭萝莉,成为舔狗"); } } public class HelloJava{ public static void main(String[] args) { Dog busyDog = new Dog(); busyDog.catch_(); Dog corgi = new Corgi(); corgi.catch_(); } }
8.2.2 方法的重载
重载与重写虽只有一字之隔,但含义和使用场景却大不相同。重写是指重新实现从父类继承下来的实例方法,参数列表和返回值类型不能改变。重载也是对同一个方法进行重新实现,但必须使用不同的参数类型和参数个数,即一个类中同时存在多个同名方法,但是参数类型和个数不一样。现在为Corgi类重载catch_方法:
class Dog{ public void catch_(){ System.out.println("狗拿耗子,多管闲事"); } } class Corgi extends Dog{ // 重写继承而来的catch_方法,柯基犬已经厌恶了狗拿耗子 @Override public void catch_(){ System.out.println("勾搭萝莉,成为舔狗"); } public void catch_(boolean isAMouse){ if ( isAMouse ) { System.out.println("拒绝狗拿耗子"); } else { System.out.println("不再狗拿耗子,做一只懒萌的柯基犬"); } } } public class HelloJava{ public static void main(String[] args) { Corgi corgi = new Corgi(); corgi.catch_(); corgi.catch_(false); } }
在以上代码中,先是重写继承下来的catch_方法,再为重写后的catch_方法重载一个不同的方法,在该重载方法中定义了一个布尔类型的参数,Java在编译Java程序时,会根据方法的参数类型和参数个数来将方法标记为重载方法。现在读者来思考一个问题,为什么需要对方法进行重载?方法的重写比较好理解,因为父类提供的方法无法满足子类的需求。那么重载呢?重载用来对已有的方法进行扩展,而不会侵入原有的代码。
举个简单的例子,我们一开始实现了一个简单的播放器,播放器对外提供一个公有的play方法,play方法只能播放MP4格式的视频。随着项目的迭代,现在要求播放器能播放FLV格式的视频。有两种扩展方案,一种是直接修改原有的play方法,在方法内部再增加一个判断逻辑。一种是再提供一个新的方法。采用第一种方案会侵入原有的代码,影响系统的稳定性。
为什么会影响系统的稳定性?你一旦修改了原有的方法,那么系统中所有依赖于原有方法的代码都需要做好充分的测试,一旦测试不足,可能会引入一些潜在的bug。采用第二种方案时可以重新命名一个新的方法,比如play_flv, 但是这样做会造成接口冗余。
面向对象程序设计非常注重抽象思维,不论是play方法还是ply_flv方法,其本质都是实现播放的功能,我们应该将接口名统一起来,通过参数类型和个数来区分不同的行为。此时就应该对play方法进行重载,这样既能保持接口统一,又不会侵入原有的代码。
同学们在学习方法的重载时,还需掌握重载方法的匹配规则。请读者看以下三个重载方法:
class Statistics{ public static int sum(int a, int b){ return a+b; } public static double sum(int a, double b){ return a+b; } public static double sum(double a, double b){ return a+b; } }
Java在匹配重载方法时,优先匹配参数个数一致的函数,如果参数个数不一致,Java会立即抛出错误异常。如果参数个数一致,再寻找参数类型完全匹配的方法。如果参数类型不一致,则继续寻找参数能隐式转换为目标参数类型的重载方法。这里的隐式转换是指小范围的数据类型向上提升为范围更大的数据类型,匹配的策略采取就近原则进行匹配。在以上三个重载方法中,如果按以下方式来调用sum方法:
class Statistics{ public static int sum(int a, int b){ return a+b; } public static double sum(int a, double b){ return a+b; } public static double sum(double a, double b){ return a+b; } } public class HelloJava{ public static void main(String[] args) { int a = 1; int b = 1; int value = Statistics.sum(a, b); System.out.println(value); } }
则匹配到的重载方法是public static int sum(int a, int b);因为参数的个数和类型完全匹配。如果调用sum函数时,传递的参数为char类型,则优先匹配的仍为public static int sum(int a, int b):
class Statistics{ public static int sum(int a, int b){ return a+b; } public static double sum(int a, double b){ return a+b; } public static double sum(double a, double b){ return a+b; } } public class HelloJava{ public static void main(String[] args) { char a = 1; char b = 1; int value = Statistics.sum(a, b); System.out.println(value); } }
因为char类型会隐式提升为int类型,所以优先匹配。如果将方法public static int sum(int a, int b)注释掉,那么匹配到的是哪个方法?请读者分析以下代码实例:
class Statistics{ /*public static int sum(int a, int b){ return a+b; } */ public static double sum(int a, double b){ return a+b; } public static double sum(double a, double b){ return a+b; } } public class HelloJava{ public static void main(String[] args) { char a = 1; char b = 1; double value = Statistics.sum(a, b); System.out.println(value); } }
此时匹配到的重载方法是public static int sum(int a, double b); 因为第一个实参a按就近原则隐式提升为int类型,第二个实参按就近原则则提升为double类型。
8.2.3 修改与扩展
一些初学者经常将修改和扩展搞混,在学习面向对象的程序设计时,有必要将这两个基础概念区分开来:修改侧重于修改原有的实现逻辑,而扩展则侧重于在原有的基础上添加新的功能。
设计模式有一个很重要的原则:开闭原则。开闭即对扩展开放,对修改关闭。之所以要对修改关闭,是因为业已稳定的系统与原有的方法实现构成直接或间接的依赖,一旦修改原有的实现,会影响系统的稳定性和可复用性。
初学者在学习面向对象的程序设计时,也需逐渐熟悉并掌握这样的设计原则。在开发软件项目时,不要着急写代码,先构思一个总体的设计框架,在扩展新的功能时,要思考下究竟是直接修改原有的代码呢,还是在不影响原有代码的基础上来进行扩展,这样方可写出高质量高复用的代码。
成为架构师不是一蹴而就的,初学者可先从细微处入手,比如在定义类和方法时,多思考一些设计上的问题(高复用,可扩展,少冗余,可维护)。打算深入学习面向对象的同学,可系统学习设计模式。
8.2.4 课后习题
(1) 何为方法的重写?为什么要对方法进行重写?
(2) 写一个方法重写的代码实例,以回答你为什么要对方法进行重写。
(3) 什么是方法的重载?可以对重写后的方法进行重载吗?
(4) 简述重写与重载的区别。
(5) 定义一个编程语言类,要求至少设计三个重载方法。
(6) 请用一句话概括修改和扩展之间的联系。