目录

- public class Food {
- private String name;
-
- public Food(String name) {
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
- }
- public class Fish extends Food{
-
- public Fish(String name) {
- super(name);
- }
- }
- public class Bone extends Food{
-
- public Bone(String name) {
- super(name);
- }
- }
- public class Animal {
-
- private String name;
-
- public Animal(String name) {
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
- }
- public class Cat extends Animal {
-
- public Cat(String name) {
- super(name);
- }
- }
- public class Dog extends Animal{
- public Dog(String name) {
- super(name);
- }
- }
这些类写好了之后,再单写一个Master类
- public 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 feed(Dog dog,Bone bone){
- System.out.println("主人 "+name+" 给 "+ dog.getName()+" 吃"+bone.getName());
- }
- }
然后进行测试
- public class Poly01 {
-
- public static void main(String[] args) {
- Master tom = new Master("汤姆");
- Dog dog = new Dog("大黄");
- Bone bone = new Bone("大棒骨");
- tom.feed(dog,bone);
- }
-
- }
那么我们现在又要让主人给小猫喂鱼,又要在Master类里写一个feed的方法,只是参数不一样,也就是重载。
- //主人给小狗 喂食 黄花鱼
- public void feed(Cat cat,Fish fish){
- System.out.println("主人 "+name+" 给 "+ cat.getName()+" 吃"+fish.getName());
- }
测试
- public class Poly01 {
-
- public static void main(String[] args) {
- Master tom = new Master("汤姆");
- Dog dog = new Dog("大黄");
- Bone bone = new Bone("大棒骨");
- tom.feed(dog,bone);
-
- Cat cat = new Cat("小花猫");
- Fish fish = new Fish("黄花鱼");
- System.out.println("==========");
- tom.feed(cat,fish);
- }
- }
注:
从功能实现上来说,效果确实是出来了,但是我们这样写下去会出现一种后果
如果随着代码的扩展,将来主人可能还会养猪、兔子、鸟、等各种动物,这样的话不同的动物吃的东西也不一样,猪喜欢吃米饭,鱼喜欢吃虫子,
这样动物很多,食物也很多,就会造成feed方法扩展越来越多,不利于管理和维护。
这里就引出多态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
- public class PloyMethod {
-
- public static void main(String[] args) {
-
- }
- }
-
- class B { //父类
- public void say() {
- System.out.println("B say() 方法被调用...");
- }
- }
-
- class A extends B { //子类
-
- public int sum(int n1, int n2) { //和下面的sum 构成重载
- return n1 + n2;
- }
-
- public int sum(int n1, int n2, int n3) {
- return n1 + n2 + n3;
- }
-
- public void say(){
- System.out.println("A say()方法被调用...");
- }
- }
这里有父类和子类,子类里有2个sum构成重载,那么这是如何构成多态的呢?
此时我们在main方法里这样写:
- public static void main(String[] args) {
- // 方法重载体现多态
- A a = new A();
- // 这里我们传入不同的参数,就会调用不同的sum方法,就体现出多态
- System.out.println(a.sum(10,20));
- System.out.println(a.sum(10,20,30));
- }
我们通过不同的参数个数去调用sum方法,就会去调用不同的方法,因此对sum方法来说,就是多种状态的体现。
重载体现出了多态,那么方法的重写又是如何体现出多态?
同样是刚刚的例子,我们可以看到父类由一个say方法,子类也有一个say方法
这里我们在main方法中new一个B对象,调用a的say方法和b的say方法。
- //方法重写体现多态
- B b = new B();
- a.say();
- b.say();
这里也会体现出多态。
注:
重载和重写体现的多态是比较好理解的,真正难理解的是对象的多态,因为对象多态里面很复杂,对象里面会有编译类型和运行类型的概念。

(1)一个对象的编译类型和运行类型可以不一致
这里Dog类是Animal的子类,这里可以把一个子类对象赋给一个父类的对象引用,也就是说我能用一个父类的引用指向子类对象,现在这个animal准确的讲是对象的引用,并不是对象,而是对象的引用,而后面new的才是真正的对象,此时animal的编译类型是Animal,运行类型是Dog
(2)编译类型在定义对象时,就确定了,不能改变
Animal animal = new Dog();我们可以看到以上代码,它一但定义好了,也就是animal,它的编译类型就是Animal,不可以改变,已经确定下来了
(3)运行类型是可以改变的
animal = new Cat();这个时候animal的运行类型变成了Cat,编译类型仍然是Animal
(4)编译类型看定义时 = 号的左边,运行类型看 = 号的右边
代码演示
public class Animal { public void cry(){ System.out.println("Animal 动物在叫..."); } }
public class Cat extends Animal{ public void cry() { System.out.println("Cat cry() 小猫喵喵叫..."); } }
public class Dog extends Animal{ public void cry() { System.out.println("Dog cry() 小狗汪汪叫..."); } }
public class PolyObject { public static void main(String[] args) { //体验对象多态的特点 //animal 编译类型已经确定 --> Animal,运行类型是 Dog Animal animal = new Dog(); //因为运行时,也就是执行到animal.cry()这一行代码的时候,animal的运行类型是Dog,因此这个cry就会找运行类型的cry,也就是dog里的cry animal.cry(); //小狗汪汪叫 } }运行时,也就是执行到animal.cry()这一行代码的时候,animal的运行类型是Dog,因此这个cry就会找运行类型的cry,也就是dog里的cry
此时我们再把运行类型改成Cat
animal = new Cat(); animal.cry(); //小猫喵喵叫编译类型在定义的时候就已经确定了,这是无法改变的,一直是animal,但是运行类型是Cat。
一但我们将右边new为Cat的时候,animal的指向就发生变化了,这时候是指向Cat,也就是说,在运行的时候主要看的是堆里面的,既然运行类型变成猫了,那么在执行执行的时候又以猫为主,运行的时候全部看的是堆里面的真正对象。
回顾我们一开始多态问题的引出,此时我们来分析一下,这确实要写很多个feed方法
- //主人给小狗 喂食 骨头
- public void feed(Dog dog,Bone bone){
- System.out.println("主人 "+name+" 给 "+ dog.getName()+" 吃"+bone.getName());
- }
- //主人给小狗 喂食 黄花鱼
- public void feed(Cat cat,Fish fish){
- System.out.println("主人 "+name+" 给 "+ cat.getName()+" 吃"+fish.getName());
- }
我们可以对代码进行简化
- //使用多态机制,可以统一的管理主人喂食的问题
- public void feed(Animal animal,Food food){
- System.out.println("主人 "+name+" 给 "+ animal.getName()+" 吃"+food.getName());
- }
里面的参数类型可以用父类来接收,父类的引用可以指向它的子类,所以里面直接写个Animal就好了,同样食物也是。
由于animal的编译类型是Animal类型,它可以指向(接收) Animal子类的对象。
同样的道理,Food的编译类型是food,它可以指向(接收)Food的子类对象
这时候去测试:
- public class Poly01 {
-
- public static void main(String[] args) {
- Master tom = new Master("汤姆");
- Dog dog = new Dog("大黄");
- Bone bone = new Bone("大棒骨");
- tom.feed(dog,bone);
-
- Cat cat = new Cat("小花猫");
- Fish fish = new Fish("黄花鱼");
- System.out.println("==========");
- tom.feed(cat,fish);
- }
- }
多态带来的好处不止这个。
假如我们此时让主人喂食给一只猪,我们来编写一下代码
- public class Pig extends Animal{
- public Pig(String name) {
- super(name);
- }
- }
- public class Rice extends Food{
- public Rice(String name) {
- super(name);
- }
- }
此时主人要给小猪喂食物,我们会发现,这个feed方法不用动,因为它已经可以接收所有动物的子类对象,也可以接收所有食物的子类对象
- //使用多态机制,可以统一的管理主人喂食的问题
- public void feed(Animal animal,Food food){
- System.out.println("主人 "+name+" 给 "+ animal.getName()+" 吃"+food.getName());
- }
直接测试:
- public class Poly01 {
-
- public static void main(String[] args) {
- Master tom = new Master("汤姆");
-
- //添加给小猪喂米饭
- Pig pig = new Pig("小花猪");
- Rice rice = new Rice("米饭");
- tom.feed(pig,rice);
-
- }
- }
多态的前提是:两个对象(类)存在继承关系
多态的向上转型:
那么为了举例子,我们重新建一个Animal类和Cat类
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(){ //Cat特有的方法 System.out.println("猫抓老鼠"); } }
public class PolyDetail { public static void main(String[] args) { //(向上转型):父类的引用指向子类的对象 //语法:父类类型引用名 = new 子类类型(); Animal animal = new Cat(); Object object = new Cat(); System.out.println("OK"); } }将Animal和Object作为引用指向子类的对象这样都是可以的,虽然Object不是Cat的直接父类,但是是Animal的父类。
可以调用父类中的所有成员(需遵守访问权限)
但是就是不能调用子类中的catchMouse()方法,如果需要调用的话,则需要强转
也就是说不能调用子类的特有成员,这个例子里catchMouse()是Cat类独有的方法,在编译阶段,能调用哪些成员,是由编译类型来决定的。
此时我们调用4个方法:
public class PolyDetail { public static void main(String[] args) { //(向上转型):父类的引用指向子类的对象 //语法:父类类型引用名 = new 子类类型(); Animal animal = new Cat(); animal.eat(); animal.run(); animal.show(); animal.sleep(); } }那么这样的输出结果会是什么呢??
这里我们要清楚,在编译阶段,它指定编译类型,也就是javac,但是一但运行起来以后,其实运行的时候就是交给java虚拟机来做了,它在运行程序的时候,不管你编译类型,只看你运行类型,而当我们执行animal.eat()这一行的时候,会先去Cat类里面找eat方法,如果有的话直接找Cat里的eat方法,那么这里直接输出"猫吃鱼"。
注:
调用方法的时候,按照从子类(运行类型)开始查找方法,然后调用,规则和前面的方法调用规则一致
向下转型:
- public class PolyDetail {
- public static void main(String[] args) {
-
- //(向上转型):父类的引用指向子类的对象
- //语法:父类类型引用名 = new 子类类型();
- Animal animal = new Cat();
-
- animal.eat();
- animal.run();
- animal.show();
- animal.sleep();
- }
- }
向上转型的调用方法规则中有:可以调用父类中的所有成员,但是不能调用子类的特有成员。
那么我们就是想调用子类的特有成员该怎么做呢?
这里就出现了多态的向下转型
//多态的向下转型 //语法:子类类型 引用名 = (子类类型) 父类引用. Cat cat = (Cat)animal; cat.catchMouse();这个时候catchMouse(),就可以调用了,此时cat的编译类型是Cat,运行类型还是Cat,因为强转为Cat了
那么此时我们再来看一段代码,我们写了一个Dog类,让Dog类也去继承这个Animal
Dog dog = (Dog)animal;一开始的Cat cat = (Cat)animal这段代码中的animal是因为指向的是Cat对象,那么强转为Cat,这时又有一个cat也是指向到Cat对象。
但是我们想强转为Dog,我们这么想一想,animal指向的是Cat对象,总不可能让一个狗指向猫吧?
属性没有重写之说,属性的值看编译类型。
- public class PolyDetail2 {
-
- public static void main(String[] args) {
- Base base = new Sub();
- System.out.println(base.count); //10
- Sub sub = new Sub();
- System.out.println(sub.count); //20
- }
-
- }
-
-
- class Base{ //父类
- int count=10; //属性
- }
-
- class Sub extends Base{ //子类
- int count=20; //属性
- }
instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
- public class PolyDetail3 {
- public static void main(String[] args) {
- BB bb = new BB(); //true
- System.out.println(bb instanceof BB); //true
- }
- }
-
- class AA{} //父类
- class BB extends AA{} //子类
那么instanceOf判断的是编译类型还是判断的运行类型?
- //aa 编译类型是 AA,运行类型是BB
- AA aa = new BB();
- System.out.println(aa instanceof AA); //true
- System.out.println(aa instanceof BB); //true
那么得出结论:判断对象的运行类型是否为 XX 类型或 XX 类型的子类型


Java 重要特性: 动态绑定机制
我们先来看个问题:
- public class DynamicBinding {
-
- public static void main(String[] args) {
- A a = new B();
- System.out.println(a.sum()); //40
- System.out.println(a.sum1()); //30
- }
- }
-
- 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;
- }
- }
这里毫无疑问,输出40和30
但是我们此时,将子类的sum方法注释掉

此时由于子类没有sum()方法,则去找父类的,但是父类里面又有一个getI()方法,此时子类和父类都有这个getI()方法,那么调用的到底是父类的getI还是子类的?这里就会出现一个动态绑定机制在里面了。

以上面的例子为例,a.sum()这一步,执行到父类的sum()方法时,我们会发现:

由于a.sum()中的a是运行类型是B,此时去调用子类的getI()

而到了子类的getI()的时候,里面的return i;i是个属性,那么此时这个i就是子类里的i,也就是20;所以在父类里的sum方法,return的getI()就是20+10
而最后的运行结果则是从40变为30。

那么现在我们继续把子类的sum1()注释掉

这里调用方法,同样跟该对象的内存地址绑定,但是B类的sum1被我们注释掉,这里就是没有,但是这里继承机制发生了,去父类去找,父类由sum1方法,此时return i+10;属性是没有动态绑定机制的,也就是哪里声明哪里使用,这个i就是当前类,也就是10,那么最后输出20。


- 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 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;
- }
-
- //重写父类say方法
- @Override
- public String say() {
- return super.say() + " salary="+salary;
- }
- }
- public class PloyArray {
- public static void main(String[] args) {
-
- /**
- * 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象
- * 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
- */
-
- Person[] persons = new Person[5];
- persons[0] = new Person("jack",20);
- persons[1] = new Student("jack",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++) {
- //person[i]的编译类型是Person,运行类型是根据实际情况由JVM来判断
- System.out.println(persons[i].say());//动态绑定机制
-
- }
- }
- }
既然要统一放在一个数组里,肯定得用父类类型来接收,因为父类的引用可以指向子类的对象,所以可以这样创建对象数组:Person[] persons = new Person[5];那这里面可以放Person和Person的子类,最后循环遍历,就会跟着运行类型进行输入say方法

此时我们对这个问题进行强化一下:

那么我们添加一下方法
- //学生特有方法
- public void sutdy(){
- System.out.println("学生 "+getName()+" 正在学java课程");
- }
- //老师特有方法
- public void teach(){
- System.out.println("老师 "+getName()+" 正在讲java课程");
- }
但是我们在循环里面调用,是没办法去调用的

因为person[i]的编译类型是Person类,而Person类是没有这个方法的,那么我们可以在循环里面进行判断,判断运行类型是不是学生,instanceof判断运行类型是不是Student,或者是Student的子类。
- public class PloyArray {
- public static void main(String[] args) {
-
- /**
- * 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象
- * 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
- */
-
- Person[] persons = new Person[5];
- persons[0] = new Person("jack", 20);
- persons[1] = new Student("mary", 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++) {
- //person[i]的编译类型是Person,运行类型是根据实际情况由JVM来判断
- System.out.println(persons[i].say());//动态绑定机制
-
- if (persons[i] instanceof Student) { //判断person[i]的运行类型是不是Student
- Student student = (Student) persons[i]; //向下转型
- student.study();
- //这里可以使用一条语句写:((Student)person[i]).study();
- } else if (persons[i] instanceof Teacher) {
- ((Teacher) persons[i]).teach();
- } else if (persons[i] instanceof Person){
-
- }else{
- System.out.println("你的类型有误,请自行判断...");
- }
- }
- }
- }
这里要巧妙的运用instanceof判断运行类型,然后对其进行向下转型的处理,如果运行类型是Person则不进行处理


其实在这里就已经体现出了参数的多态,cat和pig是Animal的子类,fish和rice是Food的子类。
- 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 Manager extends Employee {
-
- private double bonus;
-
- public Manager(String name, double salary, double bonus) {
- super(name, salary);
- this.bonus = bonus;
- }
-
- public void manage() {
- System.out.println("经理 " + getName() + "is managing");
- }
-
- //重写获取年薪方法
- @Override
- public double getAnnual() {
- return super.getAnnual() + bonus;
- }
-
- public double getBonus() {
- return bonus;
- }
-
- public void setBonus(double bonus) {
- this.bonus = bonus;
- }
- }
- 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 PloyParameter {
- public static void main(String[] args) {
- Worker tom = new Worker("tom", 2500);
- Manager milan = new Manager("milan", 5000, 200000);
- PloyParameter ployParameter = new PloyParameter();
- ployParameter.showEmpAnnual(tom);
- ployParameter.showEmpAnnual(milan);
-
- ployParameter.testWork(tom);
- ployParameter.testWork(milan);
- }
- /**
- * showEmpAnnual(Employee employee)
- * 实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [employee.getAnnual()]
- */
- public void showEmpAnnual(Employee employee) {
- System.out.println(employee.getAnnual());
- }
- }
此时:ployParameter.showEmpAnnual(tom);这里我们传进去的tom,它的编译类型是Worker,运行类型也是Worker,那么传进去肯定会去找worker。
那么我们传milan进去,就会给Manager绑定,因为有动态绑定机制。
那么我们再写第二个方法:

此时们将形参再写Employee,然后用instanceof判断运行类型,如果是Worker,直接调用e.work(),这里编译器会自动转换--->向下转型。
那么继续用istanceof判断是不是Manager。
- public class PloyParameter {
-
- public static void main(String[] args) {
- Worker tom = new Worker("tom", 2500);
- Manager milan = new Manager("milan", 5000, 200000);
- PloyParameter ployParameter = new PloyParameter();
- ployParameter.showEmpAnnual(tom);
- ployParameter.showEmpAnnual(milan);
-
- ployParameter.testWork(tom);
- ployParameter.testWork(milan);
- }
-
- /**
- * showEmpAnnual(Employee employee)
- * 实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [employee.getAnnual()]
- */
- public void showEmpAnnual(Employee employee) {
- System.out.println(employee.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{
-
- }
- }
- }
