首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
第一章:OOP:继承
第二章:关键字 super
第三章:关键字 static
第四章:成员变量初始化
第五章:OOP:多态
第六章:关键字 native
第七章:关键字 final
第八章:Object 类的使用
第九章:抽象类
第十章:接口
第十一章:经典接口介绍
第十二章:包装类
第十三章:内部类
当前位置:
首页>>
技术小册>>
Java语言基础6-面向对象高级
小册名称:Java语言基础6-面向对象高级
5.1 概述 - 多态的前提条件: - ① 有继承关系。 - ② 有方法重写。 - ③ 父类引用指向子类对象。 - 语法: ```bash 父类类型 变量名 = new 子类类名(); ``` 温馨提示:多态也可以应用在抽象类和接口上。 - Java中的引用类型变量有两种类型: - ① 编译型类型:由声明该变量所使用的类型决定的。 - ② 运行时类型:有实际赋予给变量的对象决定的。 - 换言之,编译时,看左边;运行时,看右边 。 - 如果编译时类型和运行时类型不一致,就出现了多态(对象的多态性)。 - 多态情况下,"看左边" :看的是父类的引用(父类中不具备子类中特有的属性和方法);"看右边" :看的是子类的对象(实际运行的是子类重写父类的方法)。 - 一个引用类型变量如果声明为父类的类型,但是实际引用的是子类对象,那么该变量就 不能 再访问子类中添加的属性和方法,因为属性和方法是在编译时确定的,编译的时候是父类类型,没有子类独有的属性和方法。 注意:成员变量没有多态性,成员方法才具有多态性。 - 示例: ```bash package com.github.polymorphism.demo1; /** * 动物类 * * @author maxiaoke.com * @version 1.0 */ public class Animal { public void eat() { System.out.println("吃东西。。。"); } public void sleep() { System.out.println("动物睡觉。。。"); } } ``` ```bash package com.github.polymorphism.demo1; /** * 猫 * * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } public void catchMouse() { System.out.println("猫捉老鼠"); } } ``` ```bash package com.github.polymorphism.demo1; /** * 狗 * * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); } public void lookHome() { System.out.println("狗看门"); } } ``` ```bash package com.github.polymorphism.demo1; /** * <pre> * 多态的前提: * ① 有继承关系。 * ② 有方法的重写。 * ③ 父类引用指向子类对象。 * </pre> * * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Cat cat = new Cat(); cat.eat(); System.out.println("--------------------"); Dog dog = new Dog(); dog.eat(); System.out.println("--------------------"); Animal animal = new Dog(); animal.eat(); System.out.println("--------------------"); animal = new Cat(); animal.eat(); } } ``` 5.2 多态的应用场景 5.2.1 多态应用在形参和实参( ★ ) - 父类类型作为方法的形式参数,子类对象作为实参。 - 示例: ```bash package com.github.polymorphism.demo2; /** * 程序员 * * @author maxiaoke.com * @version 1.0 */ public class Programmer { public void eat() { System.out.println("程序员吃饭"); } } ``` ```bash package com.github.polymorphism.demo2; /** * 中国程序员 * * @author maxiaoke.com * @version 1.0 */ public class ChineseProgrammer extends Programmer { @Override public void eat() { System.out.println("中国程序员使用筷子吃饭"); } public void play() { System.out.println("中国人玩太极"); } } ``` ```bash package com.github.polymorphism.demo2; /** * 印度程序员 * * @author maxiaoke.com * @version 1.0 */ public class IndianProgrammer extends Programmer { @Override public void eat() { System.out.println("印度程序员使用手吃饭"); } public void play() { System.out.println("印度人玩摩托车"); } } ``` ```bash package com.github.polymorphism.demo2; /** * 英国程序员 * * @author maxiaoke.com * @version 1.0 */ public class EnglishProgrammer extends Programmer { @Override public void eat() { System.out.println("英国程序员用刀叉吃饭"); } public void play() { System.out.println("英国人玩极限运动"); } } ``` ```bash package com.github.polymorphism.demo2; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { ChineseProgrammer chineseProgrammer = new ChineseProgrammer(); showEat(chineseProgrammer); IndianProgrammer indianProgrammer = new IndianProgrammer(); showEat(indianProgrammer); EnglishProgrammer englishProgrammer = new EnglishProgrammer(); showEat(englishProgrammer); } private static void showEat(Programmer programmer) { programmer.eat(); } } ``` ```bash 运行结果: 中国程序员使用筷子吃饭 印度程序员使用手吃饭 英国程序员用刀叉吃饭 ``` 5.2.2 多态应用在数组中 - 数组元素类型声明为父类类型,实际存储的元素是子类对象。 - 示例: ```bash package com.github.polymorphism.demo3; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { String name; public void showInfo() { System.out.println("名字是:" + this.name); } public void eat() { System.out.println("动物吃饭。。。。"); } } ``` ```bash package com.github.polymorphism.demo3; /** * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("小猫吃饭"); } } ``` ```bash package com.github.polymorphism.demo3; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("小狗吃饭"); } } ``` ```bash package com.github.polymorphism.demo3; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Animal[] animals = new Animal[2]; animals[0] = new Cat(); animals[1] = new Dog(); for (Animal animal : animals) { animal.eat(); } } } ``` ```bash 运行结果: 小猫吃饭 小狗吃饭 ``` 5.2.3 多态应用在方法的返回值 - 方法的返回值类型声明为父类类型,实际返回值为子类对象。 - 示例: ```bash package com.github.polymorphism.demo4; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { public void eat() { System.out.println("动物吃饭"); } } ``` ```bash package com.github.polymorphism.demo4; /** * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("小猫吃鱼"); } } ``` ```bash package com.github.polymorphism.demo4; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("小狗吃肉"); } } ``` ```bash package com.github.polymorphism.demo4; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Animal animal = buy("cat"); animal.eat(); animal = buy("dog"); animal.eat(); } public static Animal buy(String name) { if (name.equals("cat")) { return new Cat(); } else if (name.equals("dog")) { return new Dog(); } else { return null; } } } ``` ```bash 运行结果: 小猫吃鱼 小狗吃肉 ``` 5.3 练习 5.3.1 练习1 ① 声明父类 Traffic ,包含方法 public void drive() 。 ② 声明子类 Car , Bicycle 等,并重写 drive 方法。 ③ 在测试类的 main 中创建一个数组,有各种交通工具,遍历调用 drive() 方法。 示例: ```bash package com.github.polymorphism.demo5; /** * @author maxiaoke.com * @version 1.0 */ public class Traffic { public void drive() { System.out.println("驱动"); } } ``` ```bash package com.github.polymorphism.demo5; /** * @author maxiaoke.com * @version 1.0 */ public class Bicycle extends Traffic { @Override public void drive() { System.out.println("自行车 晃荡晃荡"); } } ``` ```bash package com.github.polymorphism.demo5; /** * @author maxiaoke.com * @version 1.0 */ public class Car extends Traffic { @Override public void drive() { System.out.println("汽车 滴滴滴"); } } ``` ```bash package com.github.polymorphism.demo5; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Traffic[] trafficArray = {new Car(), new Bicycle()}; for (Traffic traffic : trafficArray) { traffic.drive(); } } } ``` 5.4 向上转型和向下转型 5.4.1 概述 - 一个对象在 new 的时候创建哪个类型的对象,它从始到终的类型都不会变,换言之,即这个对象的运行时类型,是不会变化的。 - 将这个对象赋值给不同类型的变量,这些变量的编译型类型是不同的。 - 示例:证明对象的运行时类型不会改变 ```bash package com.github.polymorphism.demo8; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { public void eat() { System.out.println("动物吃饭"); } } ``` ```bash package com.github.polymorphism.demo8; /** * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } ``` ```bash package com.github.polymorphism.demo8; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } public void watchHouse() { System.out.println("看家"); } } ``` ```bash package com.github.polymorphism.demo8; /** * 证明对象的运行时状态不会发生改变 * * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Cat cat = new Cat(); Animal animal = cat; Object obj = cat; // 运行时类型 System.out.println(cat.getClass()); // class com.github.polymorphism.demo8.Cat System.out.println(animal.getClass()); // class com.github.polymorphism.demo8.Cat System.out.println(obj.getClass()); // class com.github.polymorphism.demo8.Cat System.out.println(cat.getClass() == animal.getClass()); // true System.out.println(obj.getClass() == animal.getClass()); // true // cat、animal、obj的编译型类型不同 // 通过cat可以调用catchMouse中的所有方法,包括从父类继承而来的,包括自己扩展的 // 通过animal只能调用Animal类及其父类有的方法,不能调用Cat扩展的方法 // 通过obj只能调用Object类中的所有方法 } } ``` - 为什么需要类型转换? - 因为多态,一定会将子类类型的对象赋值给父类类型的引用,这个时候,在编译期间 ,就会出现类型转换的现象(向上转型,自动类型转换)。 - 但是,使用了父类类型的引用接收了子类类型的对象,我们就不能调用子类独有的方法(父类没有的方法),这个时候如果想要调用子类独有的方法,必须使用父类转换。 - 向上转型:左边的变量的类型(父类)= 右边对象/变量的类型(子类)。 - 此时,编译的时候按照左边的变量的类型处理,就只能调用父类中有的变量和方法了,不能调用子类独有的变量和方法了。 - 运行的时候,依然是子类对象的类型。 - 自动完成的,安全的,不会出现异常。 - 向下转型:左边的变量的类型(子类)= (子类的类型)右边对象/变量的类型(父类)。 - 此时,编译的时候按照左边左边的变量的类型处理,就可以调用子类独有的变量和方法了。 - 运行的时候,依然是子类对象的类型。 - 不一定安全的,需要使用(子类的类型)进行强制类型转换。 - 不是所有通过编译的向下类型转换都是正确的,可能会发生 ClassCastException ,为了安全,需要通过 instanceof 关键字进行判断。 ![](/uploads/images/20230724/737e73715b3d1573aba90bf39955cfaa.png) 示例: ```bash package com.github.polymorphism.demo9; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { public void eat() { System.out.println("动物吃饭"); } } ``` ```bash package com.github.polymorphism.demo9; /** * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } ``` ```bash package com.github.polymorphism.demo9; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } public void watchHouse() { System.out.println("看家"); } } ``` ```bash package com.github.polymorphism.demo9; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Animal animal = new Dog(); Dog dog = (Dog)animal; dog.eat(); dog.watchHouse(); } ``` 5.4.2 关键字 instanceof - 语法: ```bash x instanceof A ``` - 解释:判断左边的引用类型变量 x 是否是右边类 A 的对象,如果是,返回 true ;否则,返回 false 。 注意: - ① 要求 x 所在的类和类 A 必须是子类和父类的关系,否则编译报错。 - ② 如果 x 所在的类属于类 A 的子类,那么 x instanceof A 的返回值也为 true 。 - 示例: ```bash package com.github.polymorphism.demo10; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { public void eat() { System.out.println("动物吃饭"); } } ``` ```bash package com.github.polymorphism.demo10; /** * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } ``` ```bash package com.github.polymorphism.demo10; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } public void watchHouse() { System.out.println("看家"); } } ``` ```bash package com.github.polymorphism.demo10; /** * instanceof 判断左边的引用类型变量是否是右边类的对象 * * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Animal animal = new Dog(); if (animal instanceof Dog) { Dog dog = (Dog)animal; dog.eat(); dog.watchHouse(); } else if (animal instanceof Cat) { Cat cat = (Cat)animal; cat.eat(); cat.catchMouse(); } } } ``` 5.5 多态应用时关于成员变量和成员方法的原则 5.5.1 成员变量:只看编译时类型 如果直接访问成员变量,那么只看编译型类型。 示例: ```bash package com.github.polymorphism.demo11; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { String name = "动物"; public void eat() { System.out.println("吃饭"); } } ``` ```bash package com.github.polymorphism.demo11; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { String name = "狗"; @Override public void eat() { System.out.println("吃肉"); } } ``` ```bash package com.github.polymorphism.demo11; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Animal animal = new Dog(); System.out.println(animal.name); } } ``` ```bash 运行结果: 动物 ``` 5.5.2 非虚方法(不可以重写的方法):只看编译时类型 在 Java 中非虚方法有三种: ① 由 invokestatic 指令调用的 static方法 ,这种方法在编译的时候确定,运行的时候不会发生变化。 ```bash javap -c Test.clsss ``` - ② 由 invokespcial 指令调用的方法,这些方法包括 私有方法 、实例构造方法 和 父类方法 ,这些方法也是在编译的时候确定,运行的时候不会发生变化。 - ③ 由 final 关键字修饰的 方法 。虽然 final 方法是由 invokevirtual 指令调用的,但是 final 修饰的方法不能够在子类中进行重写,所以 final 修饰的方法是不能够在运行期进行动态改变的。在 Java 语言规范中明确规定 final 方法是非虚方法。 温馨提示:静态方法不能被重写,所以最好采用 类名.方法名() 的形式调用。 5.5.3 虚方法(可以重写的方法):静态分派和动态绑定 - 在 Java 中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口,在运行阶段才能确定的方法,即可能被重写的方法。 - 当我们通过 对象.方法 的形式,调用一个虚方法,我们如何确定它具体执行那个方法? - ① 静态分派:先看这个对象的编译时类型,在这个对象的编译时类型中找到最匹配的方法。 最匹配指的是:实参的编译时类型和形参的类型最匹配。 - ② 动态绑定:再看这个对象的运行时类型,如果这个对象的运行时类型重写了刚刚找到的那个最匹配的方法,那么执行重写,否则依然执行刚才编译时类型的那个方法。 - 示例:没有重载有重写 ```bash package com.github.polymorphism.demo12; /** * @author maxiaoke.com * @version 1.0 */ public class Animal { public void eat() { System.out.println("吃饭"); } } ``` ```bash package com.github.polymorphism.demo12; /** * @author maxiaoke.com * @version 1.0 */ public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } } ``` ```bash package com.github.polymorphism.demo12; /** * @author maxiaoke.com * @version 1.0 */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } } ``` ```bash package com.github.polymorphism.demo12; /** * @author maxiaoke.com * @version 1.0 */ public class Test { public static void main(String[] args) { Animal animal = new Cat(); animal.eat(); // 猫吃鱼 } } ``` 上面的代码在编译期间先进行静态分派:此时 animal 编译时类型是 Animal 类,所以去 Animal 类中搜索 eat 方法,如果 Animal 类或它的父类中没有这个方法,将会报错。 在运行期间动态的进行动态绑定:animal 的运行时类型是 Cat 类,而子类 Cat 类重写了 eat 方法,所以执行的是 Cat 类的 eat 方法,如果子类 Cat 类没有重写 eat 方法,那么还是执行 Animal 类的 eat 方法。 示例:有重载没有重写 ```bash package com.github.polymorphism.demo13; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Father {} ``` ```bash package com.github.polymorphism.demo13; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Son extends Father {} ``` ```bash package com.github.polymorphism.demo13; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Daughter extends Father {} ``` ```bash package com.github.polymorphism.demo13; /** * @author maxiaoke.com * @version 1.0 * @return */ public class MyClass { public void method(Father father) { System.out.println("father"); } public void method(Son son) { System.out.println("son"); } public void method(Daughter daughter) { System.out.println("daughter"); } } ``` ```bash package com.github.polymorphism.demo13; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { MyClass myClass = new MyClass(); Father father = new Father(); Father son = new Son(); Father daughter = new Daughter(); myClass.method(father); // father myClass.method(son); // father myClass.method(daughter); // father } } ``` 上面的代码在编译期间先进行静态分派:因为 myClass 属于 MyClass 类型,那么在 MyClass 类中寻找最匹配的 method 方法。 在运行期间动态的进行动态绑定:确定指定的是类 MyClass 中的 method(Father father) 方法,因为此时实参类型 father 、son 、daughter 的类型都是 Father 类型。 示例:有重载没有重写 ```bash package com.github.polymorphism.demo14; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Father {} ``` ```bash package com.github.polymorphism.demo14; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Son extends Father {} ``` ```bash package com.github.polymorphism.demo14; /** * @author maxiaoke.com * @version 1.0 * @return */ public class MyClass { public void method(Father father) { System.out.println("father"); } public void method(Son son) { System.out.println("son"); } } ``` ```bash package com.github.polymorphism.demo14; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { MyClass myClass = new MyClass(); Father father = new Father(); Son son = new Son(); Daughter daughter = new Daughter(); myClass.method(father); // father myClass.method(son); // son myClass.method(daughter); // father } } ``` 上面的代码在编译期间先进行静态分派:因为 myClass 属于 MyClass 类型,那么在 MyClass 类中寻找最匹配的 method 方法。 在运行期间动态的进行动态绑定:确定指定的是类 MyClass 中的 method(Father father) 和 method(Son son) 方法,因为此时实参类型 father 、son 、daughter 的类型分别是 Father 、Son 、Daughter 类型,而 Daughter 类型只能和 Father 参数类型匹配。 示例:有重载没有重写 ```bash package com.github.polymorphism.demo15; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Father {} ``` ```bash package com.github.polymorphism.demo15; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Son extends Father {} ``` ```bash package com.github.polymorphism.demo15; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Daughter extends Father {} ``` ```bash package com.github.polymorphism.demo15; /** * @author maxiaoke.com * @version 1.0 * @return */ public class MyClass { public void method(Father father) { System.out.println("father"); } public void method(Son son) { System.out.println("son"); } } ``` ```bash package com.github.polymorphism.demo15; /** * @author maxiaoke.com * @version 1.0 * @return */ public class MySub extends MyClass { public void method(Daughter daughter) { System.out.println("daughter"); } } ``` ```bash package com.github.polymorphism.demo15; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { MyClass myClass = new MySub(); Father father = new Father(); Son son = new Son(); Daughter daughter = new Daughter(); myClass.method(father); // father myClass.method(son); // son myClass.method(daughter); // father } } ``` - 上面的代码在编译期间先进行静态分派:因为 myClass 属于 MyClass 类型,那么在 MyClass 类中寻找最匹配的 method 方法。 - 在运行期间动态的进行动态绑定:确定指定的是类 MyClass 中的 method(Father father) 和 method(Son son) 方法,因为此时实参类型 father 、son 、daughter 的类型分别是 Father 、Son 、Daughter 类型,而 Daughter 类型只能和 Father 参数类型匹配,在 MySub 类中并没有重写method(Father f) 方法,所以仍然执行 MyClass 类中的 method(Father f) 方法。 - 示例:有重载有重写 ```bash package com.github.polymorphism.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Father {} ``` ```bash package com.github.polymorphism.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Son extends Father {} ``` ```bash package com.github.polymorphism.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Daughter extends Father {} ``` ```bash package com.github.polymorphism.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class MyClass { public void method(Father father) { System.out.println("father"); } public void method(Son son) { System.out.println("son"); } } ``` ```bash package com.github.polymorphism.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class MySub extends MyClass { @Override public void method(Father father) { System.out.println("sub--"); } public void method(Daughter daughter) { System.out.println("daughter"); } } ``` ```bash package com.github.polymorphism.demo16; /** * @author maxiaoke.com * @version 1.0 * @return */ public class Test { public static void main(String[] args) { MyClass myClass = new MySub(); Father father = new Father(); Son son = new Son(); Daughter daughter = new Daughter(); myClass.method(father); // sub-- myClass.method(son); // son myClass.method(daughter); // sub-- } } ``` - 上面的代码在编译期间先进行静态分派:因为 myClass 属于 MyClass 类型,那么在 MyClass 类中寻找最匹配的 method 方法。 - 在运行期间动态的进行动态绑定:确定指定的是类 MyClass 中的 method(Father father) 和 method(Son son) 方法,因为此时实参类型 father 、son 、 daughter 的类型分别是 Father 、Son 、Daughter 类型,因为 MySub 重写了 method(Father father) ,所以执行了 MySub 的method(Father father) 方法。
上一篇:
第四章:成员变量初始化
下一篇:
第六章:关键字 native
该分类下的相关小册推荐:
java源码学习笔记
Java语言基础9-常用API和常见算法
Java语言基础8-Java多线程
Mybatis合辑1-Mybatis基础入门
Java语言基础3-流程控制
Java语言基础14-枚举和注解
SpringBoot合辑-初级篇
Java语言基础10-Java中的集合
Mybatis合辑2-Mybatis映射文件
Java语言基础1-基础知识
Java语言基础15-单元测试和日志技术
Java语言基础12-网络编程