【面向对象编程——组合、多态、抽象类、接口】

一、组合

和继承类似,组合也是一种表达类之间关系的方式,也能达到代码重用的效果。
组合表示 has - a 语义;
继承表示 is - a 语义。

例如一个学校:

public class Student{
       ...
}
public class Teacher{
       ...  
}
//学校里“包含”若干位学生和老师
public class School{
  public Student[] students;
  public Teacher[] teachers;
}

二、多态

多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义
顾名思义,就是“一个引用,能表现出多种不同的形态”。

实现多态有 3 个必要条件:继承重写向上转型

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。

使用多态的好处是什么?
1)类调用者对类的使用成本进一步降低。封装能让类的调用者不需要知道类的实现细节。多态能让类的调用者连这个类是什么都不用知道,只需要知道这个对象具有某个方法即可。
2)能够降低代码的“圈复杂度”,避免使用大量的if - else
3)可扩展能力强。如果需要新增一种新的形状,使用多态的方式的方法代码改动成本也比较低

接下来我们来详细介绍“重写”和“向上转型”

1.重写

在讲重写之前先复习一下重载

重载:发生在同一个类中,定义了若干个方法名称相同,参数列表不同的一组方法
(重载和重写完全不一样,不要混淆)

什么是重写?

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

也就是说,方法的重写一定发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他完全相同的方法。

注意:
1.普通方法可以重写,static修饰的静态方法不能重写
2.重写中子类的方法的访问权限不能低于父类的访问权限。
3.重写的方法返回值类型不一定和父类的方法相同。(最好写成相同的,特殊情况除外)

// Bird.java 
public class Bird extends Animal { 
 @Override 
 private void eat(String food) { 
 ... 
 } 
} 

我们可以用@Override注解来显式指定。有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发 现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写. 我们推荐在代码中进行重写方法时显式加上 @Override 注解。

2.向上转型

public class Animal{
    public String name;
}
public class Bird extends Animal {
    public Bird (String name) {
        super(name);
    }
} 

此时我们创建对象可以写成 Bird bird = new Bird (“圆圆”);
也可以写成:Bird bird = new Bird (“圆圆”);Animal bird2 = bird;
或者,Animal bird2 = new Bird (“圆圆”);

此时bird2是一个父类的引用,指向一个子类的实例,这种写法称为向上转型

向上转型发生的时机:直接赋值、方法传参、方法返回

1.方法传参

public class Test {
  public static void main(String[] args){
     Bird bird = new Bird("圆圆")feed(bird);
  }
  public static void feed(Animal animal){
     animal.eat("谷子")
  }
}
//执行结果
//圆圆正在吃谷子

此时形参animal的类型是Animal,实际上对应到Bird的实例

2.方法返回

public class Test {
  public static void main(String[] args){
     Animal animal = findMyAnimal();
  }
  public static Animal findMyAnimal(){
  Bird bird = new Bird("圆圆")return bird;
  }
}

此时方法findMyAnimal返回的是一个Animal类型的引用,但实际上对应到Bird的实例。

3.动态绑定

当子类和父类中出现同名方法时候,再调用会出现什么情况呢?

 
// Animal.java 
public 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); 
 } 
} 
// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小鸟"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Animal animal1 = new Animal("圆圆"); 
 animal1.eat("谷子"); 
 Animal animal2 = new Bird("扁扁"); 
 animal2.eat("谷子"); 
 } 
} 
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子

此时我们发现:

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

因此,在java中,调用某个类的方法,究竟执行了那段代码,要看究竟这个引用指向的是父类对象,还是子类对象。这个过程是程序运行时决定的,因此成为动态绑定

三、抽象类

语法规则

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

注意事项:
1)抽象类不能直接实例化
2)抽象方法不能是private权限
3)抽象类中可以包含其他非抽象方法,也可以包含字段。这个非抽象方法和普通方法的规则是一样的,可以被重写,也可以被子类直接调用。
4)子类继承了抽象类,就必须强制子类重写抽象类中的所有抽象方法(子类是普通类),也满足单继承局限,一个子类只能extends一个抽象类

抽象类的作用
抽象类存在的最大意义就是为了被继承。
抽象类本身不能被实例化,要想要,只能创建该类抽象类的子类,然后让子类重写抽象类中的抽象方法。

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

很多语法的存在,意义就是为了“预防出错”,例如我们曾经用过的final也是类似的,创建的变量用户不去修改,不就相当于常量吗?但是加上final能预防在不小心误修改的时候,编译器及时提醒我们。

四、接口

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

使用场景:
1.接口表示具备某种能力/行为,子类实现接口时不是 is - a ,而是具备这种行为或者能力
例如:“游泳”这一行为,Person类可以满足,Animal类也能满足
2.接口表示一种规范或者标准
“USB接口”、“5G标准”

语法规则

interface IShape { 
 void draw(); 
} 
class Cycle implements IShape { 
 @Override 
 public void draw() { 
 System.out.println("○"); 
 } 
} 
public class Test { 
 public static void main(String[] args) { 
 IShape shape = new Rect(); 
 shape.draw(); 
 } 
} 
  • 使用interface定义一个接口
  • 接口中的方法一定是抽象方法,可以省略abstract
  • 接口中的方法一定是public,因此可以省略public
  • Cycle使用 implement继承接口。此时表达的含义不是“扩展”,而是“实现”
  • 在调用的时候同样可以创建一个接口引用,对应到一个子类的实例
  • 接口不能单独被实例化。
  • 接口中只能包含抽象方法。对于字段来说,接口中只能包含静态常量(final static),可以省略final static

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

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

定义接口,飞,跑,游

//在IDEA中,使用Ctrl+i快速实现接口
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 + "正在蹬腿游泳"); 
 } 
}

鸭子,三栖动物,能跑能游能飞

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面向对象编程中最常见的用法:一个类继承一个父类,同时实现多个接口。
接口表达的含义是“具有xxx特性”。

总结

抽象类和接口都是java中多态的常见使用方式,注意区分两者
在这里插入图片描述

核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有抽象方法。


版权声明:本文为m0_66551463原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。