继承与多态(下)

ops/2024/11/29 5:25:23/

目录

一.关键字final

1.修饰变量

2.修饰方法

3.修饰类

二.继承与组合

三.多态

1.方法重写

2.方法重载(严格上来说非多态)

3.向上转型

4.向下转型

5.向上向下转型综合例子

四.重载和重写的区别


一.关键字final

在 Java 中,final关键字是一个修饰符,可以用于 变量方法,其主要作用是限制修改。

1.修饰变量

(1)基本数据类型

final 修饰的基本数据类型的变量一旦被初始化,其值就不能再更改。

java">final int number = 10;
// number = 20; // 编译错误,无法更改

(2)引用类型

final 修饰引用类型变量后,引用本身不能更改(即不能指向新的对象),但对象的内容仍然可以修改。

java">final StringBuilder builder = new StringBuilder("Hello");
builder.append(" World"); // 可以修改对象内容
// builder = new StringBuilder("Hi"); // 编译错误,不能更改引用

2.修饰方法

final 修饰的方法不能被子类重写,但可以被继承和调用。

java">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 的。

java">final class FinalClass {public void display() {System.out.println("This is a final class.");}
}// class SubClass extends FinalClass { } // 编译错误,无法继承

二.继承与组合

对于继承

继承是一种某某“是一个 (is-a)”某某的关系,子类通过继承父类,自动拥有父类的属性和方法,可以重写(override)父类的方法,也可以扩展新的功能。

对于组合

组合是一种某某“有一个 (has-a)”某某的关系,表示一个对象包含另一个对象作为其成员。通过组合,可以在一个类中直接调用另一个类的方法来实现功能。

举一个汽车例子,解释一下组合:

java">// 引擎类 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修饰非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

举个例子:

java">class Animal {void sound() {System.out.println("动物发出声音~");}
}class Dog extends Animal {@Overridevoid sound() {System.out.println("汪汪叫~");}
}class Cat extends Animal {@Overridevoid 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,不能改为 protectedprivate

4.使用 @Override 注解(建议),不写也不会有报错。但使用 @Override 注解可以帮助编译器检查是否正确进行了重写

5.父类中使用 final 修饰的方法不能被子类重写。

6.静态方法属于类本身,不属于对象,不能被重写。子类中的同名静态方法只是隐藏了父类的静态方法,不能称为重写。

7.构造方法是为初始化类而设计的,不继承自父类,因此不能被重写。

8.重写只适用于实例方法,不适用于类变量、类方法(静态方法)或实例变量。

9.通过父类的引用调用被重写的方法时,执行的是子类的实现(动态绑定)。

对于注意事项的第2项,举个例子:

java">class Parent {Number getValue() {return 10;}
}class Child extends Parent {@OverrideInteger getValue() { // Integer 是 Number 的子类return 20;}
}

对于注意事项的第6项,举个例子:

java">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 methodChild.show();  // 输出: Child static method}
}

2.方法重载(严格上来说非多态)

方法重载(Overloading)是指在同一个类中,定义多个方法,它们具有相同的名称,但参数列表不同(参数类型、数量或顺序不同)。

下面举几个例子:

(1).通过参数类型重载

java">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));        // 调用第一个 addSystem.out.println(calc.add(5.5, 10.2));    // 调用第二个 add}
}

(2).通过参数个数重载

java">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).通过参数顺序重载

java">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 Testd.show("Test", 10);    // 输出: Test 10}
}

(4).注意事项

1.同一类中方法名必须相同:重载的方法属于同一个类,方法名相同但参数列表不同。

2.参数列表必须不同:参数类型、参数的个数或参数的顺序至少有一个不同。

3.返回类型可以相同或不同:但仅靠返回类型的不同不能区分方法。

4.与访问修饰符无关:访问权限修饰符(如 publicprivate)可以不同,但它们不会影响重载的识别。

5.与抛出的异常无关:重载方法可以声明不同的异常,但编译器不会依据异常区分方法。

6.构造方法也可以重载:在 Java 中,构造方法支持重载,可以通过不同的参数列表初始化对象。

7.与方法的修饰符无关:重载与方法是否为 staticfinal 无关。

3.向上转型

向上转型是将子类对象的引用赋值给其父类类型的引用

特点:

  • 自动完成:编译器会自动执行向上转型。
  • 使用父类引用:通过父类引用只能调用父类中声明的方法或属性,但这些方法会在运行时绑定到实际对象(子类)的实现。
  • 实现多态:向上转型是多态的基础,可以统一对父类或接口的操作。

举个例子:

java">class Parent {void show() {System.out.println("父类show方法");}
}class Child extends Parent {@Overridevoid 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

特点

  1. 强制转换:必须显式使用强制类型转换语法 (Child)
  2. 运行时检查:向下转型在运行时会验证对象是否是目标类型。如果父类引用实际上不指向目标子类类型的对象,会抛出异常。
  3. 恢复子类功能:向下转型后可以访问子类的特有方法和属性。

举个例子:

java">class Parent {void show() {System.out.println("父类show方法");}
}class Child extends Parent {@Overridevoid 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.向上向下转型综合例子

java">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);}@Overridepublic void eat() {System.out.println(name+"正在吃喵粮");}@Overridepublic void sleep() {System.out.println(name+"正在猫窝睡觉");}@Overridepublic void sound() {System.out.println(name+"正在喵喵叫");}
}
--------------------------------------------------
public class Dog extends Animal{public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name+"正在吃狗粮");}@Overridepublic void sleep() {System.out.println(name+"正在狗窝睡觉");}@Overridepublic 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实际指向的是狗// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastExceptioncat = (Cat)animal;}
}

四.重载和重写的区别

特性重载(Overloading)重写(Overriding)
定义方法名相同,参数列表不同子类方法与父类方法具有相同签名
是否依赖继承
参数列表必须不同必须相同
返回类型可以相同或不同可以相同或是父类返回类型的子类型
访问修饰符无要求子类修饰符不能更严格
适用于静态方法可以不适用(只能隐藏)
抛出异常无要求子类异常不能比父类更广


http://www.ppmy.cn/ops/137573.html

相关文章

贝叶斯统计:高斯分布均值μ的后验分布推导

使用贝叶斯统计方法 均值 ( μ \mu μ ) 的后验分布解析 在贝叶斯统计中,后验分布表示在观察到数据后,对参数的更新后的信念。本篇文章将结合高斯分布的假设,详细解析均值 ( μ \mu μ ) 的后验分布推导过程,并对 ( μ \mu μ…

一个关于 CSS Modules 的陷阱

我在引用 less 文件样式的时候&#xff0c;发现 index.less .drag_upload {width: 100%;height: 90vh;padding: 20px; }index.jsx import React, { useState, useEffect } from react; import styles from ./index.less;export default ({ }) > {return (<div classNa…

C++ —— 以真我之名 如飞花般绚丽 - 智能指针

目录 1. RAII和智能指针的设计思路 2. C标准库智能指针的使用 2.1 auto_ptr 2.2 unique_ptr 2.3 简单模拟实现auto_ptr和unique_ptr的核心功能 2.4 shared_ptr 2.4.1 make_shared 2.5 weak_ptr 2.6 shared_ptr的缺陷&#xff1a;循环引用问题 3. shared_ptr 和 unique_…

太通透了,Android 流程分析 蓝牙enable流程(应用层/Framework/Service层)

零. 前言 由于Bluedroid的介绍文档有限&#xff0c;以及对Android的一些基本的知识需要了(Android 四大组件/AIDL/Framework/Binder机制/JNI/HIDL等)&#xff0c;加上需要掌握的语言包括Java/C/C等&#xff0c;加上网络上其实没有一个完整的介绍Bluedroid系列的文档&#xff0…

深入理解 HTTP 请求头与请求体

在Web开发中&#xff0c;每一次HTTP请求都由请求行、请求头和请求体组成。虽然看似简单&#xff0c;但它们在数据传输和接口设计中扮演着至关重要的角色。本文将通过简要分析&#xff0c;帮助你深入理解请求头与请求体的关系和应用。 请求头&#xff1a;请求的元信息 请求头是…

cangjie (仓颉) vscode环境搭建

sdk下载 下载中心-仓颉编程语言官网 可选择半年更新版&#xff0c;不用申请。目前版本&#xff1a;0.53.13 &#xff0c;选择不同平台压缩包下载解压到任意位置即可 补充下载&#xff0c;vscode插件解压后&#xff0c;在vscode扩展中选择从vsix安装&#xff0c;安装后新增名为…

神经网络的数学——一个完整的例子

神经网络是一种人工智能方法&#xff0c;它教导计算机以类似于人脑的方式处理数据。神经网络通过输入多个数据实例、预测输出、找出实际答案与机器答案之间的误差&#xff0c;然后微调权重以减少此误差来进行学习。 虽然神经网络看起来非常复杂&#xff0c;但它实际上是线性代数…

【Elasticsearch入门到落地】2、正向索引和倒排索引

接上篇《1、初识Elasticsearch》 上一篇我们学习了什么是Elasticsearch&#xff0c;以及Elastic stack(ELK)技术栈介绍。本篇我们来什么是正向索引和倒排索引&#xff0c;这是了解Elasticsearch底层架构的核心。 上一篇我们学习到&#xff0c;Elasticsearch的底层是由Lucene实…