面向对象编程的三大特性之一:多态。
1.继承的存在(继承是多态的基础,没有继承就没有多态).
2.子类重写父类的方法(多态下调用子类重写的方法).(子类不一定需要将方法进行重写)
3.父类引用变量指向子类对象(子类到父类的类型转换).
从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的作用:消除类型之间的耦合关系。
例如:
Bird bird = new Bird("圆圆");
这个代码也可以写成
Bird bird = new Bird("圆圆");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("圆圆");
此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为向上转型。
例如:
Animal bird2 = bird;
这就是直接赋值的一种表现,此时bird的类型是Bird,而bird2的类型是Animal,将bird赋值给bird2时,bird就会发生向上转型,成为Anmal类型。和基本数据类型中的自动提升相似。
作用:
当我们调用一个方法时,会有参数的传入。利用向上转型,就可以在设置方法传入参数的类型时,设置成父类类型,这样子类的对象都可以作为这个方法的参数传入。
例如:
当一个方法需要返回一个类型时,我们也可以将这个类型设置成父类类型,这样想返回其子类也可以,不用重新去写一个方法。
例如:
当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
// 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 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定。
向下转型就相当于基本类型中的大类型转换成小类型,需要使用强转。
Bird bird = (Bird)animal;
注意animal原本的原本的类型,如果他原来不是Bird类型就会出现异常
Animal animal = new Cat("小猫");
Bird bird = (Bird)animal;
bird.fly();
// 执行结果, 抛出异常
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Bird
at Test.main(Test.java:35)
当我们需要使用子类独有的方法时,就需要使用向下转型才能使用。
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,但是也有一定的用途.
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了.
Animal animal = new Cat("小猫");
if (animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
在子类中调用父类的属性和方法,就需要使用super关键字。
public Bird(String name) {
super(name);
}
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
@Override
public void eat(String food) {
// 修改代码, 让子调用父类的接口.
super.eat(food);
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
注意:
在构造方法中调用重写的方法
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
此时会触发动态绑定,调用子类中重写的方法,不会调用父类的方法。
这个父类本身没有太大的意义,然后其子类有共同的行为,但是各自的表现形式都不相同。此时我们就可以将这个父类定义成抽象类。当一个类被定义成抽象类,这个类就无法产生对象。
我们用abstract
来表示一个类是抽象类。
1.abstract不能和private同时修饰一个类
2.抽象类里面没有方法体的方法称为抽象方法
3.抽象类是普通类的一个超集,普通类有的属性和方法抽象类都有。
4.抽象类可以没有抽象方法,有抽象方法的类一定是抽象类
5.抽象类的子类必须完成抽象类里面的抽象方法,不然也要定义成抽象类。
6.当一个子类多层继承了多个抽象类,此时如果子类不想定义为抽象类,就需要完成所有抽象方法。
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.
接口表示一种规则,一种规定。就像生活中的电脑的插口,或者插排上面的插口,想使用三角口,就必须将插头设计成三角形,不然无法使用。
我们使用interface
来修饰接口
例如:
interface USB{
//常量字段
//抽象方法
}
当我们需要去使用这个USB接口时,我们就必须按照他的规定,就是纪要满足他的常量片段,还要完成他给的抽象方法。
我们使用implements
表示一类实现了一个接口,即满足了这个规定。
实现的这个类继承了接口中所有的常量字段。
class Keybord implements USB{
}
现在我们通过类来表示一组动物
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 + "正在蹬腿游泳");
}
}
还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”
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 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
先继承后实现。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
...
}
通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog 类, 就继续要实现 run 方法,也需要实现 swim 方法.
接口间的继承相当于把多个接口合并在一起