一、引言(概念)
Java 中的多态是面向对象编程的一个重要特性,它允许我们使用一个接口来表示不同的类实例。多态可以通过方法重写(override)和接口实现来实现。下面是关于 Java 中多态的一些关键概念和示例。
1、方法重写:
方法重写是指在子类中重新定义父类的方法。
重写的方法必须具有相同的签名(方法名、参数列表和返回类型)。
重写的方法可以改变行为,但不能降低访问级别。
2、向上转型:
可以将子类的对象赋值给父类类型的引用变量,这种转换称为向上转型(upcasting)。
向上转型是自动进行的,不需要显式转换。
3、向下转型:
可以将父类类型的引用变量转换为子类类型的引用变量,这称为向下转型(downcasting)。
向下转型需要显式转换,并且只有当对象实际上是子类实例时才安全。
二、多态的用处
情景1:假设现在有个需求完成学生的管理系统,有老师和学生和管理员这三个模块,在以前
我们需要写三个部分
也就是这样,很显然这样是不好的,重复性太高了。所以这个时候我们就可以使用多态来实现这个模块----在写register时在参数部分写上(Person p==new teacher() 把teacher这个对象赋值给p)同理对于student和administor
三、多态的用法
首先,看到多态的表现形式
package 继承练习2; public class person { String name; int age; public person(){ } public person(String name, int age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void show(){ System.out.println(name+" ,"+age+" "); } }
这边,我新建了一个Person父类,如图
然后接着定义student类继承父类
package 继承练习2; public class student extends person{ @Override public void show() { System.out.println("学生的信息为:"+getName()+","+getAge()); } }
teacher类
package 继承练习2; public class teacher extends person{ @Override public void show() { System.out.println("老师的信息为:"+getName()+","+getAge()); } }
admin类
package 继承练习2; public class admin extends person{ @Override public void show() { System.out.println("管理员的信息为:"+getName()+","+getAge()); } }
最后一个test类
package 继承练习2; public class test2 { public static void main(String[] args) { //创建三个对象,调用register方法 student s = new student(); s.setName("张三"); s.setAge(12); teacher t =new teacher(); t.setName("李四"); t.setAge(13); admin ad=new admin(); ad.setName("王五"); ad.setAge(14); register(s); register(t); register(ad); } public static void register(person p) { p.show(); } }
在这些代码中,就有多态的使用,在test中register(s);register(t); register(ad);这三句,就是多态的场景用法。
四、 多态中调用成员方法的特点
1、这里调用回分两种情况:一种是变量调用,一种事方法调用
1.调用变量
package 继承练习2; public class Test3 { public static void main(String[] args) { Animal ad=new dog(); System.out.println(ad.name);//动物 //调用变量,编译看左边,运行也看左边 } } class Animal{ String name="动物"; public void show(){ System.out.println("animal---动物"); } } class dog extends Animal { String name="小狗"; @Override public void show(){ System.out.println("dog----show"); } }
可以看到,在程序运行的结果是----“动物”---,这是因为在程序运行到Animal ad=new dog();时,因为调用的是一个成员变量,所以在编译时会先看左边,左边是父类,而且父类是存在name这个变量的,如果左边编译成功,那运行的结果也就是左边的答案。
2.调用方法
package 继承练习2; public class Test3 { public static void main(String[] args) { Animal ad=new dog(); //System.out.println(ad.name);//动物 //调用变量,编译看左边,运行也看左边 ad.show();//编译看左边,但是运行会看右边 } } class Animal{ String name="动物"; public void show(){ System.out.println("animal---动物"); } } class dog extends Animal { String name="小狗"; @Override public void show(){ System.out.println("dog----show"); } }
可以看到,在程序运行的结果是----“dog----show”,当在调用方法时,编译看左边,但是运行会看右边,左边编译是正确的,但是在程序运行的时候,结果是打印右边子类的结果。
五、从内存图中理解多态中调用成员变量
如图所示,这个是代码运行时的内存图
在java中永远都是,父类先进入方法区然后加载字节码文件,当然最先进入的一定是object类,这个毋庸置疑。这个先是Animal a 在栈内存加载一个空间,让等号的右边有一个new就意味着在堆内存中兴建一块空间,当程序运行时,会先编译,在父类中也发现了变量name不为null,且在运行时也会看左边的父类,所以第一个结果就是----“动物”---,
二、对于调用成员方法,这个会先去在父类中找有没有该方法,如果存在就继续运行,如果不存在就直接报错,但是程序运行的结果是看右边的子类。所以第二个结果就是打印出子类的“dog----show”
六、多态的优势和弊端
(1)多态的优势
1、 在项目操作时,如果我不想使用学生这个对象时,那我只需要对new Student 操作就行了,剩下的代码不用操作。提高了编写程序的便利。
2、可以接受所有的子类对象
(2)多态的弊端:不能调用子类的特有方法
package 继承3; public class test { public static void main(String[] args) { Animal a = new dog(); a.eat(); } } class Animal { String name="动物"; public void eat() { System.out.println("动物在吃东西"); } } class dog extends Animal { String name="小狗"; @Override public void eat() { System.out.println("小狗在吃饭"); } public void lookHome(){ System.out.println("小狗在看家"); } } class Cat extends Animal{ String name="小猫"; @Override public void eat(){ System.out.println("小猫在吃饭"); } public void catcher() { System.out.println("小猫在抓老鼠"); } }
在test中,如果调用a.eat()方法是可以的,这个会打印出---小狗在吃饭---的结果,但是如果想打印出---小狗在看家----这个是做不到的,多态中无法调用子类中特有的方法
解释:因为在调用方法的时候,编译先看左边,那左边是父类,父类是没lookHome这个方法的,程序就会出错。
2.1弊端的解决办法:
把调用者a再变回子类类型即可
package 继承3; public class test { public static void main(String[] args) { Animal a = new dog(); a.eat(); dog d=(dog) a; d.lookHome(); } } class Animal { String name="动物"; public void eat() { System.out.println("动物在吃东西"); } } class dog extends Animal { String name="小狗"; @Override public void eat() { System.out.println("小狗在吃饭"); } public void lookHome(){ System.out.println("小狗在看家"); } } class Cat extends Animal{ String name="小猫"; @Override public void eat(){ System.out.println("小猫在吃饭"); } public void catcher() { System.out.println("小猫在抓老鼠"); } }
只需要在刚才的test类中, dog d = (dog)a;这个就相当于把a转回成子类
当然我们也需要做个判断来确定类型是不是转错,
if(a instanceof dog d){ d.lookHome(); } else if ( a instanceof Cat c) { c.catcher(); else{ System.outer.println("没有这个结果,错误") }
1.首先,判断a是不是dog类型,如果是强转成dog类型,转完之后,变量名为d,如果不是,就不会强转,结果就是false
对于下面的cat也是同理。
七、总结
一、什么是多态:
说白了,就是对象的多种形态
二、多态的前提:
1、有继承或实现关系
2、有子类继承父类
3、要有方法重写
三、多态的好出:
1、父类方法可以接受子类中所有的形参,体现代码的扩展性和便利性。