java面向对象编程_多态_抽象类_接口

举报
bug郭 发表于 2022/07/31 13:14:28 2022/07/31
【摘要】 多态在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。多态(百度词条) 向上转型子类对象赋值给了父类引用该对象只能访问父类的字段和方法! 直接赋值子类对象赋值给了父类引用class Animal{ protected String name;...

多态

在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。多态(百度词条)

向上转型

子类对象赋值给了父类引用

该对象只能访问父类的字段和方法!

直接赋值

子类对象赋值给了父类引用

class Animal{
    protected String name;
    protected int  age;
    public void eat(){
        System.out.println("animal eat()!");
    }
}
class Dog extends Animal{
    protected int height;
    public void running(){
        System.out.println("dog running()!");
    }
}
public class Test_1 {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal = new Dog();  //1.子类对象赋值给了父类引用
        Animal animal1 = new Dog(); //2.和 1 等价
        animal.eat(); //调用父类中的方法
        //error animal只能访问父类中的字段和方法!
        animal.height=1;
        animal.running();
    }
}

在这里插入图片描述

方法传参

//方法传参
class Animal{
    protected String name;
    protected int  age;
    public void eat(){
        System.out.println("animal eat()!");
    }
}
class Dog extends Animal{
    protected int height;
    public void running(){
        System.out.println("dog running()!");
    }
}
public class Test_2 {
    public static void func(Animal animal){
        animal.eat();
    }
    public static void main(String[] args) {
        Dog dog = new Dog();
        func(dog);
        func(new Dog()); //子类对象赋值给了父类引用!
    }
}

方法返回

//方法返回
class Animal{
    protected String name;
    protected int  age;
    public void eat(){
        System.out.println("animal eat()!");
    }
}
class Dog extends Animal{
    protected int height;
    public void running(){
        System.out.println("dog running()!");
    }
}
public class Test_2 {
    public static Animal func(){
       return new Dog();  //子类对对象返回给了父类引用!
    }
    public static void main(String[] args) {
       Animal animal = func();
       animal.eat();
    }
}

在这里插入图片描述

动态绑定

动态绑定就是,当子类重写了父类的方法时,向上转型后,对象调用与重写的方法,访问的是子类对象中的重写方法!

//运行时绑定
class Animal{
    protected String name;
    protected int  age;
    public void eat(){
        System.out.println("animal eat()!");
    }
}
class Dog extends Animal{
    protected int height;
    @Override
    public void eat() {
        System.out.println("dog eat()!");
    }
    public void running(){
        System.out.println("dog running()!");
    }
}
public class Test_2 {
    public static void main(String[] args) {
       Animal animal = new Dog();
       animal.eat(); //运行时绑定!
    }
}

在这里插入图片描述为啥要叫运行时绑定呢?
难道说编译的时候没有绑定?
确实如此,当我们查看java的反汇编代码时就会发现,编译期间anmial调用的是自己的eat方法,但是运行时却绑定了子类的eat()!
在这里插入图片描述

java反汇编代码步骤:

  • 找到,我们需要反汇编代码类的字节码文件
  • 在命令符的窗口下,输入javap -c 类名代码 回车即可!

理解多态

我们想一想多态可以帮助我们做些什么!
bug郭的理解

  • 运行时绑定使用场景
    我们可以重写父类的方法,实现多态。调用重写的方法,一个父类可以有多个子类,不同的子类重写了不同的方法,实现了真正意义上的多态!

eg:打印图形

//类的实现者
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_1{
    public static void main(String[] args) {
        Shape shape1 = new Flower();
        Shape shape2 = new Cycle();
        Shape shape3 = new Rect();
        drawShape(shape1);
        drawShape(shape2);
        drawShape(shape3);
    }
    // 打印单个图形
    public static void drawShape(Shape shape) {
        shape.draw();
    }
}

在这里插入图片描述
使用多态的好处是什么?

  • 类调用者对类的使用成本进一步降低。

封装是让类的调用者不需要知道类的实现细节。
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低。

  • 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
    “圈复杂度” :就是代码中的分支和循环;
  • 可扩展能力更强。
    如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。

向下转型

我们知道向上转型是子类对象赋值给了父类引用!
那向下转型莫不就是:父类对象赋值给了子类引用~

并不常见~了解一下即可!

//向下转型
class Animal {
    protected String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println("我是一只小动物");
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void eat(String food) {
        System.out.println("我是一只小鸟");
        System.out.println(this.name + "正在吃" + food);
    }
    public void fly() {
        System.out.println(this.name + "正在飞");
    }
}
public class Test_2 {
    public static void main(String[] args) {
        Animal animal = new Bird("鸽鸽"); //先借助向上转型
        animal.eat("脐橙");
        //animal.fly;  //error
        Bird bird;
        bird = (Bird)animal; //向下转型需要强转
        bird.fly();
    }
}

在这里插入图片描述可以看到向下转型步骤比较繁琐,通常要借助向上转型!
而且我们需要确定是否为父子类关系!避免异常!

利用instanceof 关键字可以判定一个引用是否是某个类的实例。
若真返回true,若假返回false!

在这里插入图片描述

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

//坑
class B {
    public B() {
        // do nothing
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}
public class Test_3{
    public static void main(String[] args) {
        D d = new D();
    }
}

在这里插入图片描述bug郭看了半天愣是没整明白为啥这个代码运行结果是这样!!!
我的理解:创建子类对象d会调用自己的构造方法,子类要先帮助父类构造,而父类中调用了子类重写的方法,动态绑定了;
我们并有执行子类中的 private int num = 1;语句!所以num此时并没有赋值!所以为0
正解

  • 构造D 对象的同时, 会调用B的构造方法.
  • B 的构造方法中调用了func方法, 此时会触发动态绑定, 会调用到D 中的func
  • 此时 D对象自身还没有构造, 此时num 处在未初始化的状态, 值为 0.

结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏但又及难发现的问题!

抽象类

基本语法

在刚才的打印图形例子中, 我们发现, 父类Shape中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class)

abstract class Shape { 
    abstract public void draw(); 
} 

draw 方法前加上abstract关键字, 表示这是一个抽象方法。同时抽象方法没有方法体(没有{ }, 不能执行具体代码)。
在这里插入图片描述

对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类。
在这里插入图片描述

注意事项

  • 抽象类不能直接实例化。

在这里插入图片描述

  • 抽象类可以有一般类一样的字段和方法,语法相同。
abstract class Shape {
    protected  int longth;
    protected int wide;
    public int area(){
        return longth*wide;
    }
     abstract public  void draw();
}
  • 子类继承父类抽象类,必须重写父类中的抽象方法!
    在这里插入图片描述

  • 抽象方法不能是 private

在这里插入图片描述我们知道private修饰只能在当前类中访问,而抽象方法需要子类实现!

抽象类的作用

抽象类存在的最大意义就是为了被继承。

抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题!

接口

基本语法

//定义接口类型
interface IAnimal{
    //抽象方法
    // public static final 字段
}
  • 使用interface 定义一个接口
    在这里插入图片描述

  • 接口中的方法一定是抽象方法, 因此可以省略 abstract
    接口中的方法一定是public, 因此可以省略public
    在这里插入图片描述

  • 接口中只能包含抽象方法,对于字段来说, 接口中只能包含静态常量(final static)

interface IAnimal{
    // public static final 字段 可以省略public static final
    String name = "animal"; //因为是final 修饰,需要赋初值!
    int age = 18;
    //抽象方法
    public abstract void speak();
    void eat(); //省略public abstract
}
  • 子类 使用 implements继承接口。 此时表达的含义不再是 “扩展”, 而是 “实现”

  • 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例。

  • 接口不能单独被实例化。

  • 扩展(extends)vs 实现(implements)

扩展指的是当前已经有一定的功能了, 进一步扩充功能。
实现指的是当前啥都没有, 需要从头构造出来。

  • 接口不能用限定符修饰
    在这里插入图片描述

实现多个接口

接口弥补了java无法多继承的缺陷!
我们可以实现多个接口
基本语法

有时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的。然而 Java 中只支持单继承, 一个类只extends 一个父类。 但是可以同时实现多个接口, 也能达到多继承类似的效果。

class 类名 implement interfa1,interface2,....interfaceN{
     //实现所有接口中的方法!
}

实例一
下面我们通过类来表示张三

//实现多个接口
interface IAnimal{
    String name = "animal";
    int age = 18;
    void speak();
    void eat();
}
interface IPeople{
    String iQ = "140";
    void study();
}
class Zhansan implements IAnimal,IPeople{
    @Override
    public void speak() {
        System.out.println("Speak Chinese!");
    }
    @Override
    public void eat() {
        System.out.println("Eat food!");
    }
    @Override
    public void study() {
        System.out.println("Study java!");
    }
}

public class Test_6 {
    public static void main(String[] args) {
        Zhansan zhansan = new Zhansan();
        zhansan.eat();
        zhansan.speak();
        zhansan.study();
    }
}

在这里插入图片描述实例二
现在我们通过一个类来表示一组动物。

class Animal { 
  protected String name; 
    public Animal(String name) { 
        this.name = name; 
    } 
}

另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”。

interface IFlying { 
    void fly(); 
} 
interface IRunning { 
    void run(); 
} 
interface ISwimming { 
    void swim(); 
}

接下来我们创建几个具体的动物
猫, 是会跑的。

class Cat extends Animal implements IRunning { 
    public Cat(String name) { 
        super(name); 
    } 
    @Override 
    public void run() { 
        System.out.println(this.name + "正在用四条腿跑"); 
    } 
}

在这里插入图片描述

鱼, 是会游的。

class Fish extends Animal implements ISwimming { 
    public Fish(String name) { 
        super(name); 
    } 
    @Override 
    public void swim() { 
        System.out.println(this.name + "正在用尾巴游泳"); 
    } 
}

在这里插入图片描述

青蛙, 既能跑, 又能游(两栖动物)

class Frog extends Animal implements IRunning, ISwimming { 
    public Frog(String name) { 
        super(name);
    } 
    @Override 
    public void run() { 
        System.out.println(this.name + "正在往前跳"); 
    } 
    @Override 
    public void swim() { 
        System.out.println(this.name + "正在蹬腿游泳"); 
    } 
} 

在这里插入图片描述

提示,IDEA中使用 ctrl + i 快速实现接口
在这里插入图片描述在这里插入图片描述

还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”

class Duck extends Animal implements IRunning, ISwimming, IFlying { 
    public Duck(String name) { 
        super(name); 
    } 
    @Override 
    public void fly() { 
        System.out.println(this.name + "正在用翅膀飞"); 
    } 
    @Override 
    public void run() { 
        System.out.println(this.name + "正在用两条腿跑"); 
    } 
    @Override 
    public void swim() { 
        System.out.println(this.name + "正在漂在水上"); 
    } 
} 

在这里插入图片描述

上面的代码展示了 Java面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。

继承表达的含义是 is - a 语义, 而接口表达的含义是 具有xxx 特性 。

猫是一种动物, 具有会跑的特性。
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢?

时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力!

例如, 现在实现一个方法, 叫 “散步”

public static void walk(IRunning running) { 
    System.out.println("我带着伙伴去散步"); 
    running.run(); 
}

在这个walk方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的, 就行

Cat cat = new Cat("小猫"); 
walk(cat); 
Frog frog = new Frog("小青蛙"); 
walk(frog); 

在这里插入图片描述
甚至参数可以不是 “动物”, 只要会跑

class Robot implements IRunning { 
    private String name; 
    public Robot(String name) { 
        this.name = name; 
    } 
    @Override 
    public void run() { 
        System.out.println(this.name + "正在用轮子跑"); 
    } 
} 
Robot robot = new Robot("机器人"); 
walk(robot); 

在这里插入图片描述

接口的使用实例

我们java系统包中的很多类都实现了很多接口,使得该类具有某种属性

给对象排序!

//创建student类
class Student{
    private String name;
    private double score;
    public Student(String name,double score){
      this.name = name;
      this.score = score;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
public static void main(String[] args) {
		//学生对象数组
        Student[] students = new Student[]{
                new Student("张三",88.7),
                new Student("李四",67),
                new Student("王五",98),
        };
        Arrays.sort(students); //排序
        System.out.println(students);
    }

显然,我们无法排序对象类型的数据,
报异常,说我们Student类没有实现Comparable接口!
在这里插入图片描述
Comparable接口中的抽象方法 compareTo
方法详细信息

int compareTo(T o)将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。

实现程序必须确保sgn(x.compareTo(y)) == -sgn(y.compareTo(x))所有xy。 (这意味着x.compareTo(y)必须抛出异常if y.compareTo(x)引发异常。)

实施者还必须确保关系是可传递的: (x.compareTo(y)>0 && y.compareTo(z)>0)表示x.compareTo(z)>0

最后,实施者必须确保x.compareTo(y)==0意味着sgn(x.compareTo(z)) == sgn(y.compareTo(z)) ,对于所有z

强烈建议,但不要严格要求(x.compareTo(y)==0) == (x.equals(y)) 。 一般来说,任何实现Comparable接口并违反这种情况的类应清楚地表明这一点。 推荐的语言是“注意:此类具有与equals不一致的自然排序”。

在前面的描述中,符号sgn( ) 表达式表示数学符号函数,其定义根据表达式的值是否为负,零或正返回的-1一个,0,或1

参数
o -要比较的对象。
结果
负整数,零或正整数,因为该对象小于,等于或大于指定对象。
异常
NullPointerException- 如果指定的对象为空
ClassCastException- 如果指定的对象的类型阻止它与该对象进行比较。

看到这么多文字,是不是头都大了!
没有关系,bug郭也头大,不过我知道咋用

//创建student类并且实现Comparable接口
class Student implements Comparable{
    private String name;
    private double score;
    public Student(String name,double score){
      this.name = name;
      this.score = score;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
    @Override  //实现compareTo方法
    public int compareTo(Object o) {
        Student s = (Student)o; //对o强转, o指的是待比较对象
        if (this.score > s.score) {    //当前对象的score值大
            return -1;
        } else if (this.score< s.score) { //对象o的score值大
            return 1;       
        } else {
            return 0;
        }
    }
}

我们可以看到,这时排的升序,如果要降序就只需将大于号变成小于号即可!
在这里插入图片描述sort 方法中会自动调用compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回0;

注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备compareTo这样的能力. 通过重写compareTo 方法的方式, 就可以定义比较规则。
为了进一步加深对接口的理解, 我们可以尝试自己实现一个sort方法来完成刚才的排序过程(使用冒泡排序)!

public static void sort(Comparable[] array) { 
    for (int bound = 0; bound < array.length; bound++) { 
        for (int cur = array.length - 1; cur > bound; cur--) { 
            if (array[cur - 1].compareTo(array[cur]) > 0) { 
                // 说明顺序不符合要求, 交换两个变量的位置 
                Comparable tmp = array[cur - 1]; 
                array[cur - 1] = array[cur]; 
                array[cur] = tmp; 
            } 
        } 
    } 
} 

在这里插入图片描述

接口间的继承

接口可以继承一个接口, 达到复用的效果. 使用extends关键字!

interface IAmphibious extends IRunning, ISwimming {

}
class Frog implements IAmphibious {
    @Override
    public void run() {
        System.out.println("Frog run!");
    }
    @Override
    public void swim() {
        System.out.println("Frog swim!");
    }
}
public class Test_1 {
    public static void main(String[] args) {
           Frog frog = new Frog();
           frog.run();
           frog.swim();
    }
 }

在这里插入图片描述

通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog类, 就继续要实现 run 方法,也需要实现 swim方法!

接口间的继承相当于将多个接口合并在一起!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。