登神长阶 第二阶 封装 继承 多态
??????????????????????
目录
?一.面向对象编程的三大特性
?二.封装
?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类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.语法
在了解它的语法之前我们先看这样一段代码:
//Dogpublic 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 + "汪汪汪~~~"); }}// Catpublic 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的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自几新增加的成员即可。 从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后文讲)。
此处我们运用继承的方法改写代码
public class Animal{ String name; int age; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep(){ System.out.println(name + "正在睡觉"); }}// Dogpublic class Dog extends Animal{ void bark(){ System.out.println(name + "汪汪汪~~~"); }}// Catpublic class Cat extends Animal{ void mew(){ System.out.println(name + "喵喵喵~~~"); }}// TestExtendpublic 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 类和对象)
相同点 | 不同点 |
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);}}
运行结果如下:
通过分析执行结果,得出以下结论: 父类静态代码块优先于子类静态代码块执行,且是最早执行 父类实例代码块和父类构造方法紧接着执行 子类的实例代码块和子类构造方法紧接着再执行 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
?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中,多态性通过方法重写和方法重载来实现。具体来说,当子类继承并重写父类的方法时,可以根据实际调用的对象来决定到底调用哪个版本的方法。通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
作用:
灵活性和可扩展性:通过多态机制,可以编写出更加灵活的代码,能够适应不同类型的对象,而不需要改变原有的代码结构。这使得程序更容易扩展和维护。
代码复用:多态能够提高代码的复用性,因为父类的引用变量可以指向子类的对象,从而可以统一对待不同的子类对象,简化了代码的编写和维护。
抽象设计:多态可以帮助进行抽象设计,将父类定义为抽象类或接口,然后由不同的子类来实现具体的行为。通过多态,可以以统一的方式处理各种不同类型的子类对象。
?2.多态实现条件
在 java 中要实现多态,必须要满足如下几个条件,缺一不可: 必须在继承体系下 子类必须要对父类中方法进行重写 通过父类的引用调用重写的方法 多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。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.静态绑定 动态绑定
静态绑定:也称为前期绑定(早绑定),即在编译时根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
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代码提供了重要的指导。
????????????????????????????
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出?
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢?