初识Java多态(1)

向上转型

向上转型是多态的基础语法

什么是向上转型?

父类引用指向子类的对象

发生过程
1.直接赋值
class Animal { }

class Bird extends Animal { }

public class Test {

    public static void main(String[] args) {
        Animal animal = null;//创建父类引用
        Bird bird = new Bird();//实例化子类对象
        animal = bird;//父类引用指向子类对象
    }
    
}
2.方法传参数
class Animal { }

class Bird extends Animal { }

public class Test {

    public static void main(String[] args) {
       Bird bird = new Bird();
        fun(bird);//方法传参数
    }
    
    private static void fun(Animal animal) { }
}
3.方法返回值
class Animal { }

class Bird extends Animal { }

public class Test {

    public static void main(String[] args) {
       Animal animal = fun();//方法返回值
    }
    
    private static Animal fun() {
        Bird bird = new Bird();
        return bird;
    }
}
注意

父类的引用只能访问父类的属性和方法,不能访问子类的属性和方法

class Animal {
    public String type;
    public void printType() {
        System.out.println(type);
    }
}

public class Bird extends Animal {
    public String color;
    public void printColor() {
        System.out.println(color);
    }
}

public class Test {

    public static void main(String[] args) {
       Animal animal = new Bird();
       animal.type = "生物体";
       animal.printType();
       animal.color = "黄色";//编译出错
       animal.printColor();//编译出错
    }
    
}

如果执行上面的代码,编译器会提示

java: 找不到符号
符号: 变量 color
位置: 类型为Animal的变量 animal

java: 找不到符号
符号: 方法 PrintColor()
位置: 类型为Animal的变量 animal

向上转型可以用 is-a 语义理解为,小鸟是动物,所以动物类引用可以指向鸟类对象,即父类引用指向子类的对象

动态绑定

什么是动态绑定?

父类中包含的方法在子类中有对应的同名同参的方法,通过向上转型,让父类引用调用该方法,就会发生动态绑定

即:根据父类指向的子类的不同,就会调用不同的该方法

发生时期

运行时

例子

动物类

class Animal {
    public void eat() {
        System.out.println("Animal eat");
    }
}

猫类,继承动物类

class Cat extends Animal {
    public void eat() {
        System.out.println("Cat eat");
    }
}

鸟类,继承动物类

class Bird extends Animal {
    public void eat() {
        System.out.println("Bird eat");
    }
}
public class Test {
    public static void main(String[] args) {
       Animal animal = new Animal();
       animal.eat();

       animal = new Bird();
       animal.eat();

       animal = new Cat();
       animal.eat();
    }
}

结果

Animal eat
Bird eat
Cat eat

我们可以看到,Animal类引用指向的子类对象不同,调用的eat()也不一样,这个过程是发生在程序运行的时候。

注意

方法的访问限定符会影响动态绑定
还是上面的代码,将Bird类进行如下修改

class Bird extends Animal {
    private void eat() {
        System.out.println("Bird eat");
    }
}

执行如下代码

public class Test {

    public static void main(String[] args) {
       Animal animal = null;
       animal = new Bird();
       animal.eat();
    }
    
}

结果

java: Bird中的eat()无法覆盖Animal中的eat()
正在尝试分配更低的访问权限; 以前为public

此时子类是私有的同名方法,父类没有权限去访问,编译器报错

重写

什么是重写?

子类实现父类的同名同参数方法,这种情况叫做重写、覆写

实际上,在动态绑定中涉及到了重写,子类重写父类的同名同参数方法

在方法重写时,加上@Override注解,可以让编译器进行更多的检测,来减少重写时的错误

例子
class Animal {
    public void eat() {
        System.out.println("Animal eat");
    }
}

class Bird extends Animal {
    @Override //重写注解
    public void eat() {
        System.out.println("Bird eat");
    }
}
注意
1.静态方法不能重写
class Animal {
    static public void eat() {
        System.out.println("Animal eat");
    }
}

class Bird extends Animal {
    @Override
    public void eat() {
        System.out.println("Bird eat");
    }
}

结果

java: Bird中的eat()无法覆盖Animal中的eat()
被覆盖的方法为static

2.重写中的子类的方法的访问权限不能低于父类方法的访问权限
class Animal {
    public void eat() {
        System.out.println("Animal eat");
    }
}

class Bird extends Animal {
    @Override
    //访问权限为默认的
    void eat() {
        System.out.println("Bird eat");
    }
}

结果

java: Bird中的eat()无法覆盖Animal中的eat()
正在尝试分配更低的访问权限; 以前为public

访问权限的级别

public > protected > default > private

No范围privatedefaultprotectedpublic
1同一包中的同一类
2同一包中的不同类
3不同包中的子类
4不同包中的非子类
3.重写的方法的返回值最好相同

父类和子类方法的返回值毫不相关
如:父类返回值是int,子类返回值是char

class Animal {
    public int eat() {
        System.out.println("Animal eat");
        return 0;
    }
}

class Bird extends Animal {
    @Override
    public char eat() {
        System.out.println("Bird eat");
        return '0';
    }
}

结果

java: Bird中的eat()无法覆盖Animal中的eat()
返回类型char与int不兼容

父类和子类方法的返回值有关系
如:父类返回值是父类,子类是子类

class Animal {
    public Animal eat() {
        System.out.println("Animal eat");
        return null;
    }
}

class Bird extends Animal {
    @Override
    public Bird eat() {
        System.out.println("Bird eat");
        return null;
    }
}

执行上面对的代码没有任何问题

但是,子类的方法返回值不能是父类的子类,或比父类更“高”

class Animal {
    public Animal eat() {
        System.out.println("Animal eat");
        return null;
    }
}

class Bird extends Animal {
    @Override
    public Object eat() {
        System.out.println("Bird eat");
        return null;
    }
}

结果

java: Bird中的eat()无法覆盖Animal中的eat()
返回类型java.lang.Object与Animal不兼容

Java中的所有类,都是直接或者间接的继承自Object类

多态

什么是多态?

一个引用,能表现出多种不同的形态
多态是程序设计的一种思想方法,具体语法体现在向上转型动态绑定重写

这段代码就是多态的体现

public class Test {

    public static void main(String[] args) {
       Animal animal1 = new Bird();
       Animal animal2 = new Cat();
       eat(animal1);
       eat(animal2);
    }
    
    public static void eat(Animal animal) {
		animal.eat();
	}
}

结果

Bird eat
Cat eat

多态的好处
1.类的调用者对类的使用成本降低

不需要调用类的人很清楚的了解类中的实现细节,只需要知这个类的对象有某个方法即可

2.能够降低代码的复杂度,减少大量分支语句的使用

不使用多态

class Animal { }

class Cat extends Animal {
    public void eat() {
        System.out.println("Cat eat");
    }
}

class Bird extends Animal {
    public void eat() {
        System.out.println("Bird eat");
    }
}

public class Test {

    public static void main(String[] args) {
            
        Bird bird = new Bird();
        Cat cat = new Cat();
        
        String[] strings = {"Bird", "Cat"};
        
        for (var i : strings) {
            if (i.equals("Bird")) {
                bird.eat();
            } else if (i.equals("Cat")) {
                cat.eat();
            }
        }
    }

}

使用多态

class Animal {
    public void eat() {
        System.out.println("Animal eat");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat eat");
    }
}

class Bird extends Animal {
    @Override
    public void eat() {
        System.out.println("Bird eat");
    }
}

public class Test {

    public static void main(String[] args) {
            
        Animal[] Animal = 
        {new Bird(), new Cat()};
        
        for (var i : Animal) {
            i.eat();
        }
    }
}

虽然执行结果都是一样的,但是很明显发现,不使用多态,if-else语句的判断会增多,如果有几百个类继承了Animal类,那么if-else分支就会有几百个,显然这是很不好的,所以要使用多态

结果

Bird eat
Cat eat

3.扩展能力更强

如果不使用多态,我要把Bird类删了,或者新增一个Tiger类,那么既要增添一个子类,又要修改if-else分支,万一分支较多,修改起来或者访问该类的方法比较多,修改起来非常麻烦,但是如果使用了多态,就只需要增添一个子类即可

向下转型

什么是向下转型?

父类对象转成子类对象

向下转型有的时候可能是非法的,使用的时候要保证操作合理。可以使用instanceof关键字,判定当前的父类引用是不是指向子类的实例

通过向上转型得到的父类引用,可以借助向下转型还原回原来的类型

下面这段代码就是使用了instanceof关键字和向下转型

class Animal { }

class Cat extends Animal {
    public void jump() {
        System.out.println("Cat jump");
    }
}

class Bird extends Animal {
    public void fly() {
        System.out.println("Bird fly");
    }
}

public class Test {

    public static void main(String[] args) {
        Animal animal = new Cat();
        behavior(animal);
    }
    
    private static void behavior(Animal animal) {
        if (animal instanceof Cat) {
            Cat cat = (Cat)animal;
            cat.jump();
        } else if (animal instanceof Bird) {
            Bird bird = (Bird) animal;
            bird.fly();
        }
    }
}

结果

Cat jump
Bird fly

从上面代码可以看出,向下转型的应用场景可以是:有些方法子类存在,但是父类不存在,要想调用这些方法,就要使用向下转型

注意

非法操作
不相同的类型不能转换

public static void main(String[] args) {
        Animal animal = new Animal();
        Cat cat = (Cat) animal;
}

结果(抛出异常)

Exception in thread “main” java.lang.ClassCastException: class Animal cannot be cast to class Cat (Animal and Cat are in unnamed module of loader ‘app’) at Test.main

public static void main(String[] args) {
        Animal animal = new Bird();
        Cat cat = (Cat) animal;
}

结果(抛出异常)

Exception in thread “main” java.lang.ClassCastException: class Bird cannot be cast to class .Cat (Bird and Cat are in unnamed module of loader ‘app’) at Test.main

在构造方法中调用重写的方法

考虑一下下面代码执行的结果

class A {
    private int num = 20;
    public A() {
        this.fun();
    }
    public void fun() {
        System.out.println("A | num = " + num);
    }
}

class B extends A {
    private int num = 1;
    @Override
    public void fun() {
        super.fun();
        System.out.println("B | num = " + num);
    }
}

public class Test {

    public static void main(String[] args) {
        B b = new B();
    }
    
}

结果

A | num = 20
B | num = 0

分析:

  1. 实例化B类对象调用B类的构造函数
  2. 因为B类继承于A类,所以调用A类构造函数
  3. 在A类的构造函数中调用this.func()函数,由于B类重写A类的fun函数,此时触发动态绑定,即在A类的构造函数中调用的this.func()函数是B类的fun()函数
  4. 在B类的fun()函数中执行super.fun()语句,调用A类的fun()函数,所以打印出来的是
    A | num = 20
  5. super.fun()语句执行完毕后执行
    System.out.println("B | num = " + num);
    此时打印B | num = 0
    为什么num是0不是1?
    因为此时赋值语句num = 1;还未执行,所以num是默认值1
  6. 此时执行赋值语句num = 1
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
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的表现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值