Java--多态

目录

多态的概念

向上转型

直接赋值

方法传参

方法返回

向上转型的优缺点 

方法重写

动态绑定

静态绑定

向下转型

使用多态的优缺点

避免在构造方法中调用重写方法

多态的概念

多态通俗来讲就是多种形态,具体来说,当不同对象完成某个相同的行为时,会产生出不同的状态。多态要实现必须满足以下条件:1.向上转型   2.子类和父类由同名的覆盖或重写方法   3.通过父类对象,调用父类和子类重写方法   4.满足以上三点只能说明会发生动态绑定。那什么是静态绑定什么是动态绑定呢?理解了上述内容,你才能理解什么是多态。其实多态就是一种思想,十分抽象。

向上转型

把子类对象给父类

有三种方式可以发生向上转型:直接赋值、方法传参、方法返回

直接赋值

先有一父类Animal,子类有Dog和Cat,按上面的说发,把子类对象给父类,我们可以这样写代码:

class Animal {
    public String name;
    public int age;

    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(this.name+"正在吃饭!");
    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class Dog extends Animal {
    public Dog(String name,int age) {
        super(name,age);
    }
    public void wangWang() {
        System.out.println(this.name+"汪汪汪!");
    }
}

class Bird extends Animal {
    public Bird(String name,int age) {
        super(name, age);
    }
    public void fly() {
        System.out.println(this.name+"正在飞!");
    }
}
public class Test8_1 {
    public static void main(String[] args) {
        //向上转型
        Dog dog = new Dog("小狗",2);
        Animal animal = dog;   
    }
}

main方法中的两条语句合并起来就相当于:

        //父类引用  引用了子类对象
        Animal animal = new Dog("小狗",2);

方法传参

我们在Test8_1中写一个静态方法func1,参数是Animal类,实例化两个子类对象,将子类对象传入方法func1中,此时也发生了向上转型

public class Test8_1 {
    public static void main(String[] args) {
        Dog dog1 = new Dog("小狗1",2);
        Bird bird1 = new Bird("小鸟1",1);
        func1(dog1);
        func1(bird1);
    }
    public static void func1(Animal animal) {

    }
}

方法返回

将方法的返回值类型设为父类Animal,方法中返回子类对象,在main方法中用Animal类接受返回值。

 public static void main(String[] args) {      
        Animal animal1 = func2();
        Animal animal2 = func3();
    }
    public static Animal func2() {
        return new Dog("小狗2",2);
    }
    public static Animal func3() {
        return new Bird("小鸟2",1);
    }

向上转型的优缺点 

向上转型的优点是让代码实现更加灵活,缺陷是不能调用到子类特有的方法。

看一段代码,我们来理解上面这句话:

    public static void main(String[] args) {
        //父类引用  引用了子类对象
        Animal animal = new Dog("小狗1",2);
        animal.eat();
        animal.wangWang;//报错
    }

我们实例化了一个对象,此时animal可以调用父类Animal的eat方法,但是不能调用子类Dog的方法,也就是说此时 向上转型只能调用父类的方法,不能调用子类特有的方法。

方法重写

现在我们在Dog子类中也写一个和父类同名的eat方法,看看程序运行结果会不会有变化。

    public void eat() {
        System.out.println(this.name+"吃狗粮!");
    }

输出结果:

为什么这是调用的又是子类的eat方法了呢?

大家有没有注意到,父类的eat和子类的eat方法的方法名、返回值类型、参数列表(个数,类型,顺序)都一样。

其实这就是方法重写或方法覆盖。方法的重写:1.方法名必须相同 2.参数列表必须相同(类型、个数、顺序) 3.返回值必须相同,可以不能但是两者返回值类型必须是父类和子类的关系(协变类型)。满足这三点之后,才构成了方法的重写。

不能发生重写的情况:1.被private修饰的方法不能进行重写。2.被static修饰的方法不能进行重写。3.被fianl修饰的方法(密封方法)不能进行重写。4.访问修饰限定符 private < 默认权限 < protected < public ,要求子类修饰限定符的权限要大于等于父类修饰限定符的权限。5.方法的返回值类型不同且不是父子类关系。6.构造方法不能进行重写。

加上@Override注解,可以帮助提醒你方法重写是否正确,如果方法重写不正确,@Override会提示。

动态绑定

接下来我们再来观察main方法中的代码:

    public static void main(String[] args) {
        //向上转型
        Animal animal = new Dog("小狗1",2);
        //通过父类引用 调用这个父类和子类重写的方法
        animal.eat();
    }

main方法中只做了两步:1.向上转型 2.通过父类引用,调用这个父类域子类重写的方法。程序的结果是:调用了子类的方法。这个过程我们就叫做:运行时绑定或者动态绑定。运行时绑定是多态的基础。

静态绑定

除了动态绑定,我们还有静态绑定或者编译时绑定,就是在编译时就能确定调用哪个方法了。比如:方法的重载。通过方法的参数等,在编译的时候就能确定她调用的是哪个方法。

我们在out目录下找到字节码文件所在的位置,在目录框中打开cmd,输入命令 javap -c 文件名

找到main函数

向下转型

将一个子类对象经过向上转型后当成父类方法使用,再无法调用子类的方法,但有时候需要调用子类特有的方法,此时将父类引用再还原为子类对象就称为向下转型。

    public static void main(String[] args) {
        Animal animal1 = new Bird("小鸟1",1);
        //animal1.fly();此时不能调用子类特有的fly()方法

        //向下转型 但是不安全
        Bird bird1 = (Bird) animal1;
        bird1.fly();
    }

为什么不安全呢?我们再看一段代码

    public static void main(String[] args) {
        Animal animal2 = new Dog("小狗2",2);
        Bird bird2 = (Bird) animal2;
        bird2.fly();
    }

此时这段代码并没有报红,我们运行以下看结果:

因此向下转型必须做检查,通过关键字instanceof把代码改进一下:

        Animal animal2 = new Dog("小狗2",2);
        if (animal2 instanceof Bird) {
            Bird bird2 = (Bird) animal2;
            bird2.fly();
        }else {
            System.out.println("不能飞!");
        }

目前为止,我们只是理解了动态绑定,但是到底什么是多态?多态的思想是什么样子的?下面我们看一个例子:

class Shape {
    public void draw() {
        System.out.println("画图形!");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("圆");
    }
}
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

我们定义了一个父类Shape,父类中有一个draw方法,子类Rect、Circle、Flower都重写了父类中的draw方法,现在我们要画出:圆、矩形、❀。我们应该如何做呢?

public class Test8_2 {
    public static void drawMap(Shape shape) {
        shape.draw();
    }

    public static void main(String[] args) {
        Circle circle = new Circle();
        Rect rect = new Rect();
        Flower flower = new Flower();

        drawMap(circle);
        drawMap(rect);
        drawMap(flower);
    }
}

这是输出结果为:

下面我们来观察一下代码,理解到底什么是多态

使用多态的优缺点

1.能够降低代码的“圈复杂度”,避免使用大量的if-else。我们可以粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构。

如果在没有多态的情况下,我们要打印“圆 矩形 圆 矩形 ❀”我们应该如何打印呢?

public class Test8_2 {
    public static void drawMaps() {
        Circle circle = new Circle();
        Rect rect = new Rect();
        Flower flower = new Flower();

        String[] strings = {"circle","rect","circle","rect","flower"};
        for (String s:strings) {
            if (s.equals("circle")) {
                circle.draw();
            } else if (s.equals("rect")) {
                rect.draw();
            } else if (s.equals("flower")) {
                flower.draw();
            }
        }
    }

    public static void main(String[] args) {
        drawMaps();
    }

运行结果:但此时你会发现代码中有大量的if-else,那么我们用多态应该怎么做呢?

public class Test8_2 {
    public static void drawMaps() {
        Circle circle = new Circle();
        Rect rect = new Rect();
        Flower flower = new Flower();

        Shape[] shapes = {circle,rect,circle,rect,flower};//发生了向下转型
        for (Shape shape:shapes) {
            shape.draw();
        }
    }

    public static void main(String[] args) {
        drawMaps();
    }

这是代码就简洁许多了。

2.可扩展能力很强

如果我们还想花三角形,直接定义一个三角形的子类即可。

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("🔺");
    }
}

 在drawMaps方法中实例化三角形的对象就可以了

        Circle circle = new Circle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        Triangle triangle = new Triangle();

        Shape[] shapes = {circle,rect,circle,rect,flower,triangle};//发生了向下转型
        for (Shape shape:shapes) {
            shape.draw();
        }

多态的缺点:代码的运行效率降低。1.属性没有多态性。2.构造方法没有多态性

避免在构造方法中调用重写方法

现在有一个父类B,其中有一构造方法调用了func(),子类D中重写了func()

class B {
    //构造方法
    public B() {
        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);
    }
}

现在在main方法中实例化一个D类的对象

public class Test8_3 {
    public static void main(String[] args) {
        D d = new D();
    }
}

当代码执行时,会调用D类默认的不带参数的构造方法,根据我们前面讲过的知识,会先执行父类B的构造方法,再执行子类D的构造方法,现在问题来了,父类B的构造方法中的func()是子类的还是父类的呢?我们运行代码看结果

现在我们知道,父类构造方法中的func()调用的是子类的func()方法,但是这里的num值为什么是0呢?因为父类调用子类的func()时,子类还没有完成构造,num并没有被初始化。这种代码不建议写,尤其是在父类的构造方法中忽然调用重写方法,可能会出现一些隐藏的但是又极难发现的问题。

这部分的语法难以理解,希望你可以多看几遍。

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值