【Java复健指南07】OOP中级02-重写与多态思想
前情提要:https://www.cnblogs.com/DAYceng/category/2227185.html
重写
注意事项和使用细节
方法重写也叫方法覆法,需要满足下面的条件
1.子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。
2.子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类,比如父类返回类型是Object ,子类方法返回类型是String
public Object getInfo(){}
...
public String getInfo(){}
3.子类方法不能缩小父类方法的访问权限
重载overload与重写override的区别
练习
1.编写一个Person类,包括属性/private (name.age),构造器、方法say(返回自我介绍的字符串)。
2.编写一个Student类,继承Person类,增加id、score属性/private,以及构造器,定义say方法(返回自我介绍的信息)。
3.在main中,分别创建Person和Student对象,调用say方法输出自我介绍。
public class OverrideExercise {
public static void main(String[] args) {
Person person = new Person("jk", 16);
System.out.println(person.say());
Student student = new Student(1,88.6);
System.out.println(student.say());
}
}
class Person{
private String name;
private int age;
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say(){
return "名字: " + name + " 年龄: " + age;
}
}
class Student extends Person{
private int id;
private double score;
public Student() {
}
public Student(int id, double score) {
this.id = id;
this.score = score;
}
public String say(){
return "id: " + id + " 成绩: " + score;
}
}
多态
多态其实是一种思想,它可以有多种体现方式
多态的具体体现
1.方法的多态
方法的重写和重载就体现多态思想。
2.对象的多态(核心)
(1)一个对象的编译类型和运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的.
(4)编译类型看定义时=号的左边,运行类型看=号的右边
例如:
Animal animal = new Dog();
【animal编译类型是Animal,运行类型Dog】
animal = new Cat();
【animal的运行类型变成了Cat,编译类型仍然是Animal】
动物类
public class Animal {
public void cry(){
System.out.println("Animal cry()动物在叫");
}
}
狗类
public class Dog extends Animal{
// @Override
public void cry() {
System.out.println("Dog cry()狗叫");
}
}
猫类
public class Cat extends Animal{
//@Override语法校验,写不写都行
public void cry() {
System.out.println("Cat cry()猫叫");
}
}
演示
public class PolyObject {
public static void main(String[] args) {
// animal编译类型是Animal,运行类型Dog
Animal animal = new Dog();
//因为执行到该行是,animal的运行类型是Dog
//所以cry是Dog的cry
animal.cry();//Dog cry()狗叫
// animal编译类型是Animal,运行类型Cat
animal = new Cat();
animal.cry();//Cat cry()猫叫
}
}
多态的细节
多态的前提:两个对象(类)存在继承关系
多态的向上转型
1)本质:父类的引用指向了子类的对象
2)语法:父类类型引用名=new子类类型();
3)特点:
编译类型看左边,运行类型看右边;
可以调用父类中的所有成员(需遵守访问权 限),不能调用子类中特有成员;
最终运行效果看子类的具体实现;
动物类
public class Animal {
String name ="动物";int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
猫类
public class Cat extends Animal{
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
演示
public class PolyDetail {
public static void main(String[] args) {
//向上转型:父类的引用指向了子类的对象
// 语法:父类类型引用名= new子类类型();
Animal animal = new Cat();//向上转型
Object obj = new Cat();//可以吗?可以 Object也是 Cat的父类
//可以调用父类中的所有成员(需遵守访问权限)
//但是不能调用子类的特有的成员
//因为在编译阶段,能调用哪些成员,是由编译类型来决定的
// animal.catchMouse();错误,因为该方法没有在父类中存在,是Cat的特有方法
//最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法
// ,然后调用,规则我前面我们讲的方法调用规则一致。
animal.eat();//从猫类找,猫吃鱼
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
}
}
多态的向下转型
目的是调用子类特有的方法
1)语法:子类类型引用名=(子类类型)父类引用;
2)只能强转父类的引用,不能强转父类的对象;
3)要求父类的引用必须指向的是当前目标类型的对象;
4)可以调用子类类型中所有的成员;
接着上面的演示例子
我们可以通过强转来实现对猫类特定方法的访问
public class PolyDetail {
public static void main(String[] args) {
//向上转型:父类的引用指向了子类的对象
// 语法:父类类型引用名= new子类类型();
Animal animal = new Cat();
animal.eat();//从猫类找,猫吃鱼
//希望调用Cat的 catchMouse方法
// 多态的向下转型
//语法:子类类型引用名=(子类类型)父类引用;
//编译类型Cat,运行类型Cat
//相当于生成了一个新的引用cat也指向cat对象
//简单理解就是"披着Cat皮的Animal"
Cat cat = (Cat) animal;//猫抓老鼠
//(2)要求父类的引用必须指向的是当前目标类型的对象
//可以吗?错误
//因为父类Animal的引用名animal指向的是子类类型Cat
//Dog也是子类类型,两者间不能转换(因为不存在关系,是平级的)
Dog dog = (Dog) animal;
System.out.println("ok~~");
}
}
属性没有重写这种说法
属性的值直接看编译类型
public class PolyDetail02 {
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
Base base = new Sub();//向上转型
//看编译类型,Base,所以结果是10
System.out.println(base.count);
}
}
class Base{
int count = 10;
}
class Sub extends Base{
int count = 20;
}
instanceof
instanceof用于判断对象运行类型是否为XX类型或其子类
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
//aa编译类型AA,运行类型是BB
AA aa = new BB();
System.out.println(aa instanceof AA);
System.out.println(aa instanceof BB);
}
}
class AA{}//父类
class BB extends AA{}//子类
练习
1.
public class PolyExercise01{
public static void main(Stringlargs){
double d = 13.4;//ok
long l = (long)d; //ok
System.out.println(l): //13
int in = 5; //ok
boolean b =(boolean)in;//不对,boolean -> int
Object obj="Hello";//可以,向上转型
String objStr = (String)obj;//可以向下转型
System.out.println(objStr); // hello
Object objPri = new Integer(5);//可以,向上转型
String str = (String)objPri;//错误ClassCastExcetpion,指向Integer的父类引用,转成String
Integer str1 = (Integer)objPri;//可以,向下转型
}
}
2.
public class PolyExercise02 {
public static void main(String[] args) {
SubEx s = new SubEx();
System.out.println(s.count);//20
s.display();//20
BaseEx b = s;//相当于BaseEx b = new SubEx();
System.out.println(b == s);//T
System.out.println(b.count);//因为属性只看编译类型,故这里为10
b.display();//方法调用,先看运行类型,是SubEx,所以输出20
}
}
class BaseEx {//父类
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class SubEx extends BaseEx{//子类
int count = 20;
public void display(){
System.out.println(this.count);
}
}
总结
1、多态是是由java的独特设计所产生的一种精简代码的思想【因为java需要将编译与运行过程分开】
2、向上转型指父类引用子类对象【 Animal animal = new Cat(),Cat需要先继承Animal】
具体使用场景:
当我们想使用父类的方法,并且希望这些方法如果有被子类重写,应该获取子类重写的值的情况
例如:父类是Animal,子类是Cat,Cat重写了父类的eat()方法,我想在使用父类方法eat()时,返回子Cat重写的eat()所获取到的值,这时需要使用向上转型
3、向下转型指父类在向上转型时,若想使用子类的特定方法,需要再建立一个新的子类引用并强制转换到父类的引用名上
Animal animal = new Cat();
Cat cat = (Cat) animal;//"披着Cat皮的Animal"
4、属性调用只看编译类型,方法调用先看运行类型
动态绑定机制
java的动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明,那里使用【按作用域走】
举例说明
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型A,运行类型B
A a = new B();//向上转型
/*
* 由于a绑定的运行类型是B,将B中sum()方法注释后,由于继承机制的影响,子类B会去找父类A的sum()方法
* 而父类A的sum()方法又调了getI(),此时触发动态绑定机制,java去判断a绑定的的运行类型
* 查到是B,于是去调B内的getI(),返回B中的属性i,即20
* 给到A的sum()方法,故20+10 = 30
*/
System.out.println(a.sum());//?40->30
/*
* 同理,在注释B中sum1()方法后,子类B会去找父类A的sum1()方法
* 其返回i + 10,i没有动态绑定机制,在A中声明,就在A中使用,故返回10+10=20
* */
System.out.println(a.sum1());//?30->20
}
}
class A {//父类
public int i = 10;
public int sum() {
return getI() +10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {//子类
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI(){
return i;
}
// public int sum1(){
// return i + 10;
// }
}
多态数组
定义
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
演示
需要依次定义Person(父类)、Student和Teacher(子类)三个类
public 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 String say() {//返回名字和年龄
return name + "\t" +age;
}
}
//==================================================
public 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;
}
//重写父类say()
@Override
public String say() {
return super.say() + " score = " + score;
}
//特有的方法
public void study() {
System.out.println("学生" +getName() + " 正在学java.. . ");
}
}
//==================================================
public class Teacher extends Person{
private double sal;
public Teacher(String name, int age, double sal) {
super(name, age);
this.sal = sal;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
//重写父类say()
@Override
public String say() {
return super.say() + " salary = " + sal;
}
//特有方法
public void teach(){
System.out.println("老师" +getName() +"正在讲java课程...");
}
}
测试类PloyArray
public class PloyArray {
public static void main(String[] args) {
//应用实例:现有一个继承结构如下:要求创建1个Person对象、
//2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象say方法
//数组的定义中有:数组中的元素可以是任何数据类型,包括基本类型和引用类型
//这里其实是用引用类型的数据(即对象persons)组成了数组,因此多态数组也是一个数组
Person[] persons = new Person[5];
persons[0] = new Person("jack",20);
persons[1] = new Student("jk",18, 100);
persons[2] = new Student( "smith",19, 30.1);
persons[3] = new Teacher( "scott",30,20000);
persons[4] = new Teacher( "king" ,50,25000);
//循环遍历多态数组,调用say
for (int i = 0; i < persons.length; i++) {
//persons[i]的编译类型是Person
//但是运行类型根据所存放的对象动态变化,由JVM去判断
System.out.println(persons[i].say()); //动态绑定
//错误,因为这些方法是写在子类里面的,需要进行向下转型才能使用
//首先先需要判断persons[i]的运行类型(方法看运行类型)
// persons[i].teach();
// persons[i].study();
if(persons[i] instanceof Student){
Student student = (Student)persons[i];//向下转型
student.study();
//或((Student)persons[i]).study();
} else if (persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
//或((Teacher)persons[i]).teach();
}else if(persons[i] instanceof Person) {
}else {
System.out.println("类型有误,请检查");
}
}
}
}
/*
jack 20
jk 18 score = 100.0
学生jk 正在学java.. .
smith 19 score = 30.1
学生smith 正在学java.. .
scott 30 salary = 20000.0
老师scott正在讲java课程...
king 50 salary = 25000.0
老师king正在讲java课程...
*/
总结
数组中的元素可以由任何数据类型构成,包括基本类型和引用类型。【ps:数组本身也是引用类型】
而所谓的"多态数组"就是由对象构成的一种特殊数组
其中的对象可以遵循多态的思想来使用,使得这个特殊的数组本身也具有多态的性质
多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
应用实例
定义员工类Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。
普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法
测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual0]
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
先分别定义Employee(父类)、Worker和Manager(子类)
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
//得到年工资的方法
public double getAnnual() {
return 12 * salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
//===========================================
public class Worker extends Employee{
public Worker(String name, double salary) {
super(name, salary);
}
public void work(){
System.out.println("普通员工" +getName() + " is working");
}
@Override
public double getAnnual() {//没有额外收入直接调用就行
return super.getAnnual();
}
}
//===========================================
public class Manager extends Employee{
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public void manage() {
System.out.println("经理" +getName() +" is managing");
}
//重写获取年薪方法
@Override
public double getAnnual(){
return super.getAnnual() + bonus;
}
}
测试类PolyParameter
public class PolyParameter {
public static void main(String[] args) {
Worker tom = new Worker( "tom",2500);
Manager milan = new Manager("milan",5000,200000);
PolyParameter polyParameter = new PolyParameter();
// 传进去的tom与Worker绑定,因此调的是Worker中重写的getAnnual()
polyParameter.showEmpAnnual(tom);
//传进去的milan与Manager绑定,因此调的是Manager中重写的getAnnual()
polyParameter.showEmpAnnual(milan);
polyParameter.testWork(tom);
}
// showEmpAnnual(Employee e)
//实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]
public void showEmpAnnual(Employee e) {
System.out.println(e.getAnnual());//动态绑定机制
}
//添加一个方法,testWork ,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
public void testWork(Employee e){
if(e instanceof Worker){
((Worker) e).work();//向下转型操作
} else if (e instanceof Manager) {
((Manager) e).manage();
}else {
System.out.println("nothing");
}
}
}
总结
和多态数组的概念类似,所谓的"多态参数"也就是将对象作为输入形参时的特殊情况
参数中的对象可以遵循多态的思想来使用