JavaSE系列Java面向对象之组合多态与接口

Posted 未见花闻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE系列Java面向对象之组合多态与接口相关的知识,希望对你有一定的参考价值。

⭐️前面的话⭐️

本篇文章带大家认识Java基础知识——组合,多态与接口,小伙伴们,好久不见,上篇博文《包与继承》留给读者的题选A。这篇文章将会详细介绍多态与接口,上次介绍的继承是阅读本文的基础,先来复盘一下继承吧,打个比方我们不妨将图纸分为两类,一类具有共性,另一类具有特性,具有特性的图纸继承了具有共性的图纸,则具有共性的图纸(类)称为父类(基类,超类),具有特性的图纸(类)称为子类,两者的关系是子类继承父类,子类实例化出的对象就相当于两张图纸实例的一个对象,继承往往具有子类is a 父类或者子类is like a父类。然后,开始正文!

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2021年11月23日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《Java核心技术》,📚《Java编程思想》,📚《Effective Java》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🙏作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!




1.组合

在继承的关系中,子类(导出类)与父类(基类,超类)的关系是is - a的关系,比如就鸟(子类)继承动物(父类),可以说鸟是一种动物。
而对于组合,就是在一个新类中使用一个或多个类,比如我们要创建一个学校类,我们都知道学校有老师,有学生,而且一般情况下肯定不止一个,所以学校类中会使用学生类和老师类,也就是说组合是hava的关系,也就是学校里面有老师和学生。

class Student 
    public String name;
    public double score;
    public int age;
    public int classNo;


class Teacher 
    public String name;
    public int age;
    public int classNo;
    public String subject;



public class School 
    public Student[] stu;
    public Teacher[] tec;

2.多态

2.1向上转型

向上转型的前提是类之间存在继承关系,使用父类引用指向子类对象,这就叫做向上转型。

就比如Bird类继承了Animal类,然后使用父类Animal引用指向子类Bird对象,在Java中是允许父类引用指向子类对象的,过程中发生了向上转型,但是要注意向上转型后父类引用只能访问父类的成员变量和方法(子类没有重写父类方法的情况下),就栗子来说,Animal animal = new Bird("小鸟", 2);这个animal引用只能访问eat方法,不能访问子类Bird的fly方法。

class Animal 
    public String name;
    public int age;
    public Animal(String name, int age) 
        this.name = name;
        this.age = age;
    
    public void eat() 
        System.out.println(this.name + "正在吃饭!");
    

class Bird extends Animal

    public Bird(String name, int age) 
        super(name, age);
    
    public void fly() 
        System.out.println(this.name + "正在飞翔!");
    


public class UpClass 
    public static void main(String[] args) 
        Animal animal = new Bird("小鸟", 2);
        animal.eat();
    


⭐️常见发生向上转型有三种情况:

  1. 将子类对象的地址传给父类引用。
  2. 将子类对象的地址以参数形式传递给父类类型的形参。
  3. 在返回值为父类引用类型的方法中,将子类对象的地址作为返回值。
class Animal 
    public String name;
    public int age;
    public Animal(String name, int age) 
        this.name = name;
        this.age = age;
    
    public void eat() 
        System.out.println(this.name + "正在吃饭!");
    

class Bird extends Animal

    public Bird(String name, int age) 
        super(name, age);
    
    public void fly() 
        System.out.println(this.name + "正在飞翔!");
    


public class UpClass 
    public static void func1(Animal an) 
        an.eat();
    
    public static Animal func2() 
        return new Bird("小鸟", 2);
    
    public static void main(String[] args) 
        Animal animal1 = new Bird("小鸟", 2);
        animal1.eat();
        func1(new Bird("小鸟", 2));
        Animal animal2 = func2();
        animal2.eat();
    

2.2运行时绑定

2.2.1概念

运行时绑定也称动态绑定,它是在程序运行时所发生的一种行为。发生运行时绑定是基于向上转型的,所谓运行时绑定就是通过父类引用调用父类与子类同名的覆盖(重写/覆写)方法。为什么叫做运行时绑定呢?因为在程序运行的时候,方法的重写才发生,我们来看看一个含有重写代码的编译后的反汇编,会发现编译后调用的还是父类的方法。

class Animal 
    public String name;
    public int age;
    public Animal(String name, int age) 
        this.name = name;
        this.age = age;
    
    public void eat() 
        System.out.println(this.name + "正在吃饭!");
    

class Bird extends Animal

    public Bird(String name, int age) 
        super(name, age);
    
    public void fly() 
        System.out.println(this.name + "正在飞翔!");
    

    @Override
    public void eat() 
        System.out.println(this.age + this.name + "正在慢慢地吃饭!");
    

class Cat extends Animal
    public Cat(String name, int age) 
        super(name, age);
    
    public void cute() 
        System.out.println(name + "忙着装可爱!");
    

    @Override
    public void eat() 
        System.out.println(this.age + this.name + "正在安静地吃饭!");
    

public class OverFunc 
    public static void main(String[] args) 
        Animal animal1 = new Bird("小鸟", 2);
        animal1.eat();
        Animal animal2 = new Cat("小猫", 1);
        animal2.eat();
    

2.2.2重写方法

重写又称覆写,覆盖,顾名思义就是使用一个方法覆盖/覆写/重写了一个方法,但是不是任意两个方法都能发生重写,发生重写是有要求的,就像方法重载一样,都有着特定的规定,重写方法是在子类当中的,父类中的方法是被重写方法。

⭐️方法重写的条件:

  1. 方法名相同
  2. 参数列表相同(包括形参个数与形参类型)
  3. 返回值类型相同(协变返回类型除外)

协变返回类型指的是子类中的成员函数的返回值类型不必严格等同于父类中被重写的成员函数的返回值类型,而可以是更 “狭窄” 的类型。比如,子类与父类类型的返回值就有这样的关系。

⭐️方法重写注意事项:

  1. static修饰的方法不能重写。
  2. final修饰的方法不能重写。
  3. 权限为private的方法不能重写。
  4. 子类重写父类方法时,子类中重写的方法访问权限必须大于等于父类中被重写的方法。

知道了方法的重写我们来看看前面那个程序是否真如我们所说的,在运行时发生了重写。

结果是调用了子类的eat,说明重写是在程序运行时发生的,程序编译并没有发生重写。

2.3编译时绑定

刚刚介绍了运行时绑定,现在来说说编译时绑定,所谓的编译时绑定,就是程序发生了方法的重载,这个重载在编译时就会发生,不像重写是在运行时发生的。我们来看一段含有重载的代码的反汇编就明白了。

public class Func 
    public static int add(int a, int b) 
        return a + b;
    
    public static int add(int a, int b, int c) 
        return a + b + c;
    
    public static int add(int a, int b, int c, int d) 
        return a + b + c +d;
    
    public static void main(String[] args) 
        int x = add(1,2);
        int y = add(1, 2, 3);
        int z = add(1, 2, 3 ,4);
        System.out.println(x);
        System.out.println(y);
        System.out.println(z);
    


⭐️方法重载的规则(复习):

  1. 方法名相同。
  2. 参数列表不同(包括形参类型与个数)。
  3. 返回值不做要求。

编译时绑定也称静态绑定,是程序编译时期的一种行为。

⭐️重写与重载的区别:

区别重写重载
概念方法名相同,参数列表相同,返回值相同(协变返回类型除外)方法名相同,参数列表不同,返回值不做要求
范围继承关系(父子类关系)一个类
限制子类的重写方法的权限要大于父类被重写的访问权限,访问权限不能是private,不能被final,static修饰无权限要求,final,static修饰也可发生重载

2.4向下转型

向下转型用的很少且不安全。所谓的向下转型就是在向上转型的基础上,使用子类引用的值等于父类引用的值,这个过程需要强制转换。

还是看一个老栗子:

class Animal 
    public String name;
    public int age;
    public Animal(String name, int age) 
        this.name = name;
        this.age = age;
    
    public void eat() 
        System.out.println(this.name + "正在吃饭!");
    

class Cat extends Animal 
    public Cat(String name, int age) 
        super(name, age);
    
    public void cute() 
        System.out.println(name + "忙着装可爱!");
    

    @Override
    public void eat() 
        System.out.println(this.age + this.name + "正在安静地吃饭!");
    

class Bird extends Animal 

    public Bird(String name, int age) 
        super(name, age);
    
    public void fly() 
        System.out.println(this.name + "正在飞翔!");
    

    @Override
    public void eat() 
        System.out.println(this.age + this.name + "正在慢慢地吃饭!");
    

如果向下转型后类型匹配:

public class Instancesof 
    public static void main(String[] args) 
        Animal animal = new Cat("小猫", 1);
        Cat cat = (Cat)animal;//向下转型
        cat.eat();
    


向下转型后类型不匹配:


public class Instancesof 
    public static void main(String[] args) 
        Animal animal = new Cat("小猫", 1);
        Bird Bird = (Bird) animal;
        bird.eat();
    


为什么说它是不安全的呢,因为一个父类可能会有多个子类,向下转型可能会造成类型不匹配。所以为了避免这种异常的发生,我们在使用向下转型的时候,往往需要先判断类型是否匹配,这里我们可以使用关键字instanceof对向下转型后子类引用是否类型匹配。

public class Instancesof 
    public static void main(String[] args) 
        Animal animal = new Cat("小猫", 1);
        if (animal instanceof Bird) 
            Bird bird = (Bird) animal;
            bird.eat();
        
        if (animal instanceof Cat) 
          Cat cat = (Cat)animal;
          cat.eat();
        
    

2.5多态

2.5.1理解多态

在理解向上转型情况下,我们就能来试着理解多态了,先不讲多态的概念,因为很抽象,先来一个例子。我们生活中存在许许多多的形状,比如圆,正方形,菱形,五角星,三角形等等,使用继承和重写的方法实现一个类Shape输出多种形状。

首先定义Shape类,在这个类中创建一个draw方法,然后创建其他的具体图形类继承Shape。

public class Shape 
    public void draw() 
        System.out.println("我要打印一个形状!");
    


class Circle extends Shape
    @Override
    public void draw() 
        System.out.println("○");
    

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

class Rhombus extends Shape
    @Override
    public void draw() 
        System.out.println("◇");
    

class Flower extends Shape
    @Override
    public void draw() 
        System.out.println("❀");
    

class Star extends Shape
    @Override
    public void draw() 
        System.out.println("☆");
    

class Square extends Shape
    @Override
    public void draw() 
        System.out.println("□");
    

方式1:使用父类数组存放子类的对象,然后循环调用子类的draw重写方法。

public class Test 
    public static void main(String[] args) 
        Flower flower = new Flower();
        Square square = new Square();
        Star star = new Star();
        Rhombus rhombus = new Rhombus();
        Circle circle = new Circle();
        Triangle triangle = new Triangle();

        Shape[] shapes = flower, square, star, rhombus, circle, triangle;
        for (Shape shape :shapes) 
            shape.draw();
        JavaSE入门学习21:Java面向对象之接口(interface)

JavaSE第11篇:面向对象之接口多态

JavaSE入门学习18:Java面向对象之多态

Python全栈之路系列----之-----面向对象4接口与抽象,多继承与多态)

JavaSE入门学习18:Java面向对象之多态

JavaSE之面向对象(下)