多态是继封装、继承之后,面向对象的第三大特性。
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
定义
- 多态: 是指同一行为,对于不同的对象具有多个不同表现形式。
- 程序中多态: 是指同一方法,对于不同的对象具有不同的实现.
前提条件【重点】
- 继承或者实现【二选一】
- 父类引用指向子类对象【格式体现】
- 方法的重写【意义体现:不重写,无意义】
小结:
- 多态: 是指同一行为,对于不同的对象具有多个不同表现形式。
- 条件:
- 继承或者实现
- 父类引用指向子类的对象
- 方法的重写
知识点-- 实现多态
目标:
- 如何实现多态
路径:
- 多态的实现
讲解:
多态的体现:父类的引用指向它的子类的对象:
父类类型 变量名 = new 子类对象;
变量名.方法名();
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
- 多态实现方式一:通过继承类
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}
public class Test1 {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal anl = new Dog();
anl.eat();// 狗吃骨头...
anl = new Cat();
anl.eat();// 猫吃鱼...
}
}
- 多态实现方式二:通过实现接口
interface A{
void method();
}
class Imp implements A{
@Override
public void method() {
System.out.println("重写method");
}
}
public class Test2 {
public static void main(String[] args) {
// 父接口的引用指向实现类的对象
A a = new Imp();
a.method();
}
}
小结:
- 继承或者实现
- 父类的引用指向子类的对象
- 方法的重写
知识点-- 多态时访问成员的特点
目标
- 掌握多态时访问成员的特点
路径:
- 多态时成员变量的访问特点
- 多态时成员方法的访问特点
讲解:
- 多态时成员变量的访问特点
- 编译看左边,运行看左边
- 简而言之:多态的情况下,访问的是父类的成员变量
- 编译看左边,运行看左边
- 多态时成员方法的访问特点
- 非静态方法:编译看左边,运行看右边
- 简而言之:编译的时候去父类中查找方法,运行的时候去子类中查找方法来执行
- 静态方法:编译看左边,运行看左边
- 简而言之:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行
- 非静态方法:编译看左边,运行看右边
- 注意:多态的情况下是无法访问子类独有的方法
- 演示代码:
class Fu{
int num = 10;
// 非静态方法
public void method1(){
System.out.println("Fu 非静态方法method1");
}
// 静态方法
public static void method2(){
System.out.println("Fu 静态方法method2");
}
}
class Zi extends Fu{
int num = 20;
// 非静态方法
@Override
public void method1(){
System.out.println("Zi 非静态方法method1");
}
// 静态方法
public static void method2(){
System.out.println("Zi 静态方法method2");
}
}
public class Test {
public static void main(String[] args) {
/*
多态时访问成员的特点:
成员变量:编译看父类,运行看父类(编译看左边,运行看左边)
成员方法:
非静态方法: 编译看父类,运行看子类(编译看左边,运行看右边)
静态方法: 编译看父类,运行看父类(编译看左边,运行看左边)
记忆:
除了非静态方法,编译看父类,运行看子类,其余都是看父类
*/
// 父类的引用指向子类的对象
Fu f = new Zi();
System.out.println(f.num);// 10
f.method1();
f.method2();
}
}
小结:
- 除了非静态方法,编译看父类,运行看子类,其余都是看父类
知识点-- 多态的几种表现形式
目标:
- 多态的几种表现形式
路径:
- 普通父类多态
- 抽象父类多态
- 父接口多态
讲解:
-
多态的表现形式:
- 普通父类多态
class Fu{}
class Zi extends Fu{}
public class Test {
public static void main(String[] args) {
Fu f = new Zi();
}
}
- 抽象父类多态
abstract class Fu{}
class Zi extends Fu{}
public class Test {
public static void main(String[] args) {
Fu f = new Zi();
}
}
- 父接口多态
interface A{}
class Imp implements A{}
public class Test {
public static void main(String[] args) {
A a = new Imp();
}
}
知识点-- 多态的应用场景:
目标:
- 掌握多态在开发中的应用场景
路径:
- 变量多态 -----> 意义不大
- 形参多态----> 常用
- 返回值多态—> 常用
讲解:
多态的几种应用场景:
- 变量多态
class Fu{}
class Zi extends Fu{}
public class Test {
public static void main(String[] args) {
// 变量多态---一般不使用,意义不大,除非用来检验调用父类的方法
Fu f = new Zi();
}
}
-
形参多态
-
参数的类型为父类类型,就可以传入该父类类型的对象,或者该父类类型的所有子类对象
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
//.... 很多的子类.....
public class Test {
public static void main(String[] args) {
// 形参多态---- 必须掌握
Dog d = new Dog();
method(d);
System.out.println("==============================");
Cat c = new Cat();
method(c);
}
// 定义一个方法,既可以接收Dog类的对象,又可以接收Cat类的对象(也就是说要能接收Animal类的所有对象)
// 结论: 参数的类型为父类类型,就可以传入该父类类型的对象,或者该父类类型的所有子类对象
public static void method(Animal anl){
anl.eat();
}
/* public static void method(Dog d){
d.eat();
}
public static void method(Cat c){
c.eat();
}*/
//....
}
-
返回值多态
-
如果方法的返回值类型为父类类型,那么就可以返回该父类对象,或者该父类的所有子类对象
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
Animal anl1 = method(1);// 返回狗类对象
Animal anl2 = method(3);// 返回猫类对象
}
// 结论:如果方法的返回值类型为父类类型,那么就可以返回该父类对象,或者该父类的所有子类对象
public static Animal method(int num){
if (num == 3){
// 返回猫
Cat c = new Cat();
return c;
}else {
// 返回狗
Dog d = new Dog();
return d;
}
}
}
小结:
- 如果参数的类型为父类类型,name就可以传入该父类类型的对象,或者该父类类型的所有子类对象
- 如果方法的返回值类型为父类类型,那么就可以返回该父类类型的对象,或者该父类的所有子类对象
知识点-- 多态的好处和弊端
目标:
- 实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。但有好处也有弊端
步骤:
- 多态的好处和弊端
讲解:
- 好处
- 提高了代码的扩展性
- 弊端
- 多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。
- 示例代码
// 父类
public abstract class Animal {
public abstract void eat();
}
// 子类
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void lookHome(){
System.out.println("狗看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠...");
}
}
public class Test1 {
public static void main(String[] args) {
/*
- 好处
- 提高了代码的扩展性
- 弊端
- 多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。
*/
Dog d = new Dog();
method(d);
System.out.println("=========================");
Cat c = new Cat();
method(c);
}
public static void method(Animal anl){
anl.eat();
//anl.lookHome();// 编译报错,因为多态的成员访问特点:编译看父类,父类中没有,就报错
//anl.catchMouse();// 编译报错,因为多态的成员访问特点:编译看父类,父类中没有,就报错
}
}
知识点-- 引用类型转换
目标:
- 向上转型与向下转型,instanceof关键字
步骤:
- 向上转型
- 向下转型
- instanceof关键字
讲解:
向上转型
- 子类类型向父类类型向上转换的过程,这个过程是默认的。
Aniaml anl = new Cat();
向下转型
- 父类类型向子类类型向下转换的过程,这个过程是强制的。
Aniaml anl = new Cat();
Cat c = (Cat)anl;//向下转型
c.catchMouse();// 可以访问 子类独有的功能,解决多态的弊端
instanceof关键字
-
向下强转有风险,最好在转换前做一个验证 :
-
格式:
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
if( anl instanceof Cat){//判断anl是否能转换为Cat类型,如果可以返回:true,否则返回:false
Cat c = (Cat)anl;//安全转换
}
class Animal{
public void eat(){}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
// 看家
public void lookHome(){
System.out.println("狗看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
// 抓老鼠
public void catchMouse(){
System.out.println("猫抓老鼠...");
}
}
public class Test {
public static void main(String[] args) {
/*
引用类型转换:
向上转型: 子类类型向父类类型向上转换的过程,这个过程是默认的。
向下转型: 父类类型向子类类型向下转换的过程,这个过程是强制的。
格式: 子类类型 变量名 = (子类类型)父类类型的变量;
注意: 右边只能是父类类型的变量,并且该变量指向的是要转换的子类类型的对象
规范: 为了保证转型不出问题,一般转型之前需要进行类型判断----instanceof关键字
格式:
if(变量名 instanceof 数据类型){
}
如果变量指向的对象是属于后面的数据类型,返回true。
如果变量指向的对象不属于后面的数据类型,返回false。
*/
// 向上转型
Animal anl = new Dog();
// 向下转型
//Animal a = new Animal();
//Dog d = (Dog)a;// 运行报错 ClassCastException类型转换异常
//Dog d = (Dog)anl;// 没有问题
//Cat c = (Cat)anl;// 运行报错 ClassCastException类型转换异常
System.out.println("============================================");
if (anl instanceof Dog){
Dog d = (Dog)anl;
d.eat();
d.lookHome();
}
}
}
解决多态的弊端
abstract class Animal{
public abstract void eat();
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void lookHome(){
System.out.println("狗看家...");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠...");
}
}
public class Test1 {
public static void main(String[] args) {
/*
- 好处
- 提高了代码的扩展性
- 弊端
- 多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。
*/
Dog d = new Dog();
method(d);
System.out.println("=========================");
Cat c = new Cat();
method(c);
}
public static void method(Animal anl){
anl.eat();
// 判断anl指向的是否是Dog对象
if (anl instanceof Dog){
Dog d = (Dog)anl;
d.lookHome();
}
// 判断anl指向的是否是Cat对象
if (anl instanceof Cat){
Cat c = (Cat)anl;
c.catchMouse();
}
}
}