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 类和对象)
相同点 | 不同点 |
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代码提供了重要的指导。
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。