【Java】多态

多态

多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
在这里插入图片描述

多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法

重写

我们已经了解过继承 那么什么是重写呢?

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, **返回值和形参都不能改变。即外壳不变,核心重写!**重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写的规则
  1. 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  2. 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  3. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  4. 父类被static、private修饰的方法、构造方法都不能被重写
  5. 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写
哪些情况下不能重写

在这里插入图片描述

重写(override)和重载(overload)的区别

在这里插入图片描述
即方法重载是一个类的多态性的表现,而方法重写是子类与父类的一种多态性表现
在这里插入图片描述

重写的设计原则

对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。

向上转型和向下转型

向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

Animal animal = new Cat("元宝",2);

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
在这里插入图片描述
向上转型的使用场景有哪些呢?

直接赋值
Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
方法传参
public static void eatFood(Animal a){
	a.eat();
}
//调用时/
eatFood(cat);
eatFood(dog);
//用父类对象接受 传入一个子类对象
方法返回
//作为返回值直接返回子类对象 类似于直接赋值
public static Animal buyAnimal(String var){
	if("狗".equals(var) ){
		return new Dog("狗狗",1);
	}else if("猫" .equals(var)){
		return new Cat("猫猫", 1);
	}else{
		return null;
	}
}

//使用/
Animal animal = buyAnimal("狗");
优缺点

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

向下转型

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。

public class TestAnimal {
	public static void main(String[] args) {
		Cat cat = new Cat("元宝",2);
		Dog dog = new Dog("小七", 1);
		// 向上转型
		Animal animal = cat;
		animal.eat();
		animal = dog;
		animal.eat();
		if(animal instanceof Cat){
			cat = (Cat)animal;
			cat.mew();
		}
		if(animal instanceof Dog){
			dog = (Dog)animal;
			dog.bark();
		}
	}
}

绑定

静态绑定

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

我们前面了解过的重载就是一种静态绑定

public class test {
    public static int add(int a, int b) {
        return a + b;
    }

    public static double add(double a, double b) {
        return a + b;
    }
//两个add方法构成了重载
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        System.out.println(add(a, b));
        double c = 6.5;
        double d = 8.4;
        System.out.println(add(c, d));
    }
}

运行结果
在这里插入图片描述
也就是说在编译器编译时就可以根据你调用的参数分辨出你调用的是哪个方法,这就叫做静态绑定

动态绑定

也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体
调用那个类的方法。

package testDemo;
class Animal {
    String name;
    int age;
    float weight;

    public void eat() {
        System.out.println(this.name + "正在吃饭!!!");
    }

    public void sleep() {
        System.out.println(this.name + "正在睡觉!!!");
    }
}

class Dog extends Animal {

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

class Cat extends Animal {
    public void bark() {
        System.out.println(this.name + "喵喵喵~~~");
    }
}

public class test1 {
    public static void main(String[] args) {
        Animal animals = new Dog();
        animals.eat();
    }
}

这里我们向上转型创建了一个Dog对象 但是当父类Animals使用
运行结果
在这里插入图片描述
这里我们虽然编译的时候是父类的eat方法 但结果却调用了子类的eat方法 这个过程就叫做运行时绑定!!
在这里插入图片描述

再看多态

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

public class Animals {//父类Animals
    String name;
    int age;

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

public class Cat extends Animals {//子类
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {//方法的重写
        System.out.println(this.name + "吃鱼");
    }
}

public class Dog extends Animals {//子类
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {//方法的重写
        System.out.println(this.name + "吃狗粮");
    }
}
以上是方法实现者的写的代码///

以下是方法调用者写的代码/
public class TestAnimals {
    public static void testFunc(Animals animals) {
        animals.eat();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("miaomiao", 3);
        Dog dog = new Dog("wangcai", 9);
        testFunc(cat);
        testFunc(dog);

        //向上转型:出现在testFunc 方法中通过参数的传递体现的向上转型 方法需要Animals但我们实际上传入了Cat				   和Dog类
        //方法的重写:在Dog类和Cat类中的eat方法被重写
        //动态绑定:编译的时候调用的是Animals的eat方法 但是实际上调用的是子类的方法 体现了动态绑定
    }
}

当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的a 引用指向的是哪个类型(哪个子类)的实例. 此时 a这个引用调用 eat方法可能会有多种不同的表现(和 a 引用的实例相关), 这种行为就称为 多态.

多态的优缺点

优点

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如
果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.
如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10

例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:

public class Shape {
    public void draw() {
        System.out.println("画图形!");
    }
}

class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个圆");
    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个矩形");
    }
}

class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("花一朵花");
    }
}

class drawShape {
    public static void drawshape() {
        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();
            }
        }
    }

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

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单

public class drawShape2 {
    public static void drawshape() {
        Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
                new Rect(), new Flower()};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }

    public static void main(String[] args) {
        drawshape();
    }
}
  1. 可扩展能力更强

缺陷

  1. 属性没有多态性 当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
  2. 构造方法没有多态性
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java多态指的是同一个方法名可以根据不同的对象调用出不同的行为。具体来说,多态是一种面向对象编程的特性,实现多态的方式主要有两种:方法重载和方法覆盖。方法重载指的是在一个类中定义多个同名方法,但这些方法有不同的参数列表,编译器会根据参数列表的不同选择合适的方法进行调用。方法覆盖指的是子类重写父类的方法,使得在使用父类对象调用该方法时,实际调用的是子类中的方法。 多态的好处在于,它可以提高代码的灵活性和可扩展性。通过多态,我们可以为不同的对象提供不同的行为,从而使得程序更加具有扩展性。此外,多态还可以让程序的调用更加简洁、清晰,提高了代码的可读性和可维护性。 下面是一个简单的Java多态的例子: ```Java class Animal { public void makeSound() { System.out.println("动物发出声音"); } } class Cat extends Animal { public void makeSound() { System.out.println("猫发出“喵喵”的声音"); } } class Dog extends Animal { public void makeSound() { System.out.println("狗发出“汪汪”的声音"); } } public class PolymorphismExample { public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Cat(); Animal animal3 = new Dog(); animal1.makeSound(); // 动物发出声音 animal2.makeSound(); // 猫发出“喵喵”的声音 animal3.makeSound(); // 狗发出“汪汪”的声音 } } ``` 在上面的例子中,Animal是一个父类,Cat和Dog是Animal的子类。Animal类中定义了一个makeSound()方法,Cat和Dog类分别重写了这个方法。在main()方法中,我们创建了三个Animal类型的对象,并分别调用它们的makeSound()方法。由于animal2和animal3都是Animal类型的对象,但实际上它们分别是Cat和Dog类型的对象,因此在调用它们的makeSound()方法时,实际上是调用了它们各自的实现,也就是Cat类和Dog类中重写的makeSound()方法。这就是Java多态的表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉着的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值