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");
}
}
这就叫做向上转型,为什么叫向上转型呢
在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 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();
}
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("?");
}
}
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[] 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!!如有问题,欢迎评论区批评指正😁
|