为什么要进行方法的重写?为什么不在子类中直接构造一种新的方法?

本文探讨了在面向对象编程中,为何选择方法重写而不是创建新方法的原因。介绍了方法重写的三个关键条件,并解释了如何通过重写来定制父类行为。

为什么要进行方法的重写?为什么不在子类中直接构造一种新的方法?

​ 重写的前提是存在继承关系,子类会继承父类的方法(行为),但如果存在子类虽然会继承了父类的行为,但发现父类实现该行为的方式和自己实现该行为的方式不同,例如:子类和父类都能够进行学习,但子类是通过看视频教学学习,而父类通过看书学习;显然子类和父类都具有相同的行为(“学习”),但实现学习的方式不同。显然你觉得既然是两种不同的方式就可以重新构造一种新的方法,但如果你想重新构造一种新的方法(行为)那就必须将该方法(行为)命名为学习,不可以命名为其他,因为我们要的行为就是学习,如果构造成吃饭那显然不可以,而你如果把

方法名(行为)命为学习时,(而这里我们是以方法的形参列表相同,返回值类型和访问权限相同为例),而这种你以为是新的构造方法,其实本就是我们要重写的方法。

注意:方法重写需满足以下三个要点:

1.方法名、形参列表相同

2.返回值类型和声明异常类型,子类小于等于父类

3.访问权限,子类大于等于父类

public>protected>private;

<think>我们之前使用了抽象类Shape,其中定义了抽象方法。但问题问的是:为什么直接使用重写父类的方法,可以不写抽象类吗? 实际上,我们可以不使用抽象类,而是使用一个普通父类(非抽象)或者接口。但使用抽象类的好处是可以强制子类实现这些方法,避免遗漏。 下面我将给出两种替代方案: 1. 使用普通父类(非抽象类),但提供默认实现(可能返回0或抛出异常) 2. 使用接口 但注意:如果不使用抽象类,我们仍然可以要求子类重写方法,但无法在编译时强制要求(除非使用接口或抽象类)。 因此,我们回答:可以不写抽象类,但需要权衡。 下面分别用三种方式实现: 方案A:抽象类(如前所示) 方案B:普通父类(提供默认实现,但不强制重写) 方案C:接口 但根据需求,我们只需要有两个方法:求面积和求周长。我们分别实现这三种方式,并说明差异。 由于问题中已经给出了抽象类的例子,现在我们展示另外两种方式。 方案B:使用普通父类(非抽象) ```java // 普通父类(非抽象) class Shape { // 提供默认实现(通常没有实际意义,这里返回0) public double calculateArea() { System.out.println("父类面积计算"); return 0; } public double calculatePerimeter() { System.out.println("父类周长计算"); return 0; } } // 圆形类 class Round extends Shape { private double radius; private final double PI = 3.1415926535; public Round(double radius) { this.radius = radius; } @Override public double calculateArea() { return PI * radius * radius; } @Override public double calculatePerimeter() { return 2 * PI * radius; } } // 长方形类 class Rectangle extends Shape { private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double calculateArea() { return length * width; } @Override public double calculatePerimeter() { return 2 * (length + width); } } ``` 方案C:使用接口 ```java // 定义形状接口 interface Shape { double calculateArea(); double calculatePerimeter(); } // 圆形类实现接口 class Round implements Shape { private double radius; private final double PI = 3.1415926535; public Round(double radius) { this.radius = radius; } @Override public double calculateArea() { return PI * radius * radius; } @Override public double calculatePerimeter() { return 2 * PI * radius; } } // 长方形类实现接口 class Rectangle implements Shape { private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double calculateArea() { return length * width; } @Override public double calculatePerimeter() { return 2 * (length + width); } } ``` 解释: 方案A(抽象类): 优点:可以包含抽象方法(强制子类实现),也可以包含非抽象方法(共享代码)和成员变量。 缺点:Java是单继承,所以子类无法再继承其他类。 方案B(普通父类): 优点:子类可以选择是否重写方法(如果不重写,则使用父类默认实现,但这可能不符合子类的行为)。 缺点:无法强制子类重写方法,如果子类忘记重写,调用时使用的是父类的默认实现(可能出错,而且编译器不会提醒)。 方案C(接口): 优点:可以多实现,强制实现类必须实现接口中定义的所有方法(编译时检查)。从Java8开始,接口还可以有默认方法。 缺点:在Java8之前,接口不能包含具体的方法实现(现在可以,但有限制)。 根据需求,我们通常希望子类必须实现计算面积和周长的方法,所以方案A(抽象类)和方案C(接口)更好。 因此,回到问题:可以不写抽象类吗? 答:可以,但如果不使用抽象类或接口,则无法强制子类重写方法,可能导致错误(例如忘记重写,调用父类方法返回0,而实际应该有计算结果)。使用接口或抽象类可以在编译时确保子类实现了这些方法。 所以,虽然可以不用抽象类(比如用普通父类),但为了程序的健壮性和设计清晰,我们推荐使用抽象类或接口。 测试类(对于方案B和方案C,测试类类似,只需要注意创建对象的方式): 在方案B和方案C中,测试类的代码与之前抽象类的测试类相同(除了类继承或实现的方式不同,但使用方式一样)。 下面给出针对接口方案的测试类(与抽象类测试类几乎一致): ```java public class ShapeTest { public static void main(String[] args) { Shape circle = new Round(5.0); System.out.println("圆形面积: " + String.format("%.2f", circle.calculateArea())); System.out.println("圆形周长: " + String.format("%.2f", circle.calculatePerimeter())); Shape rectangle = new Rectangle(6.0, 4.0); System.out.println("长方形面积: " + rectangle.calculateArea()); System.out.println("长方形周长: " + rectangle.calculatePerimeter()); } } ``` 结论:根据设计需要,我们可以选择抽象类或接口。如果不需要继承其他类(且需要共享代码),可以用抽象类;如果需要多继承特性,则用接口。 §§相关问题§§: 1. 抽象类和接口在Java中有什么区别? 2. 在什么情况下应该使用抽象类,什么情况下使用接口? 3. 如果忘记重写抽象类中的抽象方法,会发生什么? 4. 接口中是否可以定义变量?如果可以,有什么特点? </think># 关于是否可以不使用抽象类的解释 在面向对象编程中,我们可以不使用抽象类来实现多态和重写,但使用抽象类提供了更好的设计结构和强制约束。下面我将详细解释各种实现方式并给出代码示例。 ## 1. 不使用抽象类的实现方式 可以不使用抽象类,直接定义一个普通父类并提供默认实现(即使这些默认实现可能没有实际意义): ```java // 普通父类(非抽象) class Shape { public double calculateArea() { System.out.println("父类面积计算方法"); return 0; // 默认返回0 } public double calculatePerimeter() { System.out.println("父类周长计算方法"); return 0; // 默认返回0 } } // 圆形类 class Round extends Shape { private double radius; private final double PI = 3.1415926535; public Round(double radius) { this.radius = radius; } @Override public double calculateArea() { return PI * radius * radius; } @Override public double calculatePerimeter() { return 2 * PI * radius; } } // 长方形类 class Rectangle extends Shape { private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } @Override public double calculateArea() { return length * width; } @Override public double calculatePerimeter() { return 2 * (length + width); } } // 测试类 public class ShapeTest { public static void main(String[] args) { Shape circle = new Round(5.0); System.out.println("圆形面积: " + circle.calculateArea()); Shape rectangle = new Rectangle(6.0, 4.0); System.out.println("长方形周长: " + rectangle.calculatePerimeter()); } } ``` ### 这种方式的优缺点: **优点**: - 不需要使用abstract关键字 - 子类可以选择性重写方法(虽然这里我们重写了所有方法) **缺点**: - 父类的方法可能有误导性(返回0) - 编译器不会强制要求子类重写方法 - 如果忘记重写方法,会调用父类的默认实现(可能导致错误) - 无法阻止创建Shape的实例(即使它没有实际意义) ## 2. 使用抽象类的优势 ```java // 抽象类 abstract class Shape { public abstract double calculateArea(); public abstract double calculatePerimeter(); } ``` ### 使用抽象类的理由: 1. **强制实现**:编译器会强制所有子类实现抽象方法 2. **防止误用**:无法创建抽象类的实例(避免了无意义的Shape对象) 3. **设计清晰**:明确表示Shape是基类,只定义接口不提供实现 4. **多态支持**:仍然可以通过父类引用处理不同子类对象 5. **避免默认实现的误导**:不会提供可能引起误解的默认实现 ## 3. 使用接口作为替代方案 接口是另一种实现多态的常用方式: ```java // 定义接口 interface Shape { double calculateArea(); double calculatePerimeter(); } // 圆形类实现接口 class Round implements Shape { // ... 实现与之前相同 } // 长方形类实现接口 class Rectangle implements Shape { // ... 实现与之前相同 } ``` ### 接口vs抽象类: | 特性 | 抽象类 | 接口 | |------|--------|------| | 方法实现 | 可以有具体方法 | Java8+可以有默认方法 | | 变量 | 可以有实例变量 | 只能有静态常量 | | 继承 | 单继承 | 多实现 | | 构造函数 | 可以有 | 不能有 | | 设计目的 | 代码复用和共享 | 定义行为契约 | ## 4. 推荐做法:抽象类 + 具体实现 对于形状这种有明显层级关系的情况,抽象类通常是更好的选择: ```java abstract class Shape { // 抽象方法强制子类实现 public abstract double calculateArea(); public abstract double calculatePerimeter(); // 可以添加公共方法(非抽象的) public void printInfo() { System.out.println("面积: " + calculateArea()); System.out.println("周长: " + calculatePerimeter()); } } // 具体子类实现 class Round extends Shape { /* 实现略 */ } class Rectangle extends Shape { /* 实现略 */ } ``` ## 结论 **可以直接重写父类方法而不使用抽象类**,但使用抽象类有以下显著优势: 1. 提供更强的设计约束和意图表达 2. 防止创建无意义的基类实例 3. 确保子类必须实现关键方法 4. 增强代码的可维护性和可读性 在大型项目中,使用抽象类或接口能更好地体现"面向接口编程而非实现"的设计原则。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值