目录
编辑
宝子们,今天咱来好好唠唠 Java 中一个超重要的概念 —— 方法重写。这玩意儿在 Java 的面向对象编程里可是起着关键作用,能让我们的代码更加灵活、可扩展,就像是给汽车换上不同的轮胎,让它能适应各种路况。不过,要真正掌握方法重写,得从一些基础概念慢慢说起。
一、方法重写是啥玩意儿
(一)定义和概念
想象一下,你有一个类,就好比是一个汽车的蓝图。这个类里有一个方法,比如是 move
方法,代表汽车怎么移动。现在,你又有一个子类,这个子类就像是基于原来汽车蓝图改进的新车型。子类可以对从父类继承来的 move
方法进行重新定义,让它的移动方式更符合这个新车型的特点,这就是方法重写。
哈哈,是不是不怎么好理解?那用专业点的话说,方法重写就是在子类中定义一个与父类中具有相同方法签名(方法名、参数列表和返回类型都相同,不过返回类型在一些情况下可以是协变的,后面会讲到)的方法,这样当通过子类对象调用这个方法时,执行的就是子类中重写后的方法,而不是父类中的方法。
(二)为啥要方法重写
- 增强功能:比如说,父类的
draw
方法只是简单地画一个图形的轮廓,而子类继承后,想要画一个填充了颜色的图形,就可以重写draw
方法,在原来画轮廓的基础上添加填充颜色的代码,这样就增强了draw
方法的功能,让子类的对象能展现出更丰富的行为。- 适应不同需求:以动物类为例,父类有一个
makeSound
方法,对于不同的子类,比如狗类和猫类,它们发出的声音是不一样的。狗是 “汪汪” 叫,猫是 “喵喵” 叫。通过在狗类和猫类中重写makeSound
方法,就能让每个子类的对象发出符合自身特点的声音,从而适应了不同动物的行为需求。
二、方法重写的规则
(一)方法签名必须相同
- 方法名一致:这很好理解,就像你要重写一个
openDoor
方法,在子类里也得叫openDoor
,不能改成别的名字,不然 Java 就不知道你是在重写父类的方法了。- 参数列表相同:参数的数量、类型和顺序都得一模一样。比如说父类的
calculate
方法接受两个int
类型的参数,子类重写的calculate
方法也得接受两个int
类型的参数,不能多一个少一个,也不能把int
换成其他类型,否则就不是方法重写,而是一个新的方法重载(这是另外一个概念,宝子们别搞混了哦)。(二)返回类型的要求
- 基本数据类型和无返回值:如果父类方法的返回类型是基本数据类型,比如
int
、double
等,或者是void
(无返回值),那么子类重写的方法返回类型必须和父类完全一致。例如,父类的getCount
方法返回int
类型,子类重写的getCount
方法也必须返回int
类型。- 引用数据类型的协变返回类型:如果父类方法的返回类型是一个引用数据类型,比如一个类或者接口,那么子类重写的方法返回类型可以是这个父类返回类型的子类。这就叫做协变返回类型。比如说,父类的
createShape
方法返回Shape
类型,子类重写这个方法时,可以返回Circle
类型(假设Circle
是Shape
的子类),这样做是合法的,而且更加灵活,能让子类返回更具体的类型,同时又保证了和父类方法的兼容性。
(三)访问权限的限制
子类重写方法的访问权限不能比父类中被重写的方法更严格。也就是说,如果父类的方法是 public
的,子类重写的方法可以是 public
或者 protected
(但一般不建议把 public
改成 protected
,除非有特殊的设计需求),但不能是 private
或者默认(包访问权限),因为这样会导致子类的方法无法在父类方法可访问的范围内被调用,就违背了方法重写的初衷。例如,父类的 display
方法是 public
的,子类重写的 display
方法可以是 public
,但如果写成 private
,那就错啦。
(四)异常处理的规则
子类重写方法抛出的异常类型不能比父类被重写方法抛出的异常类型更宽泛,只能是父类抛出异常类型的子类或者不抛出异常。这是为了保证在使用多态性时,通过父类引用调用方法时,不会出现意想不到的异常情况。比如说,父类的 processData
方法抛出 IOException
,子类重写这个方法时,可以抛出 FileNotFoundException
(因为 FileNotFoundException
是 IOException
的子类),或者不抛出任何异常,但不能抛出 Exception
(因为 Exception
比 IOException
更宽泛)。
编辑
三、方法重写的示例代码
(一)简单的图形绘制示例
// 父类 Shape class Shape { public void draw() { System.out.println("绘制一个基本图形"); } } // 子类 Circle,继承自 Shape class Circle extends Shape { @Override public void draw() { System.out.println("绘制一个圆形"); } } // 子类 Rectangle,继承自 Shape class Rectangle extends Shape { @Override public void draw() { System.out.println("绘制一个矩形"); } } public class MethodOverrideExample1 { public static void main(String[] args) { Shape shape1 = new Circle(); Shape shape2 = new Rectangle(); shape1.draw(); shape2.draw(); } }
在这个例子中,Shape
类有一个 draw
方法,Circle
和 Rectangle
子类分别重写了这个方法,当通过父类 Shape
的引用指向子类对象并调用 draw
方法时,会执行子类中重写后的 draw
方法,分别输出 “绘制一个圆形” 和 “绘制一个矩形”,这就体现了方法重写的多态性效果。
(二)动物发声示例
// 父类 Animal class Animal { public void makeSound() { System.out.println("动物发出声音"); } } // 子类 Dog,继承自 Animal class Dog extends Animal { @Override public void makeSound() { System.out.println("汪汪汪"); } } // 子类 Cat,继承自 Animal class Cat extends Animal { @Override public void makeSound() { System.out.println("喵喵喵"); } } public class MethodOverrideExample2 { public static void main(String[] args) { Animal dog = new Dog(); Animal cat = new Cat(); dog.makeSound(); cat.makeSound(); } }
我解释一下哈,这里,Animal
类的 makeSound
方法在 Dog
和 Cat
子类中被重写,通过父类引用调用 makeSound
方法时,分别输出狗和猫各自特有的叫声,展示了方法重写如何让不同子类的对象表现出独特的行为。
四、方法重写与方法重载的区别
(一)概念区别
- 方法重写(Override):如前所述,是在子类中对父类的同名同参数列表方法进行重新定义,目的是改变方法的实现细节或者增强功能,以适应子类的特定需求,同时保持方法签名的一致性,从而实现多态性。
- 方法重载(Overload):是在同一个类中定义多个方法名相同但参数列表不同(参数的数量、类型或者顺序不同)的方法。方法重载的目的是为了提供多种不同的操作方式,让调用者可以根据不同的参数情况选择合适的方法来执行。例如,一个
Calculator
类中可以有多个add
方法,一个接受两个int
参数,一个接受两个double
参数,还有一个接受三个int
参数,这样在进行加法运算时,可以根据传入的数据类型和数量选择合适的add
方法。这个往往有些人工作了,都不一定会的
(二)具体区别体现
- 方法签名:方法重写要求方法签名完全相同(除了协变返回类型的情况),而方法重载要求方法名相同但参数列表不同,这是它们最明显的区别。
- 返回类型:方法重写对于返回类型有严格的规则(如前面所讲的基本数据类型和引用数据类型的情况),而方法重载与返回类型无关,只要方法名和参数列表不同,返回类型可以相同也可以不同。
- 调用方式:方法重写是通过父类引用指向子类对象,然后调用重写后的方法,实现多态性;而方法重载是在同一个类中,根据不同的参数情况,在编译时就确定调用哪个具体的重载方法。
下面通过代码示例来更清楚地展示它们的区别:
class MathOperations { // 方法重载示例 public int add(int num1, int num2) { return num1 + num2; } public double add(double num1, double num2) { return num1 + num2; } public int add(int num1, int num2, int num3) { return num1 + num2 + num3; } } class Shape { public void draw() { System.out.println("绘制一个基本形状"); } } class Circle extends Shape { @Override public void draw() { System.out.println("绘制一个圆形"); } } public class OverrideVsOverload { public static void main(String[] args) { MathOperations math = new MathOperations(); // 调用不同的重载方法 int result1 = math.add(2, 3); double result2 = math.add(2.5, 3.5); int result3 = math.add(1, 2, 3); Shape shape = new Circle(); // 调用重写后的方法 shape.draw(); } }
在这个例子中,MathOperations
类展示了方法重载,通过不同的参数列表实现了多个 add
方法;Circle
类重写了 Shape
类的 draw
方法,体现了方法重写。宝子们可以通过这个例子更清晰地看到它们的区别和各自的特点。
五、方法重写在实际开发中的应用场景
(一)图形界面开发中的组件定制
在图形用户界面(GUI)开发中,比如使用 JavaFX 或者 Swing 框架,有各种基本的组件类,如 Button
、Label
等。我们经常需要创建自定义的组件来满足特定的界面设计需求。例如,我们可以继承 Button
类,重写它的 paint
方法,来改变按钮的外观样式,比如添加自定义的背景颜色、边框样式或者文字效果等。这样,通过方法重写,我们可以在不改变原有 Button
类基本功能的基础上,对其进行个性化的定制,使其更好地融入到我们设计的界面中。
(二)数据库访问层的优化
在企业级应用开发中,通常会有一个数据访问层(DAO)来与数据库进行交互。假设我们有一个通用的 BaseDAO
类,其中定义了一些基本的数据库操作方法,如 insert
、update
、delete
和 select
等。对于不同的实体类(如 User
、Product
等),它们在进行数据库操作时可能有一些特定的需求。例如,在插入 User
数据时,可能需要对密码进行加密处理;在查询 Product
数据时,可能需要根据不同的条件进行复杂的查询逻辑构建。这时候,我们可以创建 UserDAO
和 ProductDAO
等子类,继承自 BaseDAO
,并在子类中重写相应的数据库操作方法,如 insert
和 select
方法,来实现针对不同实体类的特定数据库操作逻辑,从而优化数据库访问层的功能,使其更加高效和灵活。
(三)游戏开发中的角色行为扩展
在游戏开发中,经常会有各种角色类,它们可能都继承自一个公共的 Character
类。Character
类中定义了一些基本的行为方法,如 move
、attack
和 defend
等。对于不同的角色,比如战士、法师和刺客等,它们的这些行为方式会有所不同。战士的 attack
方法可能是近身强力攻击,法师的 attack
方法可能是远程魔法攻击,刺客的 move
方法可能更加敏捷快速。通过在各个角色子类中重写这些方法,我们可以赋予每个角色独特的行为特点,让游戏更加丰富和有趣,同时也遵循了面向对象编程的开闭原则,即对扩展开放,对修改关闭,方便后续添加新的角色类型或者修改角色行为。
宝子们,方法重写在 Java 编程中是一个非常强大且实用的特性,它让我们的代码能够更好地适应各种变化和需求,实现更加灵活和优雅的设计。希望通过这篇文章,大家对方法重写有了更深入、更全面的理解,能够在今后的编程实践中熟练运用它,写出更加高质量的代码。如果在学习过程中遇到任何问题或者有不明白的地方,随时回来看看这篇文章,或者查阅更多的相关资料,不断练习和实践,相信你一定能掌握这个重要的概念!
编辑