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代码提供了重要的指导。

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

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

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

版权声明:本文为博主作者:中草药z原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/2302_79806056/article/details/136439586

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2024年4月1日
下一篇 2024年4月1日

相关推荐