1、多态
1、什么是多态?
多态指的是方法或对象具有多种状态,多态的前提是建立在封装和继承之上的。多态可以从方法和对象上分析
1、方法多态:
方法重载
方法重写
2、对象多态:
1、第一层面
1、父类引用可以指向子类的对象 如 student extends Person, Person student = new Student();
2、这里student包含两种类型:编译类型(Person),运行类型(Student)
3、编译类型:由编译器所能识别的类型
4、运行类型:jvm在运行时在堆中能查找到的实际类型
5、(重要) 当我们想通过student调用方法时,只能调用到编译类型所拥有的方法和属性,而不是运行类型所拥有的方法!
2、第二层面
1、一旦编译类型确定后就不能改变,如student的编译类型就永远是Person类型,但是它的运行类型可以进行任何变化(只要你继承了父类person类)
#代码测试一、
public class polyTest {
public static void main(String[] args) {
//这里 Person是person里包含的编译类型,Student是运行类型
Person person = new Student();
//person引用指向Student对象
person.eat(); //person只能调用编译类型Person的eat()方法,无法调用
System.out.println(person.name);
person = new Teacher();
//虽然person引用指向了新的Teacher对象的运行类型,但是person的编译类型还是Person
person.eat();
//所以person还是只能调用编译类型Person的eat()方法
System.out.println(person.name);
}
}
class Person{
public String name="张三";
public void eat(){
System.out.println("Person is eating..");
}
}
class Student extends Person{
public String name = "李四";
public void study(){
System.out.println("student is studying...");
}
}
class Teacher extends Person{
public String name = "王五";
public void teaching(){
System.out.println("teacher is teaching ...");
}
}
输出:
Person is eating..
张三
Person is eating..
张三
说明
1、当使用对象多态中的向上转型时,开发者只能通过引用person调用编译类型所拥有的方法和属性
2、编译类型一旦确定不会变化,但运行类型可以任意变化(前提需要继承)
#多态解决了什么问题?、
1、不用多态
public static void main(String[] args) {
//不使用多态实现主人公xxx在xxx类型公司做xxx职位的工作
Master master = new Master("张三");
Master master1 = new Master("何润东");
//公司类型
InternetCompany Internet = new InternetCompany("力扣");
EntertainmentCompany enter = new EntertainmentCompany("华谊兄弟");
//职位
Programmer programmer = new Programmer("java开发");
Performer performer = new Performer("风云1男主角");
master.JobInfo(Internet,programmer);
master1.JobInfo(enter,performer);
}
}
//主人公类
class Master{
private String name;
public Master(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public void JobInfo(InternetCompany companyType, Programmer job){
System.out.println("主人公" + name + " 在" + companyType.getCompanyType() + "公司," + "担任 " + job.getJob() + "工作");
}
//方法重载
public void JobInfo(EntertainmentCompany companyType, Performer job){
System.out.println("主人公" + name + " 在" + companyType.getCompanyType() + "公司," + "担任 " + job.getJob() + "工作");
}
//思考:如果还有更多的公司+职位的组合,那不是要写更多的方法重载吗?而且有些方法是很少用到的,属实浪费了!
// 那有没有一种解决方案,可以只写一个方法就承载所有组合呢?显然用多态就可以解决!
}
//公司类型类
class Company_Type{
private String companyType;
public Company_Type(String companyType){
this.companyType = companyType;
}
public String getCompanyType(){
return companyType;
}
public void setCompanyType(String companyType){
this.companyType = companyType;
}
}
//互联网公司
class InternetCompany extends Company_Type{
public InternetCompany(String companyType) {
super(companyType);
}
}
//娱乐公司
class EntertainmentCompany extends Company_Type{
public EntertainmentCompany(String companyType){
super(companyType);
}
}
//工作类型类
class Worke{
private String job;
public Worke(String job){
this.job = job;
}
public String getJob(){
return job;
}
public void setJob(String job){
this.job = job;
}
}
//软件开发
class Programmer extends Worke{
public Programmer(String job){
super(job);
}
}
//影视演员
class Performer extends Worke{
public Performer(String job){
super(job);
}
输出:
主人公 张三 在力扣公司,担任 java开发工作
主人公 何润东 在华谊兄弟公司,担任 风云1男主角工作
2、使用多态解决
很简单,只需要稍微修改主人公类中的JobInfo()方法,如下
public class polyTest1 {
public static void main(String[] args) {
//不使用多态实现主人公xxx在xxx类型公司做xxx职位的工作
Master master = new Master("张三");
Master master1 = new Master("何润东");
//公司类型
InternetCompany Internet = new InternetCompany("力扣");
EntertainmentCompany enter = new EntertainmentCompany("华谊兄弟");
//职位
Programmer programmer = new Programmer("java开发");
Performer performer = new Performer("风云1男主角");
master.JobInfo(Internet,programmer);
master1.JobInfo(enter,performer);
master.JobInfo(enter,programmer);
master1.JobInfo(Internet,performer);
}
}
//主人公类
class Master{
private String name;
public Master(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public void JobInfo(Company_Type companyType, Worke job){
System.out.println("主人公 " + name + " 在 " + companyType.getCompanyType() + " 公司," + "担任 " + job.getJob() + " 工作");
}
}
//公司类型类
class Company_Type{
private String companyType;
public Company_Type(String companyType){
this.companyType = companyType;
}
public String getCompanyType(){
return companyType;
}
public void setCompanyType(String companyType){
this.companyType = companyType;
}
}
//互联网公司
class InternetCompany extends Company_Type{
public InternetCompany(String companyType) {
super(companyType);
}
}
//娱乐公司
class EntertainmentCompany extends Company_Type{
public EntertainmentCompany(String companyType){
super(companyType);
}
}
//工作类型类
class Worke{
private String job;
public Worke(String job){
this.job = job;
}
public String getJob(){
return job;
}
public void setJob(String job){
this.job = job;
}
}
//软件开发
class Programmer extends Worke{
public Programmer(String job){
super(job);
}
}
//影视演员
class Performer extends Worke{
public Performer(String job){
super(job);
}
}
输出:
主人公 张三 在 力扣 公司,担任 java开发 工作
主人公 何润东 在 华谊兄弟 公司,担任 风云1男主角 工作
主人公 张三 在 华谊兄弟 公司,担任 java开发 工作
主人公 何润东 在 力扣 公司,担任 风云1男主角 工作
小结:
1、因为父类引用可以指向子类对象,所以用父类引用可以接受所有子类的对象参数!如父类引用companyType可以接收子类对象Internet 和 子类对象enter; 父类引用job可以接收子类对象programmer 和 子类对象performer
2、当Internet传入到companyType时,运行到companyType.getCompanyType(),此时的companyType运行类型是Internet类型,会去找Internet里的getCompanyType()方法,如果没有就去找父类的getCompanyType()方法,然后获取CompanyType的值!
重要!!!
3、向上转型(动态绑定机制);
前提:两个对象(类)存在继承关系
本质:父类引用指向子类对象
格式:父类类型 引用名=new 子类类型(); Person person = new Student();
技巧;编译类型在左边,运行类型在右边
#代码测试:
public class polyTest2 {
public static void main(String[] args) {
AA a = new AA();
System.out.println(a.name);
a.add();
a.insert();
a.change();
System.out.println();
AA b = new BB();//向上转型:编译类型AA是运行类型BB的父类
//b.remove(); 多态里无法调用 只存在子类(运行类型)中而不存在父类(编译类型)中的方法
System.out.println(b.name);
b.add();
b.insert();
b.change();//依然是先在子类BB中找change()方法,没有的话去父类中找
}
}
输出:
AA
AA 的 add 方法
AA 的 insert 方法
AA 的 change 方法
AA
BB 的 add 方法
BB 的 insert 方法
AA 的 change 方法
小结:
*方法层面
1、在多态的向上转型中,无法调用只有子类有而父类没有的方法
2、在调用上,只能调用父类中存在的方法,不管子类有没有
3、在运行结果上(向上转型),会先在子类中寻找该方法,如果有就执行,没有就向父类找,父类有就执行父类的该方法
*属性层面
1、获取哪个类的属性值只看编译类型, AA a = new AA()和 AA b = new BB()的编译类型都是父类AA,所以获取的是AA的类属性值
重要!!!
4、向下转型(动态绑定机制);
前提:
1、两个对象(类)存在继承关系
2、父类的引用必须指向当前目标类型的对象(如 AA a=new BB() 的编译类型要强转为BB类型,操作 BB b = (BB) a ,即先要让AA和BB绑定,你才能从AA向下转型BB)
格式:子类类型 引用名=(子类类型) 父类引用;
技巧;编译类型在左边,运行类型在右边
本质:新增了一条子类引用指向子类对象
#代码测试;
public class polyTest2 {
public static void main(String[] args) {
AA a = new AA();
System.out.println(a.name);
/*
a.add();
a.insert();
a.change();
System.out.println();
*/
AA b = new BB();//向上转型:编译类型AA是运行类型BB的父类
//此时b只能调用add、insert、change方法
System.out.println(b.name);
//b.remove(); 多态里无法调用 只存在子类(运行类型)中而不存在父类(编译类型)中的方法
//如果我想要调用BB类中的remove方法呢,用向下转型
BB c = (BB)b;//向下转型
c.remove();
System.out.println(c.name);
/*
System.out.println(b.name);
b.add();
b.insert();
b.change();//依然是先在子类BB中找change()方法,没有的话去父类中找
*/
}
}
//父类AA
class AA{
protected String name = "AA";
public void add(){
System.out.println("AA 的 add 方法");
}
public void insert(){
System.out.println("AA 的 insert 方法");
}
public void change(){
System.out.println("AA 的 change 方法");
}
}
//子类BB
class BB extends AA{
protected String name="BB";
public void remove(){
System.out.println("BB 的 remove 方法");
}
public void add(){
System.out.println("BB 的 add 方法");
}
public void insert(){
System.out.println("BB 的 insert 方法");
}
}
输出:
AA
AA
BB 的 remove 方法
BB
小结:
*方法层面:
1、向下转型作用于当你使用向上转型时,你想调用父类没有的而子类有的方法时,用向下转型可以调用到该方法
2、向下转型的两个前提是: 1、两个类或对象具有继承关系 2、父类的引用必须指向我想向下转型时的目标类型的对象
3、向下转型的注意点:只能强转父类的引用,不能强转父类的对象
#说明: AA a = new BB();BB b = (BB)a,这里刚开始栈中的a引用指向堆中BB()对象,然后执行BB b = (BB)a向下转型时,会先检查a是否是指向BB()对象,是的话,再在栈中新增b引用也指向BB(),此时父类类型的引用a和子类类型的引用b都指向BB()这个对象。所以本质上强转的是指向BB的引用,而不会影响BB对象本身!结论:当执行向下转型时,本质上是新增了一条子类引用指向子类对象
*属性层面:
1、获取哪个类的属性值只看编译类型, AA a = new AA()和 AA b = new BB()的编译类型都是父类AA,而BB c = (BB)b的编译类型是BB,所以a.name=b.name="AA",c.name="BB"
7、多态的练习题
1、下面哪些语句是正确的,那些是错误的?为什么?
public static void main(String[] args) {
double d = 13.4;
long l = (long)d;//√
System.out.println(l);//13
int i = 5;
//boolean b = (boolean)i;//错误
Object obj = "hello";//向上转型
String objstr = (String)obj;//向下转型
System.out.println(objstr);//hello
Object objP = new Integer(5);//向上转型
String str = (String)objP;//异常,抛出ClassCaseEexcption异常,父类引用没有指向子类对象
Integer str1 = (Integer)objP;//向下转型
}
2、以下程序输出的内容是什么?
public class polyTest3 {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.i);//20 -->Sub注释后 20
s.sum();//20-->Sub注释后 10
Base b = s;
System.out.println(b.i);//10-->Sub注释后 10
System.out.println(b==s);//true,因为b和s都指向Sub()对象这块地址
b.sum();//20-->Sub注释后 10
}
}
class Base{
int i = 10;
public void sum(){
System.out.println(this.i);
}
}
class Sub extends Base{
int i = 20;
// public void sum(){
// System.out.println(this.i);
// }
}
3、以下程序输出结果是什么?
public class polyTest3 {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());//30-->先找B类中的sum()方法,没有找A类sum()方法,再去B中找getl()
System.out.println(a.sum1());//20-->直接找的A类的sum1()方法
}
}
class A{
public int i = 10;
public int sum(){
return getl()+10;
}
public int sum1(){
return i+10;
}
public int getl(){
return i;
}
}
class B extends A{
public int i = 20;
// public void sum(){
// return i+20;
// }
public int getl(){
return i;
}
// public int sum1(){
// return i+10;
// }
}
4、(多态数组应用) 数组定义为父类类型,里面保存实际元素为子类类型,现继承关系如下:
Student类和Teacher类都继承于Person类,Person类属性分别为:String name,int age,Person方法有Info()。要求创建五个年龄不等的对象,分别为Person1个、Student2个、Teacher2个!
/*
(多态数组应用) 数组定义为父类类型,里面保存实际元素为子类类型,现继承关系如下:
Student类和Teacher类都继承于Person类,Student有study(),Teacher有teaching()方法
Person类属性分别为:String name,int age,Person方法有Info()。
要求创建五个年龄不等的对象,分别为Person1个、Student2个、Teacher2个!
*/
public class polyTest3 {
public static void main(String[] args) {
Person[] person = new Person[]{new Person("老王",30),new Student("小李",18,67),new Student("小张",20,88),
new Teacher("赵老师",38,6000),new Teacher("夏老师",40,10000)};
for(int i=0;i<person.length;i++){
if (person[i] instanceof Teacher){ //判断person[i]对象的运行类型
Teacher tescher = (Teacher) person[i];//向下转型
tescher.teaching();
}else if (person[i] instanceof Student){
Student student = (Student) person[i];
student.study();
}else{
Person person1 = person[i];
person1.Info();
}
}
}
}
//Person类
class Person{
private String name;
private int age;
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 Info(){
System.out.println("信息:name="+name+"age="+age );
}
}
//Student类
class Student extends Person{
private double score;
public Student(String name,int age,double score){
super(name,age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public void study(){
System.out.println("学生"+getName()+"正在学习java...");
}
}
//Teacher类
class Teacher extends Person{
private double salary;
public Teacher(String name,int age,double salary){
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public void teaching(){
System.out.println("老师"+getName()+"正在教java...");
}
}
输出:
信息:name=老王age=30
学生小李正在学习java...
学生小张正在学习java...
老师赵老师正在教java...
老师夏老师正在教java...
5、多态参数:
定义员工类Employee,包含name、salary及计算工资的getAnnual方法
普通员工和经理继承员工类,但经理多个奖金和管理manage方法
普通员工有work方法
普通员工和经理要求重写getAnnual方法
测试类中添加一个showEmpAnnal(Employee e)方法,实现获取任何员工对象的工资,并在main方法中动态绑定该方法[e.getAnnal()]
测试类中添加一个testWork方法,如果是普通员工,调用work方法,如果是经理,则调用manage方法;
public class polyTest4 {
/*
多态参数:
定义员工类Employee,包含name、salary及计算工资的getAnnual方法
普通员工和经理继承员工类,但经理多个奖金和管理manage方法
普通员工有work方法
普通员工和经理要求重写getAnnual方法
测试类中添加一个showEmpAnnal(Employee e)方法,实现获取任何员工对象的工资,并在main方法中动态绑定该方法[e.getAnnal()]
测试类中添加一个testWork方法,如果是普通员工,调用work方法,如果是经理,则调用manage方法;
*/
public static void main(String[] args) {
//定义一个多态数组存放动态对象
Employee[] employee = new Employee[]{new Manager("老王",10000,10,12,150000),
new Work("小王",5000,30,20,12),
new Employee("小谢",8000,25,12)};
Manager manager = new Manager("老张",10000,28,12,100000);
Work work = new Work("小张",6000,35,20,12);
polyTest4 polyTest4 = new polyTest4();
polyTest4.getAnnual(manager);
polyTest4.getAnnual(work);
System.out.println();
polyTest4.testWork(employee);
}
//计算所有对象的工资
public void getAnnual(Employee e){
System.out.println("员工"+e.getName()+"年薪"+e.getAnnual());
}
//判断该对象,根据对象调用对应的方法
public void testWork(Employee[] employee){
for (int i=0;i<employee.length;i++){
//判断每个对象类型
if (employee[i] instanceof Manager){
Manager manager = (Manager)employee[i];
manager.manage();
}else if ( employee[i] instanceof Work){
Work work = (Work) employee[i];
work.work();
}else {
Employee employee1 = (Employee)employee[i];
employee1.info();
}
}
}
}
//员工类
class Employee{
private int Annual=12;//工作了几个月
private String name;
private double one_Overtime_Money;//加班每小时单价
private double salary;
public Employee(String name,double salary, double one_Overtime_Money,int annual ) {
Annual = annual;
this.name = name;
this.one_Overtime_Money = one_Overtime_Money;
this.salary = salary;
}
public String getName() {
return name;
}
public void setAnnual(int annual) {
Annual = annual;
}
public double getOne_Overtime_Money() {
return one_Overtime_Money;
}
public void setOne_Overtime_Money(double one_Overtime_Money) {
this.one_Overtime_Money = one_Overtime_Money;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public void info(){
System.out.println("员工"+getName()+"月薪是"+getSalary());
}
//计算工资
public double getAnnual(){
return salary*Annual;
}
}
//普通员工类
class Work extends Employee{
private double overtime;
public Work(String name, double salary, double one_Overtime_Money, int annual, double overtime) {
super(name, salary, one_Overtime_Money, annual);
this.overtime = overtime;
}
//重写父类getAnnual()
@Override
public double getAnnual() {
return super.getAnnual() + overtime*getOne_Overtime_Money();
}
//work方法
public void work(){
System.out.println("员工"+getName()+"正在工作");
}
}
//经理类
class Manager extends Employee{
private double bonus;//奖金
public Manager(String name, double salary, double one_Overtime_Money,int annual, double bonus) {
super(name, salary, one_Overtime_Money, annual);
this.bonus = bonus;
}
//计算工资
@Override
public double getAnnual() {
return super.getAnnual()+bonus;
}
//manage
public void manage(){
System.out.println("经理"+getName()+"正在管理工作。。。");
}
}
2、多态总结:
1、java多态大致分为两类:
1、方法的多态
*方法重写
特点:
1、必须有继承关系且方法名相同,
2、方法的形参列表和返回值类型必须与父类该同名方法一致,
3、作用域发生在子类,
4、对象调用时执行子类的该同名方法)
*方法的重载
特点:
1、必须在同一类中
2、一个类中的多个同名方法的形参列表和返回类型必须不同
3、作用域发生在同一类中
4、调用时根据参数列表选择对应的方法
2、对象的多态
*向上转型
如: B extends A,A a = new B();//父类引用a指向子类对象 B()
前提:
1、子父类之间必须有继承关系,2、子类必须有重写父类方法
调用:
1、可以把a当成父类A对象,所以a可以调用所有父类方法和属性,如果方法同名,执行的是子类方法,属性同名,执行的是父类属性
作用:
1、把所有对子类的的操作全部封装到对父类的操作,提高代码简洁
2、可以在父类中定义一个方法,把要操作的子类的类名传入到该方法形参上,通过它们接收子类的对象,然后通过调用子类对象的成员来处理业务
特点:
1、好处是隐藏了子类的方法和属性,提高了代码的简洁
2、坏处是不能调用子类的中独有方法(解决:可以在父类中写一个空的同名方法解决)
*向下转型
B extends A,A a = new B(); B b = (B)a;
#1、向下转型时会先检查父类引用a是否指向子类B对象,如果不是报错类型转换错误 2、只能父类引用指向子类对象,不能子类引用不能指向父类对象
调用:
1、可以把b当作子类B的对象,所以b能调用子类和父类的所有方法和属性
2、调用同名方法还是先找子类的方法执行的,同名属性就是执行子类的该属性
作用:
1、可以使用父类和子类的所有方法和属性
特点:
好处:可以使用父类和子类的所有方法和属性
坏处:容易报类型转换错误(解决:使用instanceof进行判断)