-
2019-08-07 09:41:24
这篇文章主要探究了Java运行时多态中,子类重写了父类方法,并定义了和父类相同名称的成员变量,调用父类的方法,成员变量、静态变量等属性时,会具体调用子类还是父类的属性
先写一段Java多态的代码
父类
public class Animal {//父类 static int cons = 1; int val = 1; final int fin = 1; public void eat() { System.out.println("animal eat"); } static void eat2() { System.out.println("animal eat2"); } }
子类
public class Cat extends Animal{ static int cons = 2; int val = 2; final int fin = 2; @Override public void eat() { System.out.println("cat eat"); } static void eat2() { System.out.println("cat eat2"); } }
调用测试
public static void main(String[] args) { Animal a = new Cat();//父类引用 System.out.println(a.val);//成员变量 System.out.println(a.cons);//静态变量 System.out.println(a.fin);//常量 a.eat();//成员方法 a.eat2();//静态方法 }
根据多态的必要特点,猜一猜上述代码会输出什么?
继承
子类重写(override)父类方法
父类引用指向子类对象
下面是输出结果
1 1 1 cat eat animal eat2
结论:一般可以浅显的认为多态中调用的属性是父类的,普通方法调用的是子类的,但我们这里可以用是否满足多态的三个特点来判断:
成员变量:通过多态调用的成员变量,无论是普通类型,静态类型,常量类型仍是父类的成员变量,因为成员变量不存在override(覆盖)问题
成员方法:通过多态调用的成员方法一般仍是父类中的方法,若在相应子类中被override(覆盖),则调用子类重写的方法。
静态方法:通过多态调用的静态方法仍是父类的静态方法,因为静态方法虽然能被子类的静态方法“重写”,但严格来讲算不上是override(覆盖)。
编译时多态:主要是方法的重载,通过参数列表的不同来区分不同的方法。
运行时多态:也叫作动态绑定,一般是指在执行期间(非编译期间)判断引用对象的实际类型,根据实际类型判断并调用相应的属性和方法。主要用于继承父类和实现接口时,父类引用指向子类对象。更多相关内容 -
java多态调用
2022-04-30 16:41:25概念:多态是什么它就相当于区别对待,比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优 先买票。再者就是再举个详细的例子: 最近为了争夺在线支付市场,支付宝年底经常会做...概念:多态是什么它就相当于区别对待,比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优 先买票。再者就是再举个详细的例子: 最近为了争夺在线支付市场,支付宝年底经常会做诱人的扫红包-支付-给奖励金的活动。那么 大家想想为什么有人扫的红包又大又新鲜8块、10块…,而有人扫的红包都是1毛,5毛…。其实这背后也是 一个多态行为。支付宝首先会分析你的账户数据,比如你是新用户、比如你没有经常支付宝支付等等,那么 你需要被鼓励使用支付宝,那么就你扫码金额 = random()%99;比如你经常使用支付宝支付或者支付宝账户 中常年没钱,那么就不需要太鼓励你去使用支付宝,那么就你扫码金额 = random()%1;总结一下:同样是 扫码动作,不同的用户扫得到的不一样的红包,这也是一种多态行为。ps:支付宝红包问题纯属瞎编,大家 仅供娱乐。
多态的必要条件:继承,重写,杜磊引用指向子类对象
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法
多态的特点:
1:应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。
2:派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。
代码如下:
测试类:
package com.ytzl.total; /*多态*/ public class polymorphic { public static void main(String[] args) { //使用纯纯的调用来测试 fathers fathers = new fathers();//调用父类 Eldestson eldestson = new Eldestson();//调用大儿子类 youngestson youngestson = new youngestson();//调用小儿子类 fathers.eat();//正常调用 eldestson.eat();//正常待用 youngestson.eat();//正常调用 System.out.println("\n"); //使用多态来测试 fathers eldestson1 = new Eldestson();///子类对象的地址值交给父类的eldestson1来保存 fathers youngestson1 = new youngestson();//子类对象的地址值交给父类的youngestson1来保存 eldestson1.eat();//多态调用 youngestson1.eat();//多态调用 eldestson1.setMoney(100);//通过多态去赋值 eldestson1.setName("小明");//通过多态去赋值 youngestson1.setMoney(50);//通过多态去赋值 youngestson1.setName("小张");//通过多态去赋值 eldestson1.money();//通过多态以子类去获取父类方法 youngestson1.money();//通过多态以子类去获取父类方法 } }
父类:
package com.ytzl.total; /*父类*/ public class fathers { private int money; private String name; public fathers() { } public fathers(int money, String name) { this.money = money; this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public void eat(){ System.out.println("我是父类,我老了牙坏了不能吃硬的。"); } public void money(){ System.out.println("父类给了"+name+money+"元"); } }
子类一:
package com.ytzl.total; /*大儿类*/ public class Eldestson extends fathers{ public void eat(){ System.out.println("我是大儿子类,我还年轻我胃口好牙好什么都能吃。"); } }
子类二:
package com.ytzl.total; /*小儿子类*/ public class youngestson extends fathers{ public void eat(){ System.out.println("我是小儿子类,我还小我牙都没长齐不能吃太硬的。"); } }
运行结果如下:
-
【C++多态篇】多态调用和普通调用
2019-12-23 14:28:33多态调用和普通调用 C++中只有两个调用关系,分为普通调用和多态调用。不满足多态调用就是普通调用。 多态必须构成的两个条件 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(也就是必须满足重写...多态调用和普通调用
C++中只有两个调用关系,分为普通调用和多态调用。不满足多态调用就是普通调用。
多态必须构成的两个条件
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(也就是必须满足重写)
- 必须通过基类的指针或者引用调用虚函数(指针和引用)
多态调用:跟指向的对象有关,传的对象是谁的,调用的就是谁的。
普通调用:和类型有关。类型是什么调用就是什么,类型是Person的,那么调用的函数就是Person的。 比如下面的例子。
普通调用的例子:
class A {}; class B : public A{}; class Person{ public: virtual B*f(){ cout << "Person::f()" << endl; return new B; } }; class Student: public Person{ public: virtual B* f(){ cout << "Student::f()" << endl; return new B; } }; int main () { Person p; p.f(); Student s; p = s; //派生类赋值给子类 p.f(); system("pause"); }
结果:
分析:
在这里虽然调用的是虚函数,派生类中也对基类的虚函数进行了重写。但是不是使用基类的指针或引用调用,所以不满足多态调用的条件。
所以在这里是普通调用。只与类型有关,尽管Student对象s赋值给父类对象p,但是普通调用只与类型有关,所以仍然调用p的f()函数多态调用的例子
class A {}; class B : public A{}; class Person{ public: virtual B*f(){ cout << "Person::f()" << endl; return new B; } }; class Student: public Person{ public: virtual B* f(){ cout << "Student::f()" << endl; return new B; } }; int main() { Person p; Student s; Person* ptr = &p; //本来就是Person类的指针,指向Person类的对象,调用的本来就是Person父类的f()函数 ptr->f() ; ptr = &s; ptr->f(); //指向的是子类的对象, 调用的是子类的f()的函数 }
结果:
分析:
在这里被调用的是虚函数,且派生类Student里对虚函数f()进行了重写。又是基类的指针对虚函数的调用,所以满足多态调用。
ptr是基类的指针,指向的是派生类的对象, 基类的指针调用虚函数,构成多态调用,只和对象有关。所以调用的是派生类的f()函数。错误多态调用的例子
值得注意的一点: 必须是基类的指针对虚函数的调用,下面有一个派生类指针对虚函数的调用
class A {}; class B : public A{}; class Person{ public: virtual B*f(){ cout << "Person::f()" << endl; return new B; } }; class Student : public Person{ public: virtual B* f(){ cout << "Student::f()" << endl; return new B; } }; int main() { Student s; // 派生类指针指向派生类的对象,调用虚构函数,肯定是调用自己的,因为自己完成了对虚函数的重写 Student* ps = &s; ps->f(); cout << endl; Person p; Person* ptr = &p; // Person类的指针, 指向Person类的对象,调用的本来就是Person类的虚函数 ptr->f(); cout << endl; ps = (Student*)ptr; //将基类Person的指针强转 赋给派生类Student的指针ps ps->f(); //派生类的指针调用虚函数, 所以不构成重写, 不满足多态调用。 所以和对象没有关系,只与类型有关系 system("pause"); }
结果:
分析:
在这里看最后一个输出的Person::f()。
将基类Person的指针强转赋给派生类Student的指针ps。
派生类的指针调用虚函数, 所以不构成重写, 不满足多态调用。 所以和对象没有关系,只与类型有关。 在这里ptr是Person * 类型的,是强转成Student * 类型的, 所以输出的只与类型有关, 输出Person * 的f(). 所以输出的是Person->f() -
Java基础精品课11-多态录屏5.多态调用演示.mp4
2022-05-20 18:18:10Java基础精品课11-多态录屏5.多态调用演示.mp4 -
C++多态调用实现原理(虚函数表详解)
2018-10-11 11:42:22其次,若基类和派生类都具有相同的东西,那么在派生类的虚表中,我派生类要保持我自己的特点,所以此时派生类的虚表中存放的是自己的虚函数,这样做的目的很简单,就是为了在多态调用时,会很灵活,根据对象本身自己...1.带有虚函数的基类对象模型
我们先看段代码:
#include<iostream> using namespace std; class B1 { public: void func1() {} int _b; }; class B2 { public: virtual void func() {} int _b; }; int main() { cout<<"sizeof(B1) = "<<sizeof(B1)<<endl; cout<<"sizeof(B2) = "<<sizeof(B2)<<endl; system("pause"); return 0; }
运行结果:
可以看出,B2的这个类比B1多了4个字节,而这4个字节就是用来存放虚函数的地址,也就是说,这4个字节的数据是一个指针(地址),这个指针指向的是虚函数地址。看个图就很容易理解。
我们需要注意的是:
1.B2对象的前4个字节存放的是虚表的地址,其后才是B2该对象的成员变量;(虚函数表我们也叫做虚表)。
2.若B2这个类中有多个虚函数,那么其对象大小还是8,因为前4个字节是存放虚函数表的地址,在这个虚函数表(函数指针数组)里面每个元素才是每个虚函数的地址。2.派生类对象虚函数表如何构建?
上个例子,只是给出了带有虚函数基类的对象模型,那派生类的对象虚函数表应该如何构建?
#include<iostream> #include<string> using namespace std; class Base //基类 { public: virtual void TestFunc1() { cout << "Base::TestFunc1()" << endl; } virtual void TestFunc2() { cout << "Base::TestFunc2()" << endl; } virtual void TestFunc3() { cout << "Base::TestFunc3()" << endl; } int _b; }; class Derived : public Base //派生类 { public: virtual void TestFunc4() { cout << "Derived::TestFunc4()" << endl; } virtual void TestFunc1() { cout << "Derived::TestFunc1()" << endl; } virtual void TestFunc3() { cout << "Derived::TestFunc3()" << endl; } virtual void TestFunc5() { cout << "Derived::TestFunc5()" << endl; } int _d; }; typedef void(*PVFT)(); //声明函数指针,用来调用虚函数 void PrintVFT(Base& b, const string& str) { cout << str << endl; /*这里是先将对象的地址取出来再强制类型换,此时再解引用的话,取的值就是对象前四个字节地址里面存放的值, 这个值就是虚表的地址,即就是函数指针数组的首地址,我们再将这个地址转换成函数指针类型*/ PVFT* pVFT = (PVFT*)(*(int*)&b); while (*pVFT) //虚表中最后一个元素是空值,打印完循环退出 { (*pVFT)(); // 再解引用就是函数指针数组里面的第一个元素(即就是第一个虚函数地址),再往后一次打印 ++pVFT; } cout << endl; } void TestVirtualFunc(Base& b) { b.TestFunc1(); b.TestFunc3(); return; } int main() { Base b; Derived d; // 打印基类与派生类的虚表 PrintVFT(b, "Base VFT:"); PrintVFT(d, "Derived VFT:"); // 传递Base类对象 TestVirtualFunc(b); cout << endl; // 传递派生类对象 TestVirtualFunc(d); system("pause"); return 0; }
运行结果:
根据结果,我们首先可以看出基类虚函数表是按照声明顺序依此存放,派生类的虚函数表则相应的发生了一些改变。
1.先将基类中的虚表内容拷贝一份到派生类虚表中
2.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数替换虚表中基类的虚函数
3.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后
其实我们没必要刻意去记这些规则,如果用多态的思想去考虑下的话,这样的规则很合理。对我自己而言,首先派生类继承基类,那么基类有的东西,派生类本来没有的东西继承之后也就具有;其次,若基类和派生类都具有相同的东西,那么在派生类的虚表中,我派生类要保持我自己的特点,所以此时派生类的虚表中存放的是自己的虚函数,这样做的目的很简单,就是为了在多态调用时,会很灵活,根据对象本身自己来调用相应的虚函数,假如派生类中的虚表中存放的是基类的虚函数,那么请问,在多态调用时,不管给基类还是派生类的对象,调用的虚函数都是级基类的虚函数,这样做还会实现多态这个特性吗?最后一点不言而喻,派生类独有的虚函数按照声明顺序加在虚表后面即可。
讨论完基类和派生类对象的虚表之后,我们来看看派生类对象完整的对象模型。3.单继承的派生类对象模型
对于单继承的派生类的对象模型,其和我们上面介绍的第一个例子一样,前四个字节是虚表的地址,后面就是按顺序存放
接着看一段代码:#include<iostream> using namespace std; class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } private: int a; }; class Derive :public Base { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } virtual void func4() { cout << "Derive::func4" << endl; } private: int b; }; typedef void(*VFUNC)(); void PrintVTbale(int* table) { printf("vtable:%p\n", table); int i = 0; for (int i = 0; table[i]!= NULL; ++i) //虚表最后一个元素为空值 { printf("table[%d]:%p->", i, table[i]); VFUNC f = (VFUNC)table[i]; //转为函数指针 f(); //调用对应的虚函数 } return; }
运行结果:
根据结果:基类的对象模型和上面第一个例子一样,不再过多讨论。对于单继承的派生类对象模型,在我们清楚了其虚表的构建之后,再其虚函数表地址后面按顺序先存放基类的对象,其次再存放派生类的对象即可。
派生类的对象模型那就是在虚函数指针后面,按顺序先存放基类的成员变量,接着再存放派生类自己成员变量。如下图:
下面我们来看多继承的派生类的对象模型4.多继承的派生类对象模型
看代码:
#include<iostream> using namespace std; class Base1 //基类Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 //基类Base2 { public: virtual void func1() { cout << "Base2::func1" << endl; } virtual void func2() { cout << "Base2::func2" << endl; } private: int b2; }; class Derive : public Base1, public Base2 //派生类 { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } private: int d1; }; typedef void(*VFUNC)(); void PrintVTbale(int* table) { printf("vtable:%p\n", table); for (int i = 0; table[i] != 0; ++i) { printf("table[%d]:%p->", i, table[i]); VFUNC f = (VFUNC)table[i]; f(); } cout << endl; } int main() { Base1 b1; Base2 b2; Derive d; cout << sizeof(d) << endl; //计算派生类对象的大小 PrintVTbale((int*)(*(int*)&b1)); PrintVTbale((int*)(*(int*)&b2)); PrintVTbale((int*)(*(int*)&d)); //第一个虚表 PrintVTbale((int*)(*(int*)((char*)&d + sizeof(Base1)))); //第二个虚表 system("pause"); return 0; }
运行结果:
我们注意到;派生类对象的大小是20,对于多继承的派生类对象,如果只有一个虚表,那么它的大小应该是4(虚表指针) + 4(b1)+ 4(b2) + 4(d1) = 16,可是结果不是16,所以派生类对象的虚表应该不止一个。
所以他的对象模型应该如下图这样:
总结以下就是:
1.对于多继承的派生类的对象,其不但继承了基类的对象,也继承了基类的虚函数表指针;
2.派生类继承多个基类,派生类的对象模型其实就相当于将基类的对象模型继承下来了,只不过对于派生类独有的虚函数,那么他的虚函数指针将会按顺序存放在第一个虚表中最后的位置。
3.最后再加上派生类自己成员至此,对于常见的普通类型多态调用就这些,还有其他继承类型的多态调用,如菱形继承等。
后面会再做总结。注:文中如有不正之处,欢迎指出,希望和大家共同学习,进步。
-
面向对象--方法的调用(方式三:多态调用方法)
2019-02-27 19:14:112、多态 (1)对狗进行描述(子级) public class Dog extends Animals { //创建一个"eat"方法 public void eat( ) { System.out.println("狗吃骨头"); } //创建一个"... -
多态: 多态调用成员的特点: 基本数据类型 类型转换: 引用数据类型 转型 : 抽象的: 接口:
2021-07-10 18:12:40多态调用: 会调用子类中重写的方法 多态的实现效果: 配合方法的重写,当父类引用指向不同的子类对象,同一个方法具有不同的实现方式-->行为多态 多态调用成员的特点: 父类引用调用 成员变... -
java多态调用中关于静态方法
2018-03-06 15:42:13多个对象在访问或修改static修饰的成员变量时,其中一个对象将static成员变量值进行了修改,其他对象中的static成员变量值跟着改变,即多个对象共享同一个static成员变量)多态是指父类引用指向子类对象,调用方法时... -
【牛客网 java笔试题】多态调用
2020-04-09 18:13:07在java的多态调用中,new的是哪一个类就是调用的哪个类的方法。(错) 解析: java多态有两种情况:重载和重写 在重写中,运用的是动态单分配,是根据new的类型确定对象,从而确定调用的方法; 在重载中,运用... -
判断对错。在java的多态调用中,new的是哪一个类就是调用的哪个类的方法。
2017-02-26 15:14:14java多态有两种情况:重载和覆写 在覆写中,运用的是动态单分配,是根据new的类型确定对象,从而确定调用的方法; 在重载中,运用的是静态多分派,即根据静态类型确定对象,因此不是根据new的类型确定调用的... -
多态的调用方法
2020-12-10 08:50:011: 父类的指针指向子类的地址,然后调用虚函数 列子: #include<iostream> #include<memory> class A { public: A() = default; virtual ~A() = default; virtual void display() { std::cout &... -
java之多态调用成员变量,成员函数,静态函数的特点
2016-12-22 14:07:00子类和父类之间存在多态时,我们来探讨一下编译和运行时候调用的特点: 先上代码: class Fu { int num = 3; void show() { System.out.println("fu show"); } static void method() { System.out.... -
Java多态调用机制理解
2017-08-30 15:19:18* 运行时, 多态调用的方法用的是子类的方法, 如果子类没有则找父类的. * 成员变量(运行编译看父类): * 编译时, 如果父类没有, 则编译失败. * 运行时, 多态调用的成员变量用的是父类的.再上演示代码: 父类 ... -
Java中多态的方法调用顺序问题
2021-03-18 11:02:09super.method((super)o)这个顺序以及多态的详解可以参看一个经典实例理解java的三大特性之多态我在拜读这篇文章的时候很是赞赏,但是后来自己实践时发现了一些不一样的地方,于是在这里拉出来讨论。我在写方法时使用... -
Java多态 - 多态数组调用子类特有方法
2022-05-21 11:13:13如何调用Student类特有类study和Teacher类特有类teach呢。 目录 1. Student特有学习方法 2. Teacher特有教书方法 3. Main测试方法 1. Student特有学习方法 public void study(){ System.out.println("学生... -
C++中的多态以及多态的调用原理
2020-03-14 11:07:12多态的前提 在继承体系中,基类中必须具有虚函数,派生类必须对基类的虚函数进行重写。 必须通过基类的指针或引用调用虚函数。 虚函数 被virtual修饰的成员函数 重写 虚函数的重写(覆盖),就是在派生类中有一... -
多态的方法调用
2018-10-12 09:52:08#2.b.method之所以会调用子类的方法,注意重写的含义是“覆盖”,通过new出来的子类对象调用的方法就是子类重写的方法,所以会打印3. 第二题: public class Test2 { public static void main(String[] args) {... -
关于多态中调用子类特有方法
2020-08-29 17:11:00关于多态种调用子类特有方法 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果有,再去调用子类的同名方法;如果没有,,只能向下转型 import javax.print.attribute.standard.RequestingUserName; ... -
C#中CLR虚方法的多态调用
2011-10-06 16:05:33C#中CLR虚方法的多态调用C#中CLR虚方法的多态调用C#中CLR虚方法的多态调用C#中CLR虚方法的多态调用C#中CLR虚方法的多态调用 -
动态多态以及多态调用过程
2017-02-26 15:07:22静态多态是指编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用的那个函数,如果有对应的函数就调用该函数,否则会出现编译错误。 动态多态,我们在这里主要说明的... -
多态中调用子类特有方法2种方式(强转+匿名内部类)
2019-04-23 10:54:52多态中调用子类特有方法的两种方式 在日常撸代码的过程当中,总结了多态中调用子类特有方法的2种方式,现整理如下,如有错误,欢迎批评指正,不多说,先上代码! public class Animal{ //父类 public void... -
多态中的方法调用
2021-03-26 08:47:23java多态有两种情况:重载和覆写 **在覆写中,**运用的是动态单分配,是根据new的类型确定对象,从而确定调用的方法; **在重载中,**运用的是静态多分派,即根据静态类型确定对象,因此不是根据new的类型确定调用的... -
多态情况下,成员方法和成员变量的调用
2020-03-02 15:15:43多态情况下,父类的引用调用和子类同名的普通成员变量,使用的是父类自己的成员变量 多态情况下,父类的引用调用和子类同名的普通成员方法,使用的是子类自己的成员方法 多态情况下,父类的引用调用和子类同名的静态... -
java 多态 如何调用子类的特有方法
2021-03-08 14:20:41public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { ... } -
C++多继承小知识 关于多态的方法调用
2020-05-14 15:26:14最近在上c++课程,在类的方法里遇到了多态问题,关于子类对象到底能调用哪些函数。 先给结论: 如果不用强制类型转换只能用父类里存在的方法 如果 == 如果用强制类型转换才能用子类里所有的方法== ... -
使用多态时 子类如何调用特有的方法 使用转型(Java)
2021-04-27 13:51:53子类对象调用子类特有的方法(Java多态) 在使用多态时,可以让我们把具有共同特性的多个类更加方便地使用,但是简单的多态使用会导致一个很严重的问题,就是子类对象只能调用子类中重写的父类方法,而不能调用子类... -
多态使用的四种方式
2021-12-12 15:46:44多态使用: 父类的指针或引用指向子类对象; 调用方式1.在函数调用中,通过父类指针指向子类: //3、抽象电脑基类,提供工作函数; class Computer { private: CPU* m_CPU; VideoCard* m_VideoCard; Memory*... -
多态中对成员的调用
2016-09-04 22:31:46当子类对象中出现同名成员变量时,多态调用,调用该成员变量的引用类型。简单说,无论编译与运行,都看等号左边就可以了。class Fu { int num = 4; } class Zi extends Fu { int num = 6; } class DuoTaiDemo... -
多态时方法的调用问题
2018-02-04 15:08:31多态时方法调用问题: 前提:必须存在多态情况 存在父类:SuperClass,子类SubClass,方法doWork --------------------- 测试代码: SuperClass clz = new Subclass();//多态 clz.doWork();//??? ...