Java 封装 继承 多态(深入理解)

登神长阶 第二阶 封装 继承 多态

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀


目录

🍒一.面向对象编程的三大特性

🍍二.封装

🧉1.定义及其作用

 🥝2.访问限定符

🫛3.封装扩展 包(package)

🥕3.1.定义及其作用

 🥦 3.2.导入包的类

🍔3.3.自定义包

🌯3.3.1基本规则

🍕3.3.2操作步骤

🥐3.3.3常见的包 

🦀三.继承

🍨1.定义及其作用 

🍯2.语法 

🍺3.子类中访问父类的成员方法

🧊3.1.成员方法名字不同

🧃3.2. 成员方法名字相同

🍬4.super关键字

🦑4.1.作用

🍝4.2.super与this作比较

 🍥4.3.继承关系下代码块的执行顺序

🍟5.继承的方式

🍡6.继承与组合 

🥜6.1.组合

🌰6.2.继承与组合优缺点对比

🍅四.多态 

🫒1.定义及其作用 

🍈2.多态实现条件

🍠3.重写

🍣3.1.定义及其规则

🥟3.2.重写与重载的区别

​🥡4.静态绑定 动态绑定

🍱5.向上转移 向下转型 

🍹5.1.向上转型

🍴5.2.向下转型

🗒五.总结与反思


🍒一.面向对象编程的三大特性

  面向对象程序有三大特性:封装、继承、多态
  这三个概念的作用在于提高代码的模块化、 可重用性、可扩展性和可维护性,从而帮助开发人员构建更加健壮和灵活的软件系统。

🍍二.封装

🧉1.定义及其作用

定义:

        封装指的是将数据和对数据的操作进行结合,形成一个逻辑上独立的实体。

 作用:

  1. 隐藏细节:封装允许将对象的内部实现细节隐藏起来,只暴露一些必要的接口给外部。这样可以防止外部代码直接访问对象的内部状态,从而降低了代码的耦合性(模块间关联程度),使得对象的内部改变不会影响到外部使用者。

  2. 提高安全性:通过封装,可以限制对数据的访问和修改方式,防止非法操作导致对象处于无效或不一致的状态。这有助于确保数据的完整性和安全性。

  3. 简化复杂性:封装可以将复杂的操作逻辑封装在一个接口后面,使得外部使用者不需要了解对象内部的具体实现,只需通过公开的接口进行交互,从而简化了外部代码的复杂度。

  4. 提高可维护性:封装可以使得对象的内部实现细节与外部接口分离,当需要修改对象的内部实现时,只需确保对外部接口的行为不变即可,这样就降低了修改代码所带来的风险。

  比如,通俗来讲:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。实际上,电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

 🥝2.访问限定符

  Java 中主要通过类和访问权限来实现封装: 类可以将数据以及封装数据的方法结合在一起 ,更符合人类对事物的认 知,而访问权限用来控制方法或者字段能否直接在类外使用 Java 中提供了四种访问限定符:

  1. public:使用public修饰的成员可以被任何类访问,无论是否属于同一包或不同包。这意味着public成员是全局可见的。

  2. protected:使用protected修饰的成员对于同一包内的类和其子类是可见的。protected成员在不同包的类中也可以通过继承(后文会做详细介绍)该类的方式访问。

  3. 默认(包级私有default):如果一个成员没有使用任何访问修饰符,则默认为包级私有。这意味着该成员只能被同一包内的类访问,而对于不同包内的类是不可见的。

  4. private:使用private修饰的成员只能在定义该成员的类内部访问,其他类无法直接访问private成员。

此外,需要注意以下几点:

  • 访问限定符可以用于字段、方法、构造函数等。
  • 成员方法的访问权限不能高于所在类的访问权限。例如,如果一个类是默认级别的,那么它的方法也不能是public或protected的。
  • protected成员对于非子类的类来说,如果不在同一个包内,是不可见的。只有继承了该类的子类才能访问protected成员。(后文会做详细介绍)
  • 继承关系中,子类继承了父类的protected成员,但若子类与父类不在同一包内,则除了继承之外无法访问protected成员。

  这些访问限定符帮助程序员控制类成员的可见性,从而实现信息隐藏和封装,同时确保代码的安全性和可维护性。

🫛3.封装扩展 包(package)

🥕3.1.定义及其作用

定义:

         在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。

 作用:

  1. 组织类和资源:包可以将相关联的类和资源组织在一起,形成逻辑上的单元。这有助于让项目结构更加清晰,方便开发人员对类和资源进行管理和维护。

  2. 避免命名冲突:通过使用包,可以避免不同类之间的命名冲突。即使类的名称相同,在不同的包中也可以共存而不会产生冲突。

  3. 访问控制:包可以限定类成员的可见性,例如包内私有(default)访问级别的类成员只能被同一个包内的类访问,从而实现了一定程度的封装和信息隐藏。

  4. 提供命名空间:包为类和接口提供了命名空间,使得类和接口的名称具有更好的可读性和表达性。

  5. 模块化和复用:包可以帮助划分代码成各种独立的模块,从而提高了代码的复用性和可维护性。同时,包还支持访问控制,可以将一些特定功能对外隐藏,对其他包提供接口。

  6. Java类库组织:Java标准类库中的类也是以包的形式组织的,通过包的层次结构,可以方便地查找并使用标准类库中的类和接口。

 

 🥦 3.2.导入包的类

  Java 中已经提供了很多现成的类供我们使用 . 例如 Date 类:可以使用 java.util.Date 导入 java.util 这个包中的 Date类
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }
}

  我们更多使用以下形式

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
// 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }
}
如果需要使用 java.util 中的其他类 , 可以使用 import java.util.*
import java.util.*;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
// 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }
}

 💡但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况.

import java.util.*;
        import java.sql.*;
public class Test {
    public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
        Date date = new Date();
        System.out.println(date.getTime());
    }
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
        java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

可以使用import static导入包中静态的方法和字段

import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        double x = 30;
        double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
    }
}
💡 import 和 C++ #include 差别很大 . C++ 必须 #include 来引入其他文件内容 , 但是 Java 不需要。 import 只是为了写代码的时候更方便 . import 更类似于 C++ namespace using

🍔3.3.自定义包

🌯3.3.1基本规则
  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中
  • 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.text.demo1 )
  • 包名要和代码路径相匹配 . 例如创建 com.text.demo1的包, 那么会存在一个对应的路径 com.text.demo1来存储代码.
  • 如果一个类没有 package 语句, 则该类被放到一个默认包中.
🍕3.3.2操作步骤
1. IDEA 中先新建一个包 : 右键 src -> 新建 ->

 2. 在弹出的对话框中输入包名, 例如 com.text.demo1

3. 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了

4. 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句

🥐3.3.3常见的包 
  1. java.lang:系统常用基础类(StringObject),此包从JDK1.1后自动导入。

  2. java.lang.reflect:java 反射编程包;

  3. java.sql:进行数据库开发的支持包。

  4. java.net:进行网络编程开发包。

  5. java.util:java提供的工具程序包。(集合类等) 非常重要

  6. 6. java.io:I/O编程开发包。

🦀三.继承

🍨1.定义及其作用 

定义:

       继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用

 作用:

  1. 代码重用:通过继承,在子类中可以直接使用父类已有的属性和方法,无需重复编写相同的代码,从而提高了代码的复用性。

  2. 类层次结构:继承使得类之间形成了一种层级关系,这样可以更好地组织和管理代码,形成清晰的类之间的关系图。

  3. 方法覆盖(重写):子类可以重写父类中的方法,以满足自身的需要。这样可以根据具体情况来定制特定的行为,实现了多态性的特性。

  4. 多态性:由于继承关系,可以使用父类类型的引用指向子类对象。这样可以在运行时动态确定调用哪个类的方法,实现了多态的特性。

  5. 接口实现:接口也可以继承其他接口,类可以实现接口,这种多继承的方式实现了接口的复用性和扩展性。

  6. 继承现有的类库:Java中的许多类都是通过继承关系进行设计和实现的,开发者可以通过继承这些类来扩展其功能或定制特定的行为。

  7. 维护和更新:通过继承,对于共性的部分只需要在父类中进行修改,就可以同时影响所有子类。这样大大简化了维护和更新的工作。

🍯2.语法 

   在了解它的语法之前我们先看这样一段代码:

//Dog
public class Dog{
    String name;
    int age;
    float weight;
    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    public void sleep(){
        System.out.println(name + "正在睡觉");
    }
    void Bark(){System.out.println(name + "汪汪汪~~~");
    }
}
// Cat
public class Cat{
    String name;
    int age;
    float weight;
    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    public void sleep()
    {
        System.out.println(name + "正在睡觉");
    }
    void mew(){
        System.out.println(name + "喵喵喵~~~");
    }
}
//仔细观察以上代码

      观察以上代码可知:  上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,DogCat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自几新增加的成员即可。 从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后文讲)

   此处我们运用继承的方法改写代码

public class Animal{
    String name;
    int age;
    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    public void sleep(){
        System.out.println(name + "正在睡觉");
    }
}
// Dog
public class Dog extends Animal{
    void bark(){
        System.out.println(name + "汪汪汪~~~");
    }
}// Cat
public class Cat extends Animal{
    void mew(){
        System.out.println(name + "喵喵喵~~~");
    }
}
// TestExtend
public class TestExtend {
    public static void main(String[] args) {
        Dog dog = new Dog();
// dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
        System.out.println(dog.name);
        System.out.println(dog.age);
// dog访问的eat()和sleep()方法也是从Animal中继承下来的
        dog.eat();
        dog.sleep();
        dog.bark();
    }
}

🍺3.子类中访问父类的成员方法

🧊3.1.成员方法名字不同

public class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
}
public class Derived extends Base{
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        methodB(); // 访问子类自己的methodB()
        methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
    }
}
💡 总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时
再到父类中找,如果父类中也没有则报错。

🧃3.2. 成员方法名字相同

public class Base {
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
    public void methodB(){
        System.out.println("Base中的methodB()");
    }
}
public class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的method(int)方法");
    }
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)
        methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
}
💡 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同( 重载 ) ,根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;

🍬4.super关键字

   如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢? 此时便要用到super关键字

🦑4.1.作用

   1.访问父类的成员变量和方法。

   2.在覆盖(重写)方法时调用父类的方法。

以上两点请看以下代码

public class Base {
    int a;
    int b;
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
    public void methodB(){
        System.out.println("Base中的methodB()");
    }
}
public class Derived extends Base{
    int a; // 与父类中成员变量同名且类型相同
    char b; // 与父类中成员变量同名但类型不同
    // 与父类中methodA()构成重载
    public void methodA(int a) {
        System.out.println("Derived中的method()方法");
    }
    // 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 100; // 等价于: this.a = 100;
        b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用(在类和对象有做详细介绍)
// 访问父类的成员变量时,需要借助super关键字


// super是获取到子类对象中从基类继承下来的部分
        super.a = 200;
        super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
        methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
        super.methodB(); // 访问基类的methodB()
    }
}

💡注意:只能在非静态方法中使用 

🥧3.调用父类的构造方法:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

public class Base {
    public Base(){
        System.out.println("Base()");
    }
public class Derived extends Base{
    public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
// 很重要!!!!!!
        System.out.println("Derived()");
    }
}
public class Test {
    public static void main(String[] args) {
        Derived d = new Derived();
    }
}

//结果打印:
//    Base()
//    Derived()
💡 子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

🍝4.2.super与this作比较

(详细请看同专栏 Java 类和对象)

super和this的比较
相同点不同点
1. 都是 Java 中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
1. this 是当前对象的引用,当前对象即调用实例方法的对象, super 相当于是子类对象中从父类继承下来部分成 员的引用
2. 在非静态成员方法中, this 用来访问本类的方法和属性, super 用来访问父类继承下来的方法和属性
3. 在构造方法中: this(...) 用于调用本类构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加,但是 this(...) 用户不写则没有

 🍥4.3.继承关系下代码块的执行顺序

   我们可以通过以下代码验证在继承关系下代码块的执行顺序

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person:构造方法执行");
    }
    {
        System.out.println("Person:实例代码块执行");
    }
    static {
        System.out.println("Person:静态代码块执行");
    }
}
class Student extends Person{
    public Student(String name,int age) {
        super(name,age);
        System.out.println("Student:构造方法执行");
    }
    {
        System.out.println("Student:实例代码块执行");
    }
    static {
        System.out.println("Student:静态代码块执行");
    }
}
public class Text {
    public static void main(String[] args) {
        Student student1 = new Student("zcy",19);
        System.out.println("===========================");
        Student student2 = new Student("zhaozihao",20);}

}

运行结果如下:

 

通过分析执行结果,得出以下结论:
  1. 父类静态代码块优先于子类静态代码块执行,且是最早执行
  2. 父类实例代码块和父类构造方法紧接着执行
  3. 子类的实例代码块和子类构造方法紧接着再执行
  4. 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

🍟5.继承的方式

注意: Java 中不支持多继承
💡 时刻牢记, 我们写的类是现实事物的抽象 . 而我们真正在公司中所遇到的项目往往业务比较复杂 , 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示 , 所以我们真实项目中所写的类也会有很多 . 类之间的关系也会更加复杂.
   但是即使如此 , 我们并不希望类之间的继承层次太复杂 . 一般我们不希望出现超过三层的继承关系 . 如果继承层次太多, 就需要考虑对代码进行重构了 . 如果想从语法上进行限制继承, 就可以使用 final 关键字。

🍡6.继承与组合 

🥜6.1.组合

  组合是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法 (诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。形象而言:

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物

组合表示对象之间是has-a的关系,比如:汽车有发动机,轮胎

代码举例如下

// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
}
// 车载系统类
class VehicleSystem{
// ...
}
class Car{
    private Tire tire; // 可以复用轮胎中的属性和方法
    private Engine engine; // 可以复用发动机中的属性和方法
    private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
💡 组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合

🌰6.2.继承与组合优缺点对比

组 合 关 系继 承 关 系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立优点:子类能自动继承父类的接口
优点:具有较好的可扩展性优点:子类能自动继承父类的接口
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象优点:创建子类的对象时,无须创建父类的对象
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
缺点:整体类不能自动获得和局部类同样的接口缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
缺点:创建整体类的对象时,需要创建所有局部类的对象缺点:不支持动态继承。在运行时,子类无法选择不同的父类

🍅四.多态 

🫒1.定义及其作用 

定义:

      多态允许不同类的对象对同一消息作出不同的响应。在Java中,多态性通过方法重写和方法重载来实现。具体来说,当子类继承并重写父类的方法时,可以根据实际调用的对象来决定到底调用哪个版本的方法。通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

 作用:

  1. 灵活性和可扩展性:通过多态机制,可以编写出更加灵活的代码,能够适应不同类型的对象,而不需要改变原有的代码结构。这使得程序更容易扩展和维护。

  2. 代码复用:多态能够提高代码的复用性,因为父类的引用变量可以指向子类的对象,从而可以统一对待不同的子类对象,简化了代码的编写和维护。

  3. 抽象设计:多态可以帮助进行抽象设计,将父类定义为抽象类或接口,然后由不同的子类来实现具体的行为。通过多态,可以以统一的方式处理各种不同类型的子类对象。

🍈2.多态实现条件

java 中要实现多态,必须要满足如下几个条件,缺一不可:
  1.  必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3.  通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

public class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        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+"吃鱼~~~");
    }
}
public class Dog extends Animal {
    public Dog(String name, int age){
        super(name, age);
    }
    @Override
    public void eat(){
        System.out.println(name+"吃骨头~~~");
    }
}
///分割线//
public class TestAnimal {
    // 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
    // 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
    // 注意:此处的形参类型必须时父类类型才可以
    public static void eat(Animal a){
        a.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
        eat(cat);
        eat(dog);
    }
}
/*运行结果:
        元宝吃鱼~~~
        元宝正在睡觉
        小七吃骨头~~~
        小七正在睡觉*/
💡 在上述代码中, 分割线上方的代码是 类的实现者 编写的 , 分割线下方的代码是 类的调用者 编写的 .
   当类的调用者在编写 eat 这个方法的时候 , 参数类型为 Animal ( 父类 ), 此时在该方法内部并 不知道 , 也不关注 当前的 a 引用指向的是哪个类型 ( 哪个子类 ) 的实例 . 此时 a 这个引用调用 eat 方法可能会有多种不同的表现 ( a 引用的实例相关), 这种行为就称为 多态 .

🍠3.重写

🍣3.1.定义及其规则

定义:

   重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据自身需要实现父类的方法。

方法重写的规则
  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  • 父类被staticprivate修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用  @Override  注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写

🥟3.2.重写与重载的区别

区别点
重写 (override)
重载 (override)
参数列表
一定不能修改
必须修改
返回类型
一定不能修改【除非可以构成父子类关系】
可以修改
访问限定符
一定不能做更严格的限制(可以降低限制)
可以修改

💡方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现 

 🥡4.静态绑定 动态绑定

静态绑定:也称为前期绑定(早绑定),即在编译时根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

class A {
    void show() {
        System.out.println("A");
    }
}

class B extends A {
    void show() {
        System.out.println("B");
    }
}

public class Main {
    public static void main(String[] args) {
        A obj = new B();
        obj.show(); // 这里会调用B类的show方法,因为obj的实际类型是B
    }
}
/*运行结果:
      B   */

动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

class A {
    void show() {
        System.out.println("A");
    }
}

class B extends A {
    void show() {
        System.out.println("B");
    }
}

public class Main {
    public static void main(String[] args) {
        A obj;
        obj = new A();
        obj.show(); // 这里会调用A类的show方法
        obj = new B();
        obj.show(); // 这里会调用B类的show方法,因为obj的实际类型是B
    }
}
/*运行结果:
      A
      B 
           */

🍱5.向上转移 向下转型 

🍹5.1.向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

Animal animal = new Cat("元宝",2)
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

 

使用场景
1. 直接赋值
2. 方法传参
3. 方法返回
如下代码举例:
public class TestAnimal {
    // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eatFood(Animal a){
        a.eat();
    }
    // 3. 作返回值:返回任意子类对象
    public static Animal buyAnimal(String var){
        if("狗".equals(var) ){
            return new Dog("狗狗",1);
        }else if("猫" .equals(var)){
            return new Cat("猫猫", 1);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
        Dog dog = new Dog("小七", 1);
        eatFood(cat);
        eatFood(dog);
        Animal animal = buyAnimal("狗");
        animal.eat();
        animal = buyAnimal("猫");
        animal.eat();
    }
}
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。

🍴5.2.向下转型

向下转型:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
// 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
// 编译失败,编译时编译器将animal当成Animal对象处理
// 而Animal类中没有bark方法,因此编译失败
// animal.bark();
// 向上转型
// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.mew();
// animal本来指向的就是狗,因此将animal还原为狗也是安全的
        dog = (Dog)animal;
        dog.bark();
    }
}
 💡 向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java 中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为 true ,则可以安全转换。如下代码所示:
public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
// 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        }
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

🗒五.总结与反思

💡善于利用零星时间的人,才会做出更大的成绩来。——华罗庚

   在实际编程中,我发现合理运用封装、继承和多态可以使代码结构更清晰,逻辑更加简洁。同时,我也发现需要谨慎设计类的层次结构,避免过度使用继承导致代码过于复杂。另外,在使用多态时,要注意合理地选择方法重写和方法重载,以确保程序具有良好的可读性和可维护性。

   总的来说,学习了封装、继承和多态后,我对面向对象编程原则有了更深入的认识,这些概念也为我编写高质量、易维护的Java代码提供了重要的指导。

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

  制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/441431.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Visual Studio 2019重装vs2019打不开.netcore项目

无法打开项目文件。 .NET SDK 的版本 7.0.306 至少需要 MSBuild 的 17.4.0 版本。当前可用的 MSBuild 版本为 16.11.2.50704。请将在 global.json 中指定的 .NET SDK 更改为需要当前可用的 MSBuild 版本的旧版本。 无法打开项目文件。 .NET SDK 的版本 7.0.306 至少需要 MSBui…

Haproxy 负载均衡集群

一. Haproxy 1. Haproxy 介绍 HAProxy 是法国开发者威利塔罗 (Willy Tarreau) 在2000年使用C语言开发的一个开源软件,是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器,支持基于cookie的持久性,自动故障切换,支持正则…

c#递归函数

在 C#中,递归函数是指在函数内部直接或间接调用自身的函数。递归函数在解决一些问题时非常有用,例如遍历树形结构、递归计算等。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks…

【Qt】不透明指针(Opaque Pointer)在Qt源码中的应用

目录 什么是不透明指针(Opaque Pointer)不透明指针在Qt代码中的应用Qt中与不透明指针相关的一些宏 什么是不透明指针(Opaque Pointer) GeeksforGeeks中给的定义如下: An opaque pointer is a pointer that points to …

Vue的HTML插入——v-html指令

有时我们希望将数据作为HTML代码插入到HTML模板中&#xff0c;而不是以纯文本的形式显示。在这种情况下&#xff0c;我们需要使用Vue.js的v-html指令&#xff1a; <template><div><p>纯文本: {{ rawText }}</p><p>属性: <span v-html"r…

【理解】STM32一键下载电路

1.MCUISP 串口软件一键下载设置&#xff1a; DTR 低电平复位&#xff0c;RTS 高电平进入boot load 串口下载 在ch340 芯片对应DTR 和RTS 输出电平与电脑软件设置的电平相反。 一键下载电路根据ch340 芯片对应引脚的控制信号完成对应功能 具体实现过程如下&#xff1a; 2.单…

spring boot 使用 webservice

spring boot 使用 webservice 使用 java 自带的 jax-ws 依赖 如果是jdk1.8,不需要引入任何依赖&#xff0c;如果大于1.8 <dependency><groupId>javax.jws</groupId><artifactId>javax.jws-api</artifactId><version>1.1</version&g…

微软AI工程师向联邦贸易委员会(FTC)发出警告,对Copilot Designer的安全性表示担忧

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

⭐每天一道leetcode:67.二进制求和(简单;模拟过程)

⭐今日份题目 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例1 输入:a "11", b "1" 输出&#xff1a;"100" 示例2 输入&#xff1a;a "1010", b "1011" 输出&#xff1a;"…

数字化经济的前沿:深入了解 Web3 的商业模式

随着数字化经济的迅速发展&#xff0c;新兴技术如区块链正推动着商业模式的演进。其中&#xff0c;Web3作为区块链技术的新范式&#xff0c;正在引领着商业世界的变革。本文将深入探讨Web3的商业模式&#xff0c;逐一剖析其在数字化经济中的前沿地位&#xff0c;以及对商业世界…

Matlab中安装mltbx工具箱文件

准备 前提就是要已经下载好了相应的mltbx格式的工具箱文件 一般来说可以直接在开源的Github上下到相应的文件&#xff0c;这里以VeriStand Model Generation Support MATLAB add-on为例 注&#xff1a; 一般来说你可以下载到的文件有两种&#xff1a; Source Code &#xff…

物联网的商业模式洞察

大约在十年前&#xff08;2014年11月&#xff09;&#xff0c;全球知名管理思想家、哈佛商学院教授迈克尔波特与PTC前首席执行官吉姆赫普尔曼&#xff0c;在《哈佛商业评论》上联合撰写了一篇备受赞誉的文章&#xff0c;题为《智能互联产品如何改变竞争》。在这篇文章中&#x…

new;getline();重载<<和>>

面向对象程序设计的优点&#xff1a; 易维护易扩展模块化&#xff1a;通过设置访问级别&#xff0c;限制别人对自己的访问&#xff0c;保护了数据安全 int main(){ return 0;} 返回值0在windows下编程一般没用&#xff0c;但是在linux中编程&#xff0c;返回值有时有用 汇编与…

Rollup Summer:一览 Rollup 生态全景图

作者&#xff1a;Stanley&#xff0c;Kernel Ventures 编译&#xff1a;JIN&#xff0c;Techub News 短短几天内&#xff0c;ZKFair 的总锁定价值&#xff08;TVL&#xff09;已达到 1.2 亿美元&#xff0c;目前稳定在 8000 万美元&#xff0c;使其成为增长最快的 Rollup 之一…

uniapp富文本编辑-editor-vue2-vue3-wangeditor

前言 除了“微信小程序”&#xff0c;其他小程序想要使用editor组件实现富文本编辑&#xff0c;很难vue3项目 官方组件editor&#xff0c;在初始化时有点麻烦&#xff0c;建议搭配第三方组件wangeditor 写在前面 - editor组件缺少editor-icon.css 内容另存为editor-icon.css…

阿里云暑期实习 一,二,三面

由于记性不太好就只更一下感受&#xff0c;具体八股细节记不清了 ----- 一面&#xff1a;电话面大约60min&#xff0c;无笔试 一面比较偏技术&#xff0c;主要是结合实习问场景&#xff0c;纯八股感觉不多&#xff0c;反问了一下流程和面试官评价 ----- 二面&#xff1a;电话面…

web组态

演示地址 &#xff1a;by组态[web组态插件] 这是一款可以嵌入到任何项目组态插件&#xff0c;功能全面&#xff0c;可根据自己的项目需要进行二次开发&#xff0c;能大大的节省在组态上的开发时间&#xff0c;代码简单易懂。 一、数据流向图及嵌入原理 数据流向 嵌入原理 …

AutoDev 自定义 Agent:快速接入内部 AI Agent,构建 IDE 即 AI 辅助研发中心

在开源 AI IDE 插件 AutoDev 的 #51 issue 中&#xff0c;我们设计了 AutoDev 的 AI Agent 能力&#xff0c;半年后我们终于交付了这个功能。 在 AutoDev 1.7.0 中&#xff0c;你将可以接入内部的 AI Agent&#xff0c;并将其无缝与现有的 AI 辅助能力结合在一起。 本文将使用 …

SoapUI、Jmeter、Postman三种接口测试工具的比较分析

前段时间忙于接口测试&#xff0c;也看了几款接口测试工具&#xff0c;简单从几个角度做了个比较&#xff0c;拿出来与诸位分享一下。本文从多个方面对接口测试的三款常用工具进行比较分析&#xff0c;以便于在特定的情况下选择最合适的工具&#xff0c;或者使用自己编写的工具…

Uniapp + SpringBoot 开发微信H5项目 微信公众号授权登录 JAVA后台(一、配置使用微信公众平台测试公众号)

申请测试号进行调试开发&#xff0c;测试号拥有大部分服务号有的接口权限。 一、接口配置信息填写校验 这里需要填写一个URL和一个Token验证字符串 我这里是用了natapp内网穿透 将本地的后台8080端口服务映射到了 http://x7zws8.natappfree.cc https://natapp.cn/在natapp官网…