初识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 | 范围 | 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
分析:
- 实例化B类对象调用B类的构造函数
- 因为B类继承于A类,所以调用A类构造函数
- 在A类的构造函数中调用this.func()函数,由于B类重写A类的fun函数,此时触发动态绑定,即在A类的构造函数中调用的this.func()函数是B类的fun()函数
- 在B类的fun()函数中执行super.fun()语句,调用A类的fun()函数,所以打印出来的是
A | num = 20 - super.fun()语句执行完毕后执行
System.out.println(“B | num = ” + num);
此时打印B | num = 0
为什么num是0不是1?
因为此时赋值语句num = 1;还未执行,所以num是默认值1 - 此时执行赋值语句num = 1
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/122864.html