【Java SE】多态的详解(下)

简介: 今天的内容有一点点抽象,但是不难,内容也不多。

那么接下来我们就来看看向上转型的三种多态体现方法:

第一种:直接赋值->子类对象赋值给父类对象:

class TestAnimal {
    public static void main(String[] args) {
        Animal animal1 = new Dog("旺财", 5);
        animal1.eat();
        Animal animal2 = new Cat("小咪", 2);
        animal2.eat();
    }
}

结合上面的学习:同一种行为,发生在不同对象的身上,他们的产生的结果也就不同,自然调用的就是对应对象的 eat 方法。

结果也很符合我们刚开始的结论,果然同一种行为,不同对象产生的结果是不同的!

第二种:方法传参,形参作为父类类型引用,可以接收任意子类对象

class TestAnimal {
    //用户自己写的方法
    public static void eatFood(Animal animal) {
        animal.eat(); //直接通过方法可以进行实现多态,根据传递的参数不同
    }
    public static void main(String[] args) {
        eatFood(new Dog("小黑", 4));
        eatFood(new Cat("小咪", 1));
    }
}

这里可能在传参的地方有些不理解,其实在这我们是传了一个匿名对象过去,在后期也会讲解,如果不理解,也可也先 new 一个对象出来,在把对应的引用传进去,也是一样的。

第三种:通过返回值,返回任意子类对象

class TestAnimal {
    //通过返回值实现向上转型
    public static Animal buyAnimal(String animal) {
        if ("狗".equals(animal)) {
            return new Dog("狗", 2);
        } else if("猫".equals(animal)) {
            return new Cat("猫", 3);
        } else {
            return null;
        }
    }
    public static void main(String[] args) {
        Animal animal1 = buyAnimal("狗");
        animal3.eat();
        Animal animal2 = buyAnimal("猫");
        animal4.eat();
    }
}

equals 是字符串比较方法,相等返回 true 不相等返回 false,上面这种用法其实还是比较少的,本质就是你给我传递什么,我对应给你 new 一个对象回来,然后你在那父类类型接收,从而也可以实现向上转型。

这就是多态的体现,如上代码,一个引用调用同一个方法,因为这个引用 引用的对象不一样,导致调用这个方法所表现的行为不一样,这种思想,就叫做多态!

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

3.3 向下转型

这个其实用的很少,而且不安全,这里我们简单了解下即可:

向下转型无非就是把一个父类引用放着子类对象,然后强转成对应的子类类型在赋值给子类引用,也就是将父类引用还原成子类对象:

class TestAnimal {
    public static void main(String[] args) {
        //向下转型 -> 不安全
        //instanceof 判断animal引用的对象是不是Dog类型的,如果是则为 true 不是则为 false
        Animal animal = new Dog("王五", 12);
        if (animal instanceof Dog) {
            Dog dog = (Dog)animal;
            dog.eat();
        }
    }
}

简单理解,如果 animal 里面的对象本来是狗,那么如果将 狗 还原成 猫 那么就是不安全的,所以我们就需要用到 instanceof 关键字来判断下,这里也可以自己下来测试下。

4、通过练习进一步了解多态

4.1 打印指定不同图案(多态实现)

假设我们有这段代码:

public class Drawing {
    public void draw() {
        System.out.println("画图");
    }
}
class DrawCircle extends Drawing {
    @Override
    public void draw() {
        System.out.print("⚪");
    }
}
class DrawFlower extends Drawing {
    @Override
    public void draw() {
        System.out.print("❀");
    }
}
class DrawTriangle extends Drawing {
    @Override
    public void draw() {
        System.out.print("▲");
    }
}

要让你按找一个顺序打印:▲❀❀⚪❀▲,我们如何实现呢?

第一种方法:用 else if:

class TestDraw {
    //方法一:使用 循环 + if else
    public static void draw1() {
        DrawTriangle triangle = new DrawTriangle();
        DrawFlower flower = new DrawFlower();
        DrawCircle circle = new DrawCircle();
        String[] shapes = {"Triangle", "Flower", "Flower", 
                           "Circle", "Flower", "Triangle"
                          };
        //增强for循环遍历数组
        for (String s : shapes) {
            if (s.equals("Triangle")) {
                triangle.draw();
            } else if (s.equals("Flower")) {
                flower.draw();
            } else if (s.equals("Circle")) {
                circle.draw();
            }
        }
    }
    public static void main(String[] args) {
        draw1();
    }
}

这样写代码太复杂了,也不利于阅读,如果对 for-each还不了解,可以看我前面写的程序控制文章。

第二种方法:使用向上转型->多态

class TestDraw {
    public static void draw2() {
        Drawing[] shapes = { new DrawTriangle(), new DrawFlower(), new DrawFlower(),
                             new DrawCircle(), new DrawFlower(), new DrawTriangle()
                           };
        for (Drawing s : shapes) {
            s.draw();
        }
    }
    public static void main(String[] args) {
        draw2();
    }
}

Drawing 是他们的父类,所以我们可以直接创建 Drawing 类型数组,元素放子类对象是没问题的,然后通过增强for循环遍历这个数组就实现打印了,这两种方法最终实现效果都是一样的:

通过这个练习,我们可以看到多态部分的优点:

  • 可以有效降低代码复杂度,避免出现多个 if else
  • 可扩展性更强,如果要新增打印,也更简单

当然,多态也有一定的缺点:

  • 属性没有多态性,当父类属性和子类属性同名,通过父类引用只能调用父类自己的成员属性
  • 构造方法也没有多态性

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

class A {
    public A() {
    func();
    }
    public void func() {
        System.out.println("A.func()");
    }
}
class B extends A {
    private int num = 1;
    @Override
    public void func() {
    System.out.println("B.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
    A b = new B();
    }
}

这里 B 继承了 A 类,所以在实例化B 的时候,因为我们没有写构造方法,肯定会默认调用编译器给我们提供的无参构造方法,而要想实例化子类必须先调用父类的构造方法,而父类 A 无参构造方法中,调用了 func 方法,但是这个方法在 B 类中重写了,我们来

看打印结果:

通过测试可以发现:

  • 构造 B 对象的时候,会调用 A 的构造方法
  • A 的构造方法调用了func方法,此时会触发动态绑定,会调用 B 中的 func
  • 此时 B 对象自身还没有构造,所以在 num 还未初始化的时候默认值为 0,如果具备多态性,num 的值应该是1
  • 因此在构造方法内部,尽量不要去实例方法,除了 final 和 private 修饰的方法
相关文章
|
4月前
|
算法 Java 程序员
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
77 9
|
4月前
|
Java 开发者
在Java面向对象编程的广阔海洋中,多态犹如一股深邃的潜流,它推动着代码从单一走向多元,从僵化迈向灵活。
在Java面向对象编程的广阔海洋中,多态犹如一股深邃的潜流,它推动着代码从单一走向多元,从僵化迈向灵活。
44 7
|
4月前
|
Java 开发者
那些年,我们一同踏入Java编程的大门,多态,这个充满魔法的名字,曾无数次点亮我们探索面向对象编程的热情。
那些年,我们一同踏入Java编程的大门,多态,这个充满魔法的名字,曾无数次点亮我们探索面向对象编程的热情。
52 5
|
4月前
|
Java 程序员
让我们一起探讨Java多态的奥秘,看看它是如何打破“一刀切”的局限,让我们的代码更加生动多彩
让我们一起探讨Java多态的奥秘,看看它是如何打破“一刀切”的局限,让我们的代码更加生动多彩
45 5
|
4月前
|
Java 程序员
Java中的继承和多态:理解面向对象编程的核心概念
【8月更文挑战第22天】在Java的世界中,继承和多态不仅仅是编程技巧,它们是构建可维护、可扩展软件架构的基石。通过本文,我们将深入探讨这两个概念,并揭示它们如何共同作用于面向对象编程(OOP)的实践之中。你将了解继承如何简化代码重用,以及多态如何为程序提供灵活性和扩展性。让我们启程,探索Java语言中这些强大特性的秘密。
|
2月前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第10天】Java零基础教学篇,手把手实践教学!
35 4
|
2月前
|
Java 编译器 程序员
Java多态背后的秘密:动态绑定如何工作?
本文介绍了Java中多态的实现原理,通过动态绑定和虚拟方法表,使得父类引用可以调用子类的方法,增强了代码的灵活性和可维护性。文中通过具体示例详细解析了多态的工作机制。
75 4
|
3月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
2月前
|
Java
java继承和多态详解
java继承和多态详解
52 5
|
3月前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
84 9
Java——类与对象(继承和多态)