继承与多态(下)
目录
一.关键字final
1.修饰变量
2.修饰方法
3.修饰类
二.继承与组合
三.多态
1.方法重写
2.方法重载(严格上来说非多态)
3.向上转型
4.向下转型
5.向上向下转型综合例子
四.重载和重写的区别
一.关键字final
在 Java 中,final关键字是一个修饰符,可以用于 变量、方法 和 类,其主要作用是限制修改。
1.修饰变量
(1)基本数据类型
final
修饰的基本数据类型的变量一旦被初始化,其值就不能再更改。
final int number = 10;
// number = 20; // 编译错误,无法更改
(2)引用类型
final
修饰引用类型变量后,引用本身不能更改(即不能指向新的对象),但对象的内容仍然可以修改。
final StringBuilder builder = new StringBuilder("Hello");
builder.append(" World"); // 可以修改对象内容
// builder = new StringBuilder("Hi"); // 编译错误,不能更改引用
2.修饰方法
final
修饰的方法不能被子类重写,但可以被继承和调用。
class Parent {
public final void show() {
System.out.println("This is a final method.");
}
}
class Child extends Parent {
// @Override
// public void show() { } // 编译错误,无法重写
}
3.修饰类
final
修饰的类不能被继承,因此所有的方法都被隐式地认为是final
的。
final class FinalClass {
public void display() {
System.out.println("This is a final class.");
}
}
// class SubClass extends FinalClass { } // 编译错误,无法继承
二.继承与组合
对于继承
继承是一种某某“是一个 (is-a)”某某的关系,子类通过继承父类,自动拥有父类的属性和方法,可以重写(override)父类的方法,也可以扩展新的功能。
对于组合
组合是一种某某“有一个 (has-a)”某某的关系,表示一个对象包含另一个对象作为其成员。通过组合,可以在一个类中直接调用另一个类的方法来实现功能。
举一个汽车例子,解释一下组合:
// 引擎类 Engine
class Engine {
public void start() {
System.out.println("Engine is starting.");
}
}
// 汽车类 Car 包含一个 Engine 实例(组合关系)
class Car {
private Engine engine;
// 通过构造器注入一个 Engine 实例
public Car(Engine engine) {
this.engine = engine;
}
public void startCar() {
engine.start(); // 调用 Engine 的 start 方法
System.out.println("Car is starting.");
}
}
public class CompositionExample {
public static void main(String[] args) {
Engine engine = new Engine(); // 创建 Engine 实例
Car car = new Car(engine); // 将 Engine 组合到 Car 中
car.startCar(); // 启动汽车
}
}
三.多态
多态(Polymorphism),来源于希腊语,意为“多种形式”。在编程中,多态允许同一个方法在不同的上下文中表现出不同的行为。简单来说,同一接口,不同实现。
对于实现多态的条件,必须满足,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
原因:
没有继承,无法重写:
- 如果没有继承,子类和父类不存在关联,也就无法实现方法的动态绑定。
没有重写,行为无法变化:
- 如果子类没有对父类的方法进行重写,那么调用父类的引用时,执行的永远是父类的方法,体现不出多态的动态特性。
不通过父类引用,无法体现多态:
- 如果直接用子类引用调用方法,这只是普通的调用,不是多态。
1.方法重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
举个例子:
class Animal {
void sound() {
System.out.println("动物发出声音~");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("汪汪叫~");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("喵喵叫~");
}
}
public class Main {
public static void main(String[] args) {
Animal animal;
animal = new Dog(); // 父类引用指向子类对象
animal.sound(); // 输出: 汪汪叫~
animal = new Cat();
animal.sound(); // 输出: 喵喵叫~
}
}
(1)注意事项
1.子类重写的方法的方法名,参数列表(类型和顺序)必须与父类方法一致。
2.返回类型可以相同或是父类返回类型的子类型
3.子类的重写方法的访问权限不能低于父类的访问权限。例如,如果父类的方法是
public
,子类的方法也必须是public
,不能改为protected
或private
。4.使用
@Override
注解(建议),不写也不会有报错。但使用@Override
注解可以帮助编译器检查是否正确进行了重写5.父类中使用
final
修饰的方法不能被子类重写。6.静态方法属于类本身,不属于对象,不能被重写。子类中的同名静态方法只是隐藏了父类的静态方法,不能称为重写。
7.构造方法是为初始化类而设计的,不继承自父类,因此不能被重写。
8.重写只适用于实例方法,不适用于类变量、类方法(静态方法)或实例变量。
9.通过父类的引用调用被重写的方法时,执行的是子类的实现(动态绑定)。
对于注意事项的第2项,举个例子:
class Parent {
Number getValue() {
return 10;
}
}
class Child extends Parent {
@Override
Integer getValue() { // Integer 是 Number 的子类
return 20;
}
}
对于注意事项的第6项,举个例子:
class Parent {
static void show() {
System.out.println("Parent static method");
}
}
class Child extends Parent {
static void show() {
System.out.println("Child static method");
}
}
public class Main {
public static void main(String[] args) {
Parent.show(); // 输出: Parent static method
Child.show(); // 输出: Child static method
}
}
2.方法重载(严格上来说非多态)
方法重载(Overloading)是指在同一个类中,定义多个方法,它们具有相同的名称,但参数列表不同(参数类型、数量或顺序不同)。
下面举几个例子:
(1).通过参数类型重载
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // 调用第一个 add
System.out.println(calc.add(5.5, 10.2)); // 调用第二个 add
}
}
(2).通过参数个数重载
class Greeting {
void greet() {
System.out.println("Hello!");
}
void greet(String name) {
System.out.println("Hello, " + name + "!");
}
}
public class Main {
public static void main(String[] args) {
Greeting g = new Greeting();
g.greet(); // 输出: Hello!
g.greet("Alice"); // 输出: Hello, Alice!
}
}
(3).通过参数顺序重载
class Display {
void show(int a, String b) {
System.out.println(a + " " + b);
}
void show(String b, int a) {
System.out.println(b + " " + a);
}
}
public class Main {
public static void main(String[] args) {
Display d = new Display();
d.show(10, "Test"); // 输出: 10 Test
d.show("Test", 10); // 输出: Test 10
}
}
(4).注意事项
1.同一类中方法名必须相同:重载的方法属于同一个类,方法名相同但参数列表不同。
2.参数列表必须不同:参数类型、参数的个数或参数的顺序至少有一个不同。
3.返回类型可以相同或不同:但仅靠返回类型的不同不能区分方法。
4.与访问修饰符无关:访问权限修饰符(如
public
、private
)可以不同,但它们不会影响重载的识别。5.与抛出的异常无关:重载方法可以声明不同的异常,但编译器不会依据异常区分方法。
6.构造方法也可以重载:在 Java 中,构造方法支持重载,可以通过不同的参数列表初始化对象。
7.与方法的修饰符无关:重载与方法是否为
static
或final
无关。
3.向上转型
向上转型是将子类对象的引用赋值给其父类类型的引用。
特点:
- 自动完成:编译器会自动执行向上转型。
- 使用父类引用:通过父类引用只能调用父类中声明的方法或属性,但这些方法会在运行时绑定到实际对象(子类)的实现。
- 实现多态:向上转型是多态的基础,可以统一对父类或接口的操作。
举个例子:
class Parent {
void show() {
System.out.println("父类show方法");
}
}
class Child extends Parent {
@Override
void show() {
System.out.println("子类show方法");
}
void display() {
System.out.println("子类display方法");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // 向上转型
obj.show(); // 输出: 子类show方法
// obj.display(); // 编译错误: 父类引用无法访问子类特有的方法
}
}
4.向下转型
向下转型是将父类类型的引用强制转换为子类类型的引用。这需要显式地进行类型转换,并且可能会抛出
ClassCastException
。
特点
- 强制转换:必须显式使用强制类型转换语法
(Child)
。- 运行时检查:向下转型在运行时会验证对象是否是目标类型。如果父类引用实际上不指向目标子类类型的对象,会抛出异常。
- 恢复子类功能:向下转型后可以访问子类的特有方法和属性。
举个例子:
class Parent {
void show() {
System.out.println("父类show方法");
}
}
class Child extends Parent {
@Override
void show() {
System.out.println("子类show方法");
}
void display() {
System.out.println("子类display方法");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // 向上转型
obj.show(); // 输出: 子类show方法
// 向下转型
if (obj instanceof Child) {
Child childObj = (Child) obj; // 向下转型
childObj.display(); // 输出: 子类display方法
}
}
}
5.向上向下转型综合例子
public class Animal {
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
public void sound(){
System.out.println(name+"正在叫");
}
}
-------------------------------------------------
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"正在吃喵粮");
}
@Override
public void sleep() {
System.out.println(name+"正在猫窝睡觉");
}
@Override
public void sound() {
System.out.println(name+"正在喵喵叫");
}
}
--------------------------------------------------
public class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"正在吃狗粮");
}
@Override
public void sleep() {
System.out.println(name+"正在狗窝睡觉");
}
@Override
public void sound() {
System.out.println(name+"正在发出狗叫");
}
public void run(){
System.out.println(name+"正在跑步");
}
}
--------------------------------------------------
//在向上转型后,可以访问子类重写的方法,这是因为在 Java 中调用方法时采用的是*
// *动态绑定(Dynamic Binding)**的机制。动态绑定意味着在程序运行
// 时,JVM 会根据对象的实际类型来决定调用哪个方法,而不是根据引用的类型。这正
// 是多态的核心。
public class TestAnimal {
//2. 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eatfood(Animal a){
a.eat();
}
//作返回值:返回任意子类对象
public static Animal buyAnimal(String var){
if("狗".equals(var)){
return new Dog("狗子",1);
}else if("猫".equals(var)){
return new Cat("咪咪",2);
}else{
return null;
}
}
public static void main(String[] args) {
// //向上转型
//animal是一个 Animal 类型的引用,指向一个 Dog 类型的对象。
Animal animal=new Dog("小七",2);
animal.sound();
// animal.run(); //报错:向上转型后,只能访问父类中定义的方法和变量,而不能直接访问子类中特有的方法和变量。
Animal cat=new Cat("元宝",1);
cat.sound();
eatfood(animal);
eatfood(cat);
Animal animal1=buyAnimal("狗");
animal1.sleep();
Animal animal2=buyAnimal("猫");
animal2.sound();
System.out.println("-----------------------");
//向下转型
//instanceof关键字检查后,可以确认它是 Dog 类型
if(animal instanceof Dog){
//(Dog) myAnimal 是将 myAnimal 转换为 Dog 类型的引用
Dog myDog=(Dog)animal;
//这样就可以执行子类专有的方法
myDog.run();
}
// myDog.run();//报错:直接写在外面会报错,因为在 if 外面 myDog 变量并不可见。myD
// og 是在 if 语句的代码块中声明的局部变量,所以它的作用范围仅限于 if 代码块内部,一旦
// 离开 if 代码块,myDog 就无法访问了。
//可以这样写:
// Dog myDog = null; // 提前声明 myDog
// if (animal instanceof Dog) {
// myDog = (Dog) animal; // 向下转型
// }
//
// if (myDog != null) {
// myDog.run(); // 现在可以在 if 外调用 myDog.run()
// }
// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
cat = (Cat)animal;
}
}
四.重载和重写的区别
特性 | 重载(Overloading) | 重写(Overriding) |
---|---|---|
定义 | 方法名相同,参数列表不同 | 子类方法与父类方法具有相同签名 |
是否依赖继承 | 否 | 是 |
参数列表 | 必须不同 | 必须相同 |
返回类型 | 可以相同或不同 | 可以相同或是父类返回类型的子类型 |
访问修饰符 | 无要求 | 子类修饰符不能更严格 |
适用于静态方法 | 可以 | 不适用(只能隐藏) |
抛出异常 | 无要求 | 子类异常不能比父类更广 |