目录
1、向上转型:
向上转型发生的时机:
1、 直接赋值
2、方法传参
3、方法返回
1、直接赋值
class Animal{
public String name;
private int age;
}
class Dog extends Animal{
public int leg;//...指代狗的特有属性
public void wangwang(){
System.out.println("wang!");
}//指代狗的特有方法
}
public class TestDemo {
public static void main(String[] args) {
Dog dog=new Dog();
//直接赋值
Animal animal=dog;
Animal animal=new Dog();
}
}
2、方法传参:
class Animal{
//父类
}
class Dog extends Animal{
//子类
}
public class TestDemo {
public static void func(Animal animal){
}
public static void main(String[] args) {
Dog dog=new Dog();
//方法传参
Animal animal=new Dog();
func(animal);
func(new Dog());
func(dog);
}
}
此时形参 animal 的类型是 Dog(基类), 实际上对应到 Animal(父类) 的实例。
3、方法返回
class Animal{
//父类
}
class Dog extends Animal{
//子类
}
public class TestDemo {
//方法返回
public static Animal func1(){
return new Dog();
}
public static void main(String[] args) {
}
}
此时方法func1 返回的是一个 Animal 类型的引用, 但是实际上对应到 Dog的实例。
2、向下转型
向下转型就是父类对象转成子类对象
class Animal{
//父类
}
class Dog extends Animal{
public void wang(){
System.out.println("wang!");
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal=new Dog();
//animal.wang();//报错
//向下转型
// (Dog) 表示强制类型转换
Dog dog =(Dog) animal;//父类赋给子类
dog.wang();
}
}
- 注:在强转之前, animal 的类型是 Animal, 此时编译器 没有 wang() 方法,虽然 animal 实际引用的是一个 Bird 对象, 但是编译器是以 animal 的类型来查看有哪些方法的。
不安全性:(上面的例子之所以没有异常是 animal 本质上是Dog,才可以引用Dog的方法)
如下:animal 本质上引用的是一个 Dog 对象,是不能转成 Bird 对象的,运行时就会抛出异常。
class Animal{
//父类
}
class Dog extends Animal{
public void wang(){
System.out.println("wang!");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("fly");
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal=new Dog();
//向下转型
Bird bird=(Bird)animal;
bird.fly();
}
}
运行时异常:(类型转换异常)
所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换:(如下:animal不是Bird的实例,运行结果为空)
class Animal{
//父类
}
class Dog extends Animal{
public void wang(){
System.out.println("wang!");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("fly");
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal=new Dog();
if(animal instanceof Bird) {
//判断animal 是不是Bird的实例;animal是不是Bird的实例对像
Bird bird = (Bird) animal;
bird.fly();
}
}
}
3、运行时绑定(动态绑定)
- 父类引用引用子类对象
- 通过父类引用 调用父类和子类的同名覆盖方法。
此时就会发生运行时绑定 这也是多态的前提。
在构造方法中调用重写的方法
class Animal{
public Animal() {
eat();
System.out.println("父类构造");
}
public void eat(){
System.out.println("父类中的eat");
}
}
class Dog extends Animal{
public Dog(){
super();
System.out.println("子类构造");
}
public void eat(){
System.out.println("子类的eat(重写的)");
}
}
public class TestDemo{
public static void main(String[] args) {
Animal animal=new Dog();
}
}
//运行结果
子类的eat(重写的)
父类构造
子类构造
运行时,先调用Dog不带参数的构造方法(即②),但在此方法中 先要帮助父类构造一个不带参数的构造方法super()(即①),在①方法中先去调用了eat()方法,此时调用的eat方法是子类中重写的方法(即④)(触发运行时绑定),输出“子类中的eat”(eat调用完毕),然后输出“父类构造”(父类构造完毕), 然后输出“子类构造”。
4、重写
针对刚才的 eat 方法来说: 子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)。
重写(override):覆盖、覆盖
1、方法名相同
2、参数列表相同
3、返回值相同
注:
权限大小:private < default < protected < public
- 子类的访问权限一定要大于等于父类的访问权限
- 要重写的方法一定不可以是static方法
- 要重写的方法一定不可以被final修饰
重写与重载的区别:
5、多态
1、理解多态(polypeptide)
我们可以写一些只关注父类的代码, 就能够同时兼容各种子类的情况。
代码示例: 打印多种形状:
class Shap{
public void draw(){
System.out.println("父类draw");
}
}
class Rect extends Shap{
//方法中没有重写draw()
}
class Circle extends Shap{
@Override//注解:表示下面的这个方法是重写的
public void draw(){
System.out.println("○");
}
}
class Flower extends Shap{
@Override//注解:表示下面的这个方法是重写的
public void draw(){
System.out.println("❀");
}
}
//...................分割线.................
public class TestDemo1 {
public static void drawMap(Shap shap){
shap.draw();
}
public static void main(String[] args) {
Rect rect=new Rect();//Rect中未重写draw()
drawMap(rect);//此时调用的就是父类的draw()
Circle circle=new Circle();//Circle中重写了draw()
drawMap(circle);//此时调用的就是Circle中的draw()
Flower flower=new Flower();
drawMap(flower);
}
}
//运行结果:
父类draw
○
❀
上面代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的,
public static void drawMap(Shap shap){
shap.draw();
}
当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例,此时 shape 这个引用调用 draw 方法( shap.draw() )时,可能会有多种不同的表现 (和 shape 对应的实例相关), 这种行为就称为多态。
多态顾名思义, 就是 "一个引用, 能表现出多种不同形态"。
2、多态的好处
1) 类调用者对类的使用成本进一步降低
- 封装是让类的调用者不需要知道类的实现细节.
- 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低。
2) 能够降低代码的 "圈复杂度", 避免使用大量的 if - else 。
例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下
public static void drawShapes() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单。
public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
3) 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低,对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了。
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}