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

中草药z 2024-07-05 09:35:04 阅读 56

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

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


目录

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

🍍二.封装

🧉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.定义及其作用

定义:

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

 作用:

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

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

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

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

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

 🥝2.访问限定符

  Java

中主要通过类和访问权限来实现封装:

类可以将数据以及封装数据的方法结合在一起

,更符合人类对事物的认 知,而访问权限用来控制方法或者字段能否直接在类外使用

Java

中提供了四种访问限定符:

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

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

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

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

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

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

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

🫛3.封装扩展 包(package)

🥕3.1.定义及其作用

定义:

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

 作用:

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

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

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

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

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

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

 

 🥦 3.2.导入包的类

  Java

中已经提供了很多现成的类供我们使用

.

例如

Date

类:可以使用

java.util.Date

导入

java.util

这个包中的

Date类

<code>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常见的包 

java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。

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

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

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

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

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

🦀三.继承

🍨1.定义及其作用 

定义:

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

 作用:

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

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

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

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

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

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

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

🍯2.语法 

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

<code>//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类称为父类/基类或超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自几新增加的成员即可。 从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后文讲)。

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

<code>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.继承关系下代码块的执行顺序

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

<code>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);}

}

运行结果如下:

 

通过分析执行结果,得出以下结论:

父类静态代码块优先于子类静态代码块执行,且是最早执行 父类实例代码块和父类构造方法紧接着执行 子类的实例代码块和子类构造方法紧接着再执行 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

🍟5.继承的方式

注意:

Java

中不支持多继承

💡

时刻牢记,

我们写的类是现实事物的抽象

.

而我们真正在公司中所遇到的项目往往业务比较复杂

,

可能会涉及到一系列复杂的概念,

都需要我们使用代码来表示

,

所以我们真实项目中所写的类也会有很多

.

类之间的关系也会更加复杂.

   但是即使如此

,

我们并不希望类之间的继承层次太复杂

.

一般我们不希望出现超过三层的继承关系

.

如果继承层次太多,

就需要考虑对代码进行重构了

. 如果想从语法上进行限制继承,

就可以使用

final

关键字。

🍡6.继承与组合 

🥜6.1.组合

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

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

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

代码举例如下

<code>// 轮胎类

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中,多态性通过方法重写和方法重载来实现。具体来说,当子类继承并重写父类的方法时,可以根据实际调用的对象来决定到底调用哪个版本的方法。通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

 作用:

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

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

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

🍈2.多态实现条件

java

中要实现多态,必须要满足如下几个条件,缺一不可:

 必须在继承体系下 子类必须要对父类中方法进行重写  通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

<code>

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 父类被static、private修饰的方法、构造方法都不能被重写。 重写的方法, 可以使用  @Override  注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写

🥟3.2.重写与重载的区别

区别点

重写

(override)

重载

(override)

参数列表

一定不能修改

必须修改

返回类型

一定不能修改【除非可以构成父子类关系】

可以修改

访问限定符

一定不能做更严格的限制(可以降低限制)

可以修改

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

 🥡4.静态绑定 动态绑定

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

<code>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.

方法返回

如下代码举例:

<code>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.向下转型

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

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

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

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

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。