IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java面向对象之组合与多态 -> 正文阅读

[Java知识库]Java面向对象之组合与多态

Java面向对象之组合与多态

??前言??
本篇文章是上一篇包和继承文章的后续篇,针对面向对象编程的组合、多态、抽象类与接口方面内容的总结分享,希望各位小主们认真浏览,一定会受益多多哟!

🍉博客主页: 🍁如风暖阳🍁

🍉欢迎点赞 👍 收藏 ?留言评论 📝私信必回哟😁

🍉本文由 【如风暖阳】 原创,首发于 CSDN🙉

🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言

🍉博客中涉及源码及博主日常练习代码均已上传码云(gitee)


🍅1.组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
例如表示一个学校:

public class Student { 
 ... 
} 
public class Teacher { 
 ... 
} 
public class School { 
 public Student[] students; 
 public Teacher[] teachers; 
}

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.其关系可解释为a part of
这是我们设计类的一种常用方式之一.
组合表示 has - a(a part of) 语义
在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.
继承表示 is - a 语义
在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物。
大家要注意体会两种语义的区别

🍅2.多态

多态—我们从字面上来理解就是一种事物的多种形态,理解的很到位,但这句话可不敢在面试官面前这么说(被打),那么它究竟该如何来解释呢,下面我们一起来看看!
要想理解多态还需分小部分内容理解,最后总结理解:

2.1向上转型

什么叫向上转型呢,看下这个图
在这里插入图片描述
就是父类引用引用子类对象
代码实现:
先是两个类(子类与父类)

class Animal {
    public String name;
    public int age;
    public Animal(String name) {
        this.name=name;
    }
}
class Dog extends Animal {
    public Dog(String name){
        super(name);
    }
}

这是关键部分

public class TestDemo {
    public static void main(String[] args) {
        Animal animal = new Dog("hello");
    }//父类引用Animal引用实例化子类对象Dog
}

这就叫做向上转型,为什么叫向上转型呢

在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表
示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转

向上转型发生的时机:

  • 直接赋值
  • 方法传参
  • 方法返回
    直接赋值的方式我们已经演示了. 另外两种方式和直接赋值没有本质区别

方法传参:

    public static void fun(Animal animal) {
    }
    public static void main(String[] args) {
        fun(new Dog("hello"));
    }

方法返回:

    public static Animal fun1() {
        Dog dog=new Dog("hello");
        return dog;
    }

2.2动态绑定

当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
对前面的代码稍加修改, 给 两个类加上同名的 eat 方法, 并且在两个 eat 中分别加上不同的日志

class Animal {
    public String name;
    public Animal(String name) {
        this.name=name;
    }
    public void eat() {
        System.out.println(this.name+"吃");
    }
}
class Dog extends Animal {
    public Dog(String name){
        super(name);
    }
    public void eat() {
        System.out.println(this.name+"狼吞虎咽的吃");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Animal animal1=new Animal("hi");
        animal1.eat();
        Animal animal2=new Dog("hello");
        animal2.eat();
    }
}

执行结果:
在这里插入图片描述

此时, 我们发现:
animal1和animal2虽然都是 Animal 类型的引用, 但是 animal1指向 Animal 类型的实例,animal2指向Dog类型的实例.
针对 animal1和 animal2分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而
animal2.eat() 实际调用了子类的方法。

因此

在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) ,
要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为动态绑定(也叫运行时绑定)

动态绑定的发生条件:

  • 父类引用引用子类对象
  • 通过这个父类引用调用父类和子类同名覆盖的方法

2.3方法重写

针对刚才的 eat 方法来说:
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为覆写/重写/覆盖(Override).
什么情况下发生重写:

1.方法名相同
2.参数列表相同(个数加类型)
3.返回值相同(也可以不同,如协变类型:子类中方法的返回值是父类中方法返回值的子类)
“public Animal fun();public Dog fun() return null;"
4.是父子类继承关系

注意点:
1.方法不可以是static
2.子类的访问修饰限定,要大于或等于父类的访问修饰
3.private方法不能重写
4.被final修饰的方法不能重写
5.如果方法没有重写,向上转型的引用只能调用父类的方法
6.通过父类引用只能访问父类自己的成员

小总结.重写和重载的区别(动态绑定和静态绑定的区别)

答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

详情见CSDN博主「wintershii」的原创文章

2.4向下转型

向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象。
如下代码:实际上还是本类型引用本类型对象

class Animal {
    public String name;
    public Animal(String name) {
        this.name=name;
    }
    public void eat() {
        System.out.println(this.name+"吃");
    }
}
class Dog extends Animal {
    public Dog(String name){
        super(name);
    }
    public void eat() {
        System.out.println(this.name+"狼吞虎咽的吃");
    }
}
class Bird extends Animal {
    public Bird(String name){
        super(name);
    }
    public  void eat() {
        System.out.println(this.name+"小心翼翼的吃");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Animal animal=new Bird("hi");
        Bird bird=(Bird)animal;//此处注意强制转化
        bird.eat();
    }
}

但如果像这样:将会发生类型转化异常,所以向下转型不安全,只是自己引用自己,并不多用

 public static void main(String[] args) {
        Animal animal=new Dog("hi");
        Bird bird=(Bird)animal;//此处注意强制转化
        bird.eat();
 }
 //Exception in thread "main" java.lang.ClassCastException: com.bilibili.demo1.Dog cannot be cast to com.bilibili.demo1.Bird
	at com.bilibili.demo1.TestDemo.main(TestDemo.java:30)

所以会使用关键字instanceof可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了.

 public static void main(String[] args) {
        Animal animal=new Dog("hi");
        if(animal instanceof Bird) {
        Bird bird=(Bird)animal;//此处注意强制转化
        bird.eat();
        }
 }

2.5.super关键字

前面的代码中由于使用了重写机制, 调用到的是子类的方法. 如果需要在子类内部调用父类方法怎么办?
可以使用super 关键字.
super 表示对父类实例的引用.

  • super()来调用父类的构造方法
  • super.fun()来调用父类的普通方法
  • super.data来调用父类的成员属性

小总结.super和this的区别

      (1.)代表的事物不同

          super代表的是父类空间的引用

          this代表的是所属函数的调用者对象

      (2.)使用前提不同

          super必须要有继承关系才能使用

          this不需要继承关系也能使用

      (3.)调用的事物不同

           super:调用父类的构造方法(必须在第一行),普通方法或成员变量

           this:调用所属类的构造方法(必须在第一行),普通方法或成员变量

2.6在构造方法中调用重写的方法(一个坑)

在主函数中实例化构造dog对象需要先调用Animal的构造方法,而Animal的构造方法中又有重写的普通方法,此时会调用父类还是子类的方法呢?我们一起来看看:

class Animal {
    public String name;
    public Animal(String name) {
        this.name=name;
        eat();
    }
    public void eat() {
        System.out.println(this.name+"吃");
    }
}
class Dog extends Animal {
    public Dog(String name){
        super(name);
    }
    @Override
    public void eat() {
        System.out.println(this.name+"狼吞虎咽的吃");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("hello");
    }
}
//运行结果:
hello狼吞虎咽的吃

说明也是调用了子类的方法,此时也发生了动态绑定

2.7理解多态

有了面的向上转型, 动态绑定, 方法重写之后, 我们就可以使用 多态(polypeptide) 的形式来设计程序了,我们可以写一些只关注父类的代码, 就能够同时兼容各种子类的情况
代码示例: 打印多种形状

class Shape { 
 public void draw() { 
 // 啥都不用干
 } 
} 
class Cycle extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("○"); 
 } 
} 
class Rect extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("?"); 
 } 
} 
class Flower extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("?"); 
 } 
} 
/我是分割线// 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Shape shape1 = new Flower(); 
 Shape shape2 = new Cycle(); 
 Shape shape3 = new Rect(); 
 drawMap(shape1); 
 drawMap(shape2); 
 drawMap(shape3); 
 } 
 // 打印单个图形
 public static void drawShape(Shape shape) { 
 shape.draw(); 
 } 
}

在这个代码中, 分割线上方的代码是类的实现者编写的, 分割线下方的代码是类的调用者编写的.
当类的调用者在编写drawMap这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为多态
使用多态的好处是什么?
1) 类调用者对类的使用成本进一步降低.

  • 封装是让类的调用者不需要知道类的实现细节.
  • 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可

因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低
2) 避免使用大量的 if - else
例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:

public static void drawShapes() { 
 Rect rect = new Rect(); 
 Cycle cycle = new Cycle(); 
 Flower flower = new Flower(); 
 String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"}; 
 
for (String shape : shapes) { 
 if (shape.equals("cycle")) { 
 cycle.draw(); 
 } else if (shape.equals("rect")) { 
 rect.draw(); 
 } else if (shape.equals("flower")) { 
 flower.draw(); 
 } 
 } 
}

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单

public static void drawShapes() { 
 // 我们创建了一个 Shape 对象的数组. 
 Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), 
 new Rect(), new Flower()}; 
 for (Shape shape : shapes) { 
 shape.draw(); 
 } 
}

3) 可扩展能力更强.
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.

class Triangle extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("△"); 
 } 
}

对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低.
而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高.


??最后的话??

总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-04 10:54:21  更:2022-02-04 10:54:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:57:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码