面向对象中多态、抽象类、接口

本文主要探讨面向对象编程中的多态概念,包括向上转型、动态绑定和方法重写。接着讲解抽象类及其特点,指出抽象类不能被实例化,主要用于被继承。最后介绍了接口的使用,包括实现多个接口的场景,并通过实例展示了如何通过实现Comparable接口对对象数组进行排序。
摘要由CSDN通过智能技术生成

面向对象

多态

1.向上转型

父类引用 引用子类对象

class Shape {
    public void draw() {System.out.println("Shape::draw()");}
}
class Rect extends Shape{
    @Override
    public void draw() {System.out.println("♦");}
}
class Cycle extends  Shape {
    @Override
    public void draw() {System.out.println("●");}
}
public static void main1(String[] args) {
    Shape shape1 = new Rect(); //通过Shape来引用子类对象,向上转型
    shape1.draw(); //父类引用调用draw方法,子类也重写了draw方法,那么就发生了动态绑定
    Shape shape2 = new Cycle();
    shape2.draw();
}
//其中:
Rect rect = new Rect();
//向上转型可以写成
    Shape shape1 = new Rect();
//或
    Rect rect = new Rect();
    Shape shape1 = rect;

此时 shape1是一个父类 (Shape) 的引用, 指向一个子类 (Rect) 的实例. 这种写法称为 向上转型.

2.动态绑定

如上面的程序,子类Rect、Cycle重写了父类Shape的draw方法,父类引用调用draw方法时,调用子类重写的draw方法,此时叫发生动态绑定

运行时绑定:通过父类引用 调用父类和子类同名的覆盖方法。

3.方法重写

子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).

a、方法名称相同

b、参数列表相同【参数的个数+参数的类型】

c、返回值相同【特殊:返回值也可以是协变类型】

注意:

​ 1、普通方法可以重写, static方法不能重写

​ 2、private修饰的方法不能重写

​ 3、final修饰的方法不能重写

​ 4、子类方法的访问权限要大于等于父类的访问权限

编译时绑定:通过函数的重载实现的。编译的时候,会根据你给的参数的个数和类型,在编译期,确定你最终调用的一个方法。

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

    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 {
        public static void main(String[] args) {
            D d = new D();
        }
    }
// 执行结果
D.func() 0

多态总结:无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.

抽象类

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

class Shape {
    public abstruct void draw();
}

在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体
代码).
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类

1、包含抽象方法的类,叫做抽象类。

2、 是抽象方法: 一个没有具体实现的方法,被abstract修饰。

3、 抽象类是不可以被实例化的。new

Shape shape = new Shape();
// 编译出错
    Error:(30, 23) java: Shape是抽象的; 无法实例化

4、因为不能被实例化,所以,这个抽象类,其实只能被继承。抽象类最大的作用,就是为了被继承。

5、抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用

 abstract class Shape {
        abstract public void draw();
        void func() {
            System.out.println("func");
        }
    }
    class Rect extends Shape {
...
    }
    public class Test {
        public static void main(String[] args) {
            Shape shape = new Rect();
            shape.func();
        }
    }
// 执行结果
    func

6、一个普通类,继承了一个抽象类,那么这个普通类当中,需要重写这个抽象类的所有的抽象方法。

7、抽象方法不能是 private 的。

abstract class Shape {
        abstract private void draw();
    }
// 编译出错
    Error:(4, 27) java: 非法的修饰符组合: abstractprivate

8、一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法

9、结合第8点,当A类再次被一个普通类继承后,那么A和B的这两个抽象类当中的抽象方法,必须被重写

10、抽象类不能被final修饰,抽象方法也不可以被final修饰。

抽象类的作用:抽象类存在的最大意义就是为了被继承.

抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.

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

接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.

  1. 使用interface来修饰一个接口。interface IA{}

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

  3. 接口中的方法一定是抽象方法, 因此可以省略 abstract,

  4. 接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字default来修饰这个方法。

    interface IShape {
        public abstract void draw();//抽象方法 默认public abstract
        //public void func(){ }//普通方法不能有具体方法的实现
        default public void func(){ //如果想实现,就用关键字default
            System.out.println("hahaha");
        }
        /*
        default public void func2(){
            System.out.println("hahaha");
        }
        public static void funcStatic(){ //可以用static
            System.out.println("hahaha");
        }*/ //一键注释ctrl+shift+/ ,消除注释ctrl+shift+/
    }
    
  5. 接口当中,可以有static的方法。

  6. 接口里面的所有的方法都是public的,因此可以省略 public

  7. 抽象方法,默认是public abstract的

  8. 接口是不可以被通过关键字new来实例化的

  9. 类和接口之间的关系是通过implements实现的。

  10. 当一个类实现了一个接口,就必须要重写接口当中的抽象方法。

  11. 接口当中的成员变量,默认是public static final修饰的

  12. 当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public.

  13. 一个类可以通过关键字extends继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements实现多个接口,接口之间使用逗号隔开就好。

  14. 那么接口和接口之间会存在什么样的关系呢?

接口和接口之间可以使用extends来操作他们的关系,此时,这里面意为:拓展。 implements 继承接口. 此时表达的含义是 “实现”。

一个接口B 通过extends来 拓展另一个接口C的功能。 此时当一个类通过implements实现这个接口B的时候,此时重写的方法不仅仅是B的抽象方法,还有他从C接口,拓展来的功能[方法]。

一个错误的代码:

interface IShape {
    abstract void draw() ; // 即便不写public,也是public
}
class Rect implements IShape {
    void draw() { //这里必须写public
        System.out.println("□") ; //权限更加严格了,所以无法覆写。
    }
}

完整格式是:

interface IMessage{
    public static final int a = 10;
    public abstract void func();
}

简略版是:

interface IMessage{
    int a = 10;
    void func();
}
实现多个接口

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

class Animal{
    protected String name;
    public Animal(String name){
        this.name = name;
    }
}
//不是所有的动物都会飞,所以,不能写到Animal类当中。如果写到另一个类当中,也不行,因为
//一个类不能继承多个类。 所以,接口。
interface IFlying{
    void fly();
}
interface ISwimming{
    void swim();
}
interface IRunning{
    void run();
}

//鸟飞
class Bird extends Animal implements IFlying{
    public Bird(String name){
        super(name);
    }
    @Override
    public void fly() {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+"正在游泳");}
}

//鸭子飞,跑,游泳
class Duck extends Animal implements IRunning,ISwimming,IFlying{
    public Duck(String name){
        super(name);
    }
    @Override
    public void run() { System.out.println(this.name+"正在跑");}

    @Override
    public void swim() { System.out.println(this.name+"正在游泳");}

    @Override
    public void fly() { System.out.println(this.name+"正在飞!");}
}

public class Test4 {
    public static void runFunc(IRunning iRunning){
        iRunning.run();
    }

    public static void swimFunc(ISwimming iSwimming) {
        iSwimming.swim();
    }
    public static void flyFunc(IFlying iFlying){
        iFlying.fly();
    }
    public static void main1(String[] args) {
        runFunc(new Duck("鸭子"));
        runFunc(new Frog(("青蛙")));
    }
    public static void main2(String[] args){
        flyFunc(new Duck("鸭子"));
        flyFunc(new Bird("鸟"));
    }

    public static void main(String[] args) {
        swimFunc(new Duck("鸭子"));
        swimFunc(new Frog("青蛙"));
    }
}
接口使用实例

给对象数组排序:

给定一个学生:

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

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }

再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序)

public static void main(String[] args) {
    Student[] students = new Student[3];
    students[0] = new Student(12,"张杰",99.9);
    students[1] = new Student(23,"任嘉伦",90.5);
    students[2] = new Student(36,"朱一龙",85);
    System.out.println(Arrays.toString(students));
}

按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

 Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to
java.lang.Comparable

仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定?是按照年龄还是名字还是分数呢?这就需要我们额外指定.
让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法

class Student implements Comparable<Student>{
    ......
//谁调用这个方法 谁就是this
@Override
public int compareTo(Student o){
   /*if (this.age>o.age){
        return 1;
    }else if (this.age==o.age){
        return 0;
    }else{
        return -1;
    }*/
    //return this.age-o.age;
    //return o.age-this.age;
     return (int)(this.score-o.score);
    //return this.name.compareTo(o.name);
}
}
public static void main(String[] args){
    ......
    /*if (student1.compareTo(student2)>0){

    }*/
    System.out.println(student[0].compareTo(student[1]));
}

然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;再次执行程序, 结果就符合预期了.

注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.

以为 Comparable方法麻烦一些,所以用Comparator更灵活,更改比较规则更方便

class AgeComparator implements Comparator<Student> {

    @Override
    public int compare (Student o1,Student o2){
        return o1.age - o2.age;
    }
}
class ScoreComparator implements Comparator<Student>{
    @Override
    public int compare(Student o1,Student o2){
        return (int)(o1.score - o2.score);
    }
}
class NameComparator implements Comparator<Student>{
    @Override
    public int compare(Student o1,Student o2){
        return o1.name.compareTo(o2.name);
    }
}
public static void main(String[] args) {
    Student[] students = new Student[3];
    students[0] = new Student(12,"张杰",99.9);
    students[1] = new Student(23,"任嘉伦",90.5);
    students[2] = new Student(36,"朱一龙",85);

    AgeComparator ageComparator = new AgeComparator();
    ScoreComparator scoreComparator = new ScoreComparator();
    NameComparator nameComparator = new NameComparator();
    Arrays.sort(students,ageComparator);//比较 默认是从小到大的顺序
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值