JavaSE学习之--继承和多态(三)

简介: JavaSE学习之--继承和多态(三)

JavaSE学习之--继承和多态(二)+https://developer.aliyun.com/article/1413490

3.方法返回

   方法的返回类型可以是类 ,通过将方法的返回值设置为父类引用,让返回的对象被父类引用引用!!

// 父类
class Animal{
    public void eat() {
        System.out.println("eating");
    }
};
class Cat extends Animal{
    String name;
    int age;
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public void eat() {
        System.out.println("cat is eating!");
    }
}
class Dog extends Animal{
    String name;
    int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public void eat() {
        System.out.println("dog is eating!");
    }
};
//class Cat extends Animal{};
public class Test {
    public static void eat() {
        System.out.println("我在吃饭");
    }
    // 父类引用可以做返回值,还是让父类引用指向返回的对象
    public static Animal buyAnimal(String var) {
        if("狗".equals(var)) {
            return new Dog("小狗",2);
        } else if ("猫".equals(var)) {
            return new Cat("小米",20);
        }else {
            return null;
        }
    }
    public static void main(String[] args) {
        Animal animal1  =buyAnimal("狗");
        Animal animal2  =buyAnimal("猫");
        animal1.eat();
        animal2.eat();
    }

3.向上转型的优点:

1.实现代码的通用性,实现多态性

 通过父类引用子类对象,我们可以创建出更通用的代码,不需要去关注具体子类的类型

2.可以避免很多类型检查和强制类型转换

class Animal {
    public void makeSound() {};
}
class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵叫!");
    }
}
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪叫!");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Animal[] animals = new Animal[2];
        animals[0] = new Cat();
        animals[1] = new Dog();
        for (Animal animal: animals) {
            // 不适用向上转型
            // 需要进行类型检查和强制类型转换
            if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.makeSound();
            } else if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.makeSound();
            }
        }
        // 使用向上转型
        // 不需要关注具体的子类对象,代码更通用
        for (Animal animal2: animals) {
            animal2.makeSound();
        }
    }
}

对于这个代码,在 animals[0] = new Cat();实际上就发生了向上转型,animals[0]代表一个父类引用,new Cat()代表创建了一个新的Cat对象

4.向上转型的缺点:

无法使用子类特有的方法

如果想通过父类引用去使用子类特有的方法,就要进行“向下转型”,也就是使父类引用发生强制类型转换!

2.向下转型:

   在很多时候子类有其特有的方法,而又不能通过重写来调用,这时候就需要通过向下转型来调用子类特有的方法,向下转型就是将父类引用强制类型转化为子类对象

class Animal{};
class Dog extends Animal{
    public void bark() {
        System.out.println("汪汪叫!");
    }
}
public class testdemo1 {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        // 向下转型
        Dog dog1 = (Dog)animal1;
        dog1.bark();// 输出汪汪叫!
    }
}

   但向下转型是一个很“危险”的操作,因为父类引用是一个较大的范围,你强制转化的类型是一个小范围,会出现意想不到的错误;同时,如果多个类继承于一个父类,要注意向下转型时不要转错类型!如果转换错误,编译器会报错:

所以,为了避免这种错误,需要利用关键字instanceof来检验当前引用所指向的类型

class Animal{};
class Dog extends Animal{};
class Cat extends Animal{};
public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal animal1 = new Dog();
        // 父类引用            引用子类类型
        if(animal1 instanceof Dog) {// 利用instanceof运算符进行引用类型检查
            // 强制类型转化
            Dog dog = (Dog)animal1;
            System.out.println("类型检查成功");
        }else {
            System.out.println("ClassCastException!!!");
        }
    }
}

3.动态绑定

  动态绑定是面向对象编程非常重要的一个概念,也被叫做运行时绑定或运行多态性。他是指在运行时才确定对象的实际类型,并根据实际类型调用相应的方法,在通俗一点说就是只有在运行时我才知道引用的对象是谁,我该调用的方法是谁,这之前谁都不知道!!!

class Animal {
    public void makeSound() {};
}
class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵叫!");
    }
}
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪叫!");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Animal animal = new Dog();// 动态绑定
        animal.makeSound();// 实际调用的是Dog的方法  只有在运行时才知道实际对象是Dog
    }
}

4.方法重写

 重写(override):是子类对父类非static,final,private修饰,非构造方法的方法重写;通过重写,可以在子类内部实现属于子类的行为;

1.重写的规则:

1.被重写的父类方法不能被static,final,private修饰,不能是构造方法

2.重写的方法必须要和父类方法保持一致,即返回值,方法名,参数列表都要完全相同

3.子类的重写方法的权限要>=父类被重写方法的权限,比如父类方法的权限是public,那子类重写的方法就不能是protected!

4.子类中重写的方法可以被@Override 注解来显式指定,有了这个注解,可以多一层检验,比如父类的方法名是eat,而在子类中却写成ate,此时编译器就会报错(因为要求子类的方法名要和父类被重写方法的名称一致!)

2.重写与重载的区别:

重载是类方法多态性的体现,重写是子类与父类之间多态性的体现

方法重载是一种静态绑定,即在编译过程中就知道对应的方法(根据用户的传参)

方法重写是一种动态绑定,只有在运行时才能明确调用的方法

5.多态

 再说回多态,以上内容都是为了理解多态而阐述的,所谓多态就是子类调用同一个父类方法时所产生的行为不同的思想,以下是多态的条件:

1.必须存在父类与子类的继承关系

2.必须有方法的重写

3.必须通过父类的引用来调用方法

多态的体现:在程序运行时,引用对象的不同,所产生的结果也不同!

1.多态的优点:

1.降低圆圈复杂度

 圆圈复杂度是反应一段代码理解难易程度的表示,如果一段代码平铺直叙,那他的圆圈复杂度就很低,此代码易于理解,但如果代码中含有大量的条件语句,循环语句,那么此代码的圆圈复杂度就很高,不易于理解(要判断的东西太多)

class Shape {
    public void drawMap() {};
}
class Rect extends Shape {
    @Override
    public void drawMap() {
        System.out.println("矩形");
    }
}
class Flower extends Shape {
    @Override
    public void drawMap() {
        System.out.println("❀");
    }
}
class Cycle extends Shape {
    @Override
    public void drawMap() {
        System.out.println("⚪");
    }
}
public class Test2 {
    // 依次打印⚪矩形⚪矩形花
    public static void main(String[] args) {
        // 1.不使用多态  要判断子类的具体类型
        Rect rect = new Rect();
        Flower flower = new Flower();
        Cycle cycle = new Cycle();
        String[] shapes = {"cycle","rect","cycle","rect","flower"};
        for (String shape:shapes) {
            if(shape.equals("cycle")) {
                cycle.drawMap();
            } else if (shape.equals("rect")) {
                rect.drawMap();
            }else {
                flower.drawMap();
            }
        }
         // 2.使用多态
        Shape[] shapes = {new Cycle(),new Rect(),new Cycle(),new Rect(),new Flower()};
        for (Shape shape: shapes) {
            shape.drawMap();
        }
    }
}

 

2.可拓展性强

 比如对于shape类,现在有一个新的子类Triangle,可以直接新创建一个Triangle类,让其包含drawMap方法;如果不使用多态,则需要在打印时多添加一个if-else语句

2.多态的缺陷:代码运行效率低

3.多态的注意事项:

1.属性不具有多态性,通过父类引用只能访问到父类的属性,而无法访问到子类的属性

2.构造方法没有多态性

先来看如下代码:

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

 实例化d对象时,会先调用父类的构造方法,父类的构造方法中调用了func方法,此时会触发动态绑定,调用子类的func方法,而此时 private int num = 1;这段代码并未被执行,也就是说num还未被初始化,未被初始化则被赋值为0,所以打印:D.func() 0

结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触 发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

目录
相关文章
|
Java 编译器
【JAVASE】继承 中
【JAVASE】继承
【JavaSE专栏63】多态,父类引用子类的对象,面向对象编程中的重要概念
【JavaSE专栏63】多态,父类引用子类的对象,面向对象编程中的重要概念
|
安全 Java
【JAVASE】多态 下
【JAVASE】多态
|
Java 程序员 编译器
【JavaSE】一起学继承
【JavaSE】一起学继承
|
11月前
|
Java 编译器
JavaSE学习之--继承和多态(一)
JavaSE学习之--继承和多态
78 0
|
11月前
|
Java
JavaSE学习之--继承和多态(二)
JavaSE学习之--继承和多态(二)
88 0
|
11月前
|
存储 Java 编译器
JavaSE学习之--抽象类,接口,内部类(三)
JavaSE学习之--抽象类,接口,内部类(三)
48 0
|
11月前
|
Java
JavaSE学习之--抽象类,接口,内部类(一)
JavaSE学习之--抽象类,接口,内部类(一)
115 0
|
11月前
|
存储 Java 机器人
JavaSE学习之--抽象类,接口,内部类(二)
JavaSE学习之--抽象类,接口,内部类(二)
71 0
|
安全 Java 程序员
JavaSE继承和多态
JavaSE继承和多态