初识Java多态(1)

书读的越多而不加思考,你就会觉得你知道得很多;而当你读书而思考得越多的时候,你就会越清楚地看到,你知道得很少。

导读:本篇文章讲解 初识Java多态(1),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

向上转型

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

什么是向上转型?

父类引用指向子类的对象

发生过程
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 范围 private default protected public
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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/122864.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!