前言
继承和多态刚开始是比较难理解的,要做到多看,多敲代码。
一、继承
有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联。
为了方便理解,我们先看下面一段代码。
(1)例如, 设计一个类表示动物
// Animal.java public class Animal { public String name; public Animal(String name) { this.name = name; } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } } // Cat.java class Cat { public String name; public Cat(String name) { this.name = name; } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } } // Bird.java class Bird { public String name; public Bird(String name) { this.name = name; } } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } public void fly() { System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); }
仔细分析, 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系:
1)这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.
2)这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.
(2)语法规则
class 子类 extends 父类 { }
对于上面的代码, 可以使用继承进行改进. 此时我们让 Cat 和 Bird 继承自 Animal 类, 那么 Cat 在定义的时候就不必再写 name 字段和 eat 方法。
class Animal { public String name; public Animal(String name) { this.name = name; } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } } class Cat extends Animal { public Cat(String name) { // 使用 super 调用父类的构造方法. super(name); } } class Bird extends Animal { public Bird(String name) { super(name); } public void fly() { System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); } } public class Test { public static void main(String[] args) { Cat cat = new Cat("小黑"); cat.eat("猫粮"); Bird bird = new Bird("圆圆"); bird.fly(); } }
extends 英文原意指 “扩展”. 而我们所写的类的继承, 也可以理解成基于父类进行代码上的 “扩展”.
例如我们写的 Bird 类, 就是在 Animal 的基础上扩展出了fly方法.
(3)注意
如果我们把 name 改成 private, 那么此时子类就不能访问了
(4)字段和方法访问权限
private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访问
二、多态
首先需要实现多态需要满足一些条件:
1) 继承关系上满足向上转型
2)子类和父类 有同名的覆盖/重写方法
3)通过父类对象的引用去调用这个重写的方法
完成以上三步就会发生动态绑定,动态绑定是多态的基础.
一、向上转型
(1)在刚才的例子中, 我们写了形如下面的代码
Bird bird = new Bird("圆圆");
这个代码也可以写成这个样子
Bird bird = new Bird("圆圆"); Animal bird2 = bird; // 或者写成下面的方式 Animal bird2 = new Bird("圆圆");
此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型.
(2)向上转型方法有三种
1)直接赋值
Animal animal = new Dog("圆圆",7);
2)方法的参数,传参是向上转型
public static void funcl(Animal animal){ } public static void main(String[] args){ Dog dog = new Dog("圆圆",6); func1(dog); }
3)返回值向上转型
public static Animal func2(){ Dog dog = new Dog("圆圆",19); return dog; }
二、重写
针对刚才的 eat 方法来说:
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
(1)关于重写的注意事项
1)最基本的返回值 参数列表 方法名必须是一样的
2)被重写的方法的访问修饰限定符在子类中要大于等于父类的
3)被private修饰的方法不可以被重写的
4)被static修饰的方法是不可以被重写的
5)被final修饰的方法是不可以被重写的
三、动态绑定
子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
对前面的代码稍加修改, 给 Bird 类也加上同名的 eat 方法, 并且在两个 eat 中分别加上不同的日志.
// 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 指向
Bird 类型的实例.
针对 animal1 和 animal2 分别调用 eat 方法, 发现
animal2.eat() 实际调用了子类的方法
四、多态的好处
类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可