今天我们主要学习了如下内容:
1. 方法覆盖的条件及注意事项
2. final关键字
3. 多态的概念,及效果
4. 多态成员的访问特征
5. 多态的优点及多态的弊端
6. 解决多态的弊端(instanceof + 强制类型转化)
1. 继承(extend)中方法覆盖(method override)的条件及注意事项
Demo1
1 package com.cskaoyan.extend.methodoverride; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/17. 5 * @version 1.0 6 * 7 * 方法覆盖的问题: 8 * 当子类中定义了和父类"一模一样"的方法,如果在子类方法体中,调用"一模一样"的方法, 9 * 访问到的是子类中定义的那个"一模一样的方法" 10 * 注意:但是,如果我们在父类方法的方法体中,访问那个"一模一样的方法",访问到的仍然是子类中的方法 11 * 所以,对于子类父类定义"相同"方法的问题,方法覆盖 12 * 13 * 1. 子类中能否定义和父类一模一样的方法? 14 * 15 2. 如果可以,那么在子类类体中访问这个一模一样 的方法, 16 究竟访问到的是父类中的方法,还是子类中的方法? 访问到的是,子类中定义的那个和父类方法声明一模一样的方法 17 如果在父类中访问呢? 18 19 3. 是否可以在子类方法的方法体中,同时访问到子类和父类中定义的一模一样的方法? 20 21 22 23 当我们调用子类对象上调用方法,该方法在运行时,怎么确定,运行哪个方法的? 就近原则 24 1. 当方法执行的时候(如果子类方法调用了其他方法), 25 优先在子类中找目标方法,如果在子类中找到了目标方法,执行子类中定义的目标方法 26 27 2. 如果说,在子类中,没有找到目标方法,接着到父类中找目标方法,如果找到, 28 就执行父类中定义的方法 29 30 方法覆盖,就是在子类定义和父类一模一样的方法,来修改修改父类方法的实现 31 32 着重强调: 对方法覆盖的效果,在子类对象上, 33 调用父类中的方法,在父类中方法的方法体中,调用父类和子类定义的“相同方法” 34 35 */ 36 public class Demo1 { 37 38 39 40 41 public static void main(String[] args) { 42 //创建子类对象 43 OverrideSon overrideSon = new OverrideSon(); 44 // 在子类方法体中,调用 45 //overrideSon.sonAccess(); 46 47 // 在父类方法体中调用 48 overrideSon.fatherAccess(); 49 50 //创建了一个父类对象: 51 //OverrideFather overrideFather = new OverrideFather(); 52 //overrideFather.fatherAccess(); //father access 53 54 //在子类类体中,同时访问子类和父类中定义的一抹一样的方法 55 //overrideSon.allAccess(); 56 57 58 // 说明: 59 // overrideSon.access(); 60 } 61 } 62 63 class OverrideFather { 64 /* 65 父类中定义的方法 66 */ 67 public void access() { 68 System.out.println("father access"); 69 } 70 71 public void fatherAccess() { 72 this.access(); //son access 73 } 74 75 } 76 77 class OverrideSon extends OverrideFather { 78 79 /* 80 子类中定义的和父类一模一样的方法 81 */ 82 public void access() { 83 System.out.println("son access"); 84 } 85 86 public void sonAccess() { 87 // 在子类方法调用一下,子类,父类中定义的一模一样的方法 88 access(); //son access 89 } 90 91 /* 92 同时,在子类方法体中,实现同时访问,父类和子类中定义的一模一样的方法 93 */ 94 public void allAccess() { 95 // 调用子类中的access方法 96 access(); 97 98 //利用super关键字,访问,父类中定义的一模一样的成员方法 99 super.access(); 100 } 101 102 }
Demo2
1 package com.cskaoyan.extend.methodoverride; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/17. 5 * @version 1.0 6 * 7 方法覆盖,在实际开发中,经常使用的, 使用的场景是什么呢? 8 当子类继承父类之后,想要修改父类的方法实现,就可以使用方法覆盖 9 */ 10 public class Demo2 { 11 12 public static void main(String[] args) { 13 // 应该具有飞行行为的普通鸭子 14 FirstTypeDuck firstTypeDuck = new FirstTypeDuck(); 15 firstTypeDuck.fly(); 16 17 // 不应该具有飞行行为的模型鸭子 18 ModelDuck modelDuck = new ModelDuck(); 19 modelDuck.fly(); 20 } 21 22 } 23 24 25 class BaseDuck { 26 27 void quack() { 28 System.out.println("呱呱叫"); 29 } 30 31 void swim() { 32 System.out.println("游泳"); 33 } 34 35 // 添加飞行 36 void fly() { 37 System.out.println("fly"); 38 } 39 40 } 41 42 /* 43 某种类型的鸭子 44 */ 45 class FirstTypeDuck extends BaseDuck{ 46 47 } 48 49 class ModelDuck extends BaseDuck { 50 51 // 在子类中利用方法覆盖,在子类中修改,父类方法的实现 52 void fly() { 53 System.out.println("no fly"); 54 } 55 }
Demo3
1 package com.cskaoyan.extend.methodoverride; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/18. 5 * @version 1.0 6 * 7 * 对于方法覆盖的实现而言, 探究一下方法覆盖发生的条件: "一模一样" 8 * 主要看的是子类和父类的方法声明部分: 访问权限 返回值 方法签名 9 * 1. 方法声明的访问权限的条件 10 * 并非子类和父类方法的访问权限要相同,只要子类方法的访问权限, 11 * 不小于父类方法的访问权限 12 * 13 * 2. 方法返回值 14 * 1) 基本数据类型的方法返回值:子类必须和父类相同 15 * 2) 引用数据类型 16 * a. 子类父类返回值类型相同 17 * b. 子类方法返回值类型 是 父类方法返回值类型的 子类类型 (因为,可以把子类类型看做是父类类型) 18 * 19 * 3. 方法签名 20 * 子类方法的方法签名必须和父类一样 21 */ 22 public class Demo3 { 23 24 public static void main(String[] args) { 25 26 Son son = new Son(); 27 son.testOverride(); 28 } 29 30 } 31 32 class Father { 33 34 // 测试方法权限的条件 35 protected void testAccess() { 36 System.out.println("father test"); 37 } 38 39 // 测试方法返回值类型(基本数据类型) 40 public int testReturnValue1() { 41 System.out.println("father return value"); 42 return 0; 43 } 44 45 // 测试方法返回值类型(引用数据类型) 46 public A testReturnValue2() { 47 System.out.println("father return value 2"); 48 return null; 49 } 50 51 /* 52 利用该方法测试,有没有实现方法覆盖的效果 53 */ 54 public void testSignature() {} 55 /* 56 利用该方法测试,有没有实现方法覆盖的效果 57 */ 58 public void testOverride() { 59 // 测试父类的test方法,是否被子类覆盖 60 //testAccess(); 61 62 // 测试引用类型的返回值类型 63 testReturnValue2(); 64 } 65 66 } 67 class Son extends Father { 68 69 //访问权限部分,子类要覆盖父类方法要满足的条件 70 protected void testAccess() { 71 System.out.println("son test"); 72 } 73 74 //测试方法返回值类型(基本数据类型) 75 public int testReturnValue1() { 76 System.out.println("father return value"); 77 return 0; 78 } 79 80 //测试方法返回值类型(引用数据类型) 81 public B testReturnValue2() { 82 System.out.println("son return value 2"); 83 return null; 84 } 85 86 /* 87 利用该方法测试,有没有实现方法覆盖的效果 88 */ 89 //@Override // 如果给一个方法加了override注解,没有报错,就说明该方法成功覆盖了父类的方法 90 public void testSignature(int i) {} 91 92 } 93 94 class A {} 95 96 class B extends A {}
Demo4
1 package com.cskaoyan.extend.methodoverride; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/18. 5 * @version 1.0 6 * 7 * 注意事项: 8 1. 父类中私有方法不能被重写(override 覆盖或重写) 9 2. 静态方法不能被重写(override 覆盖或重写)! 10 11 */ 12 public class Demo4 { 13 14 public static void main(String[] args) { 15 NoticeSon noticeSon = new NoticeSon(); 16 noticeSon.testOverride(); // father private 17 // father static 18 } 19 20 } 21 22 class NoticeFather { 23 24 //测试私有方法 25 private void privateMethod() { 26 System.out.println("father private"); 27 } 28 // 测试静态方法 29 public static void staticMethod() { 30 System.out.println("father static"); 31 } 32 33 /* 34 测试方法覆盖的效果 35 */ 36 public void testOverride() { 37 //privateMethod(); 38 39 staticMethod(); 40 } 41 } 42 43 class NoticeSon extends NoticeFather { 44 //测试私有方法 45 //@Override 46 private void privateMethod() { 47 System.out.println("son private"); 48 } 49 // 测试静态方法 50 //@Override 51 public static void staticMethod() { 52 System.out.println("son static"); 53 } 54 55 }
2. final关键字
1 package com.cskaoyan.keyfinal; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/18. 5 * @version 1.0 6 * 7 * final关键字: 8 final是 最终 的意思,可以修饰类,变量,成员方法 9 10 final修饰类:一旦类被final修饰,那么该类就不能被继承 11 final修饰方法:一旦方法被final修饰,该方法就不能被覆盖 12 final修饰变量:一旦一个变量被final 该变量就只能被赋值一次,一旦赋值之后,其值就不能被修改 13 a.对于局部变量而言,如果被final修饰,那么该变量必须在使用之前赋值,而且只能赋值一次 14 15 b.对于成员变量而言,如果被final修饰,保证,对于成员变量而言我们要保证, 16 必须在 对象创建完毕 之前 ,给对象的该成员变量值赋值一次(我们自己赋值) 17 18 常量: 19 a. 字面值常量 1 2 3.0 true false "helloworld" 20 b. 自定义常量 被final 修饰的变量就是自定义常量 21 final int a = 1; 22 23 */ 24 public class Demo1 { 25 26 } 27 28 // final修饰类 29 final class FinalFather {} 30 //class FinalSon1 extends FinalFather{} 31 32 // final修饰方法 33 class FinalMehthodFahter { 34 public final void finalMethod() {} 35 } 36 class FinalMethodSon extends FinalMehthodFahter{ 37 //public void finalMethod() {} 38 } 39 40 // 测试final修饰变量 41 class FinalVariable { 42 43 final int finalField1 = 1; 44 final int finalField2; 45 final int finalField3; 46 47 // 构造代码块 48 { 49 finalField2 = 3; 50 } 51 52 //构造方法中 53 public FinalVariable(int a) { 54 finalField3 = a; 55 56 // final修饰的成员变量只能被赋值一次,赋值之后不能修改 57 //finalField3 = 4; 58 } 59 60 /* 61 如果仅在构造方法中,对final修饰的成员变量初始化, 62 必须保证每个构造方法中都必须对final成员变量赋初值 63 */ 64 public FinalVariable() { 65 finalField3 = 103; 66 } 67 68 public void testLocalFinal() { 69 final int a; 70 a = 2; 71 72 //a = 3; 73 74 75 } 76 77 }
3. 多态的概念、效果 及 多态成员的访问特征
1 package com.cskaoyan.polymorphism; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/18. 5 * @version 1.0 6 * 7 * 多态:某一个事物,在不同时刻(或条件下)表现出来的不同状态。 8 * 9 * Java语言中的多态指什么呢? “同一个对象”的行为(方法),在不同的时刻或条件下,表现出不同的效果。 10 * 11 * 要实现多态的效果,有前提条件: 12 * 1. 继承 13 * 2. 要有方法覆盖 14 * 3. 父类引用指向子类对象(对象有时也称之为实例) 15 * 16 * 多态成员的访问特征:父类引用指向子类对象的时候,如果我们父类引用,去访问子类对象中的成员 17 * 18 * 成员变量: 19 * 编译看左边,运行看左边 20 * 21 * 成员方法: 22 * 编译看左边,运行看右边 23 * 24 * 仅从理解的角度来解释: 25 * 1. 编译看左边: 父类引用指向子类对象,此时编看左边是在说, 26 * 通过引用变量可以访问到的子类成员的范围,是由引用类型来决定的 27 * 28 * a. 迄今为止,我们是怎么去访问一个对象?我们都是通过一个中间人即引用变量,间接访问堆上对象 29 * b. 也就是说,只有通过引用变量,我才能访问到堆上的对象 30 * 31 * 举例: 32 * 1. 我们如果把对象,当成我们的一台电视机,对于电视机而言,我们只能使用,遥控器去操作电视机 33 * 34 * 2. 这也就意味着,遥控器提供了什么样的功能,那我们只能使用,遥控器提供的功能,操作电视机 35 * 此时即使电视机提供了很多功能可以使用,但是如果遥控器提供了,极其有限的功能 36 * 37 * 3. 这意味着,我们可以使用的电视机的功能,被遥控器给限制了 38 * 39 * 所以,回到java程序,访问对象的时候,引用变量,就是我们用来操作对象的"遥控器",所以引用变量的类型 40 * 决定了,可以访问到的成员的范围。 41 * 42 * 2. 对于成员方法的,运行(结果)看 右边(多态) 43 * 就是说对于成员方法而言,通过引用变量,实际访问到的行为(方法), 是由引用实际指向的对象来决定的额 44 * 45 * 3. 对于成员变量,运行看左边 46 * 一个对象 属性(成员变量) 和 行为,一个对象的属性(成员变量表示),表示了一对象的外貌 47 * 1. 在多态中,此时对于子类对象而言,把子类对象赋值给父类类型的引用,就相当于给子类对象 48 * 披上了一个父类类型马甲, 因此,该子类对象开起来,就是一个父类对象(外貌特征, 49 * 表现出的就应该是父类的外貌特征) 50 * 51 * 52 * 53 * 54 * 55 * 56 */ 57 public class Demo1 { 58 59 public static void main(String[] args) { 60 // 多态的基本知识 61 //polymorphismBasic(); 62 63 64 // 父类引用指向子类对象 65 Animal animal = new Dog(); 66 67 //通过父类引用访问子类对象的成员方法 68 animal.shout(); //多态效果 汪汪汪 69 System.out.println(animal.name); // xxxAnimal 70 71 //利用另外一个子类来测试,多态的访问特征 72 animal = new Cat(); 73 //成员便令 74 System.out.println(animal.name); 75 // 多态 76 animal.shout(); 77 78 79 80 81 // 解释编译看左边 82 System.out.println(animal.name); 83 animal.shout(); 84 animal.eat(); 85 86 // 通过父类类型的引用,访问不到子类中自己定义的成员 87 //animal.special(); 88 89 90 } 91 92 private static void polymorphismBasic() { 93 // 父类引用指向子类对象 94 Animal animal = new Cat(); 95 96 //animal.shout(); //喵喵喵 97 //条件发生改变 98 animal = new Dog(); 99 //animal.shout(); //汪汪汪 100 101 animal = new Animal(); 102 //调用一下 103 testPolymorphism(animal); 104 animal = new Dog(); 105 //调用一下 106 testPolymorphism(animal); 107 animal = new Cat(); 108 //调用一下 109 testPolymorphism(animal); 110 } 111 112 public static void testPolymorphism(Animal animal) { 113 // 这句代码,即使是编译器,也不知道,shout方法究竟执行的是哪个类中定义的shout方法 114 animal.shout(); 115 } 116 117 } 118 119 class Animal { 120 String name = "xxxAnimal"; 121 122 public void eat() { 123 System.out.println(" eating"); 124 } 125 126 public void shout() { 127 System.out.println("animal shout"); 128 } 129 } 130 131 class Cat extends Animal { 132 133 String name = "xxxCat"; 134 135 @Override 136 public void shout() { 137 System.out.println("喵喵喵"); 138 } 139 } 140 141 class Dog extends Animal { 142 String name = "xxxDog"; 143 @Override 144 public void shout() { 145 System.out.println("汪汪汪"); 146 } 147 148 // 子类自己定义的方法 149 public void special() { 150 System.out.println("special"); 151 } 152 }
4. 多态的优点benefit
1 package com.cskaoyan.polymorphism.benefit; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/18. 5 * @version 1.0 6 * 7 * 多态的好处 8 1.提高了程序的维护性(由继承保证) 9 2.提高了程序的扩展性(由多态保证) 10 11 举例,模拟如下场景: 12 现在有科学家,需要收集(不同)动物的声音并研究 13 a. 假设一开始,科学家,只需要收集2种动物(猫和狗)的声音 14 b. 接着,科学家提出需求,想要收集并研究更多种类的动物的声音(猪,鸭子,猴子...) 15 c. 假设,没过多久,科学家又增加了要研究的动物的类型 16 */ 17 public class Demo1 { 18 19 public static void main(String[] args) { 20 21 Dog dog = new Dog(); 22 collectAndStudy(dog); 23 24 Cat cat = new Cat(); 25 collectAndStudy(cat); 26 27 //使用多态的解决方案 28 Dog dog1 = new Dog(); 29 Cat cat1 = new Cat(); 30 Pig pig1 = new Pig(); 31 32 CollectAndStudyByPoly(dog1); 33 CollectAndStudyByPoly(cat1); 34 CollectAndStudyByPoly(pig1); 35 } 36 /* 37 收集猫的声音并研究的功能 38 */ 39 public static void collectAndStudy(Cat cat) { 40 41 //1. 让猫发出声音 42 cat.shout(); 43 44 //2. 收集声音并研究 45 46 } 47 /* 48 收集猫的声音并研究的功能 49 */ 50 public static void collectAndStudy(Dog dog) { 51 52 //1. 让狗发出声音 53 dog.shout(); 54 55 //2. 收集并研究声音 56 } 57 58 //针对猪 59 public static void collectAndStudy(Pig pig) { 60 //让猪。。。。 61 62 } 63 64 65 /* 66 多态版本的解决方案 67 */ 68 public static void CollectAndStudyByPoly(Animal animal) { 69 // 在父类Animal引用上,调用其shout方法,让动物发出声音 70 // 通过多态,保证,shout方法执行的时候,animal父类引用上调用shout()方法实际发出的叫声 71 animal.shout(); 72 73 //2. 收集声音并研究 74 } 75 76 77 } 78 79 class Animal { 80 String name = "xxxAnimal"; 81 82 public void eat() { 83 System.out.println(" eating"); 84 } 85 86 public void shout() { 87 System.out.println("animal shout"); 88 } 89 } 90 91 class Cat extends Animal{ 92 String name = "xxxCat"; 93 94 public void shout() { 95 System.out.println("喵喵喵"); 96 } 97 } 98 99 class Dog extends Animal { 100 String name = "xxxDog"; 101 102 public void shout() { 103 System.out.println("汪汪汪"); 104 } 105 106 } 107 108 class Pig extends Animal { 109 String name = "xxxPig"; 110 111 public void shout() { 112 System.out.println("猪叫"); 113 } 114 }
5. 多态的缺点flaw, 及解决多态的弊端(instanceof + 强制类型转化)
1 package com.cskaoyan.polymorphism.flaw; 2 3 /** 4 * @author zhangshuai@Cskaoyan.onaliyun.com on 2020/4/18. 5 * @version 1.0 6 * 7 * 多态的弊端:不能访问子类特有功能(多态实现的第3个前提条件 父类 引用 指向 子类对象) 8 * 9 * 继续科学家收集并研究声音的场景: 10 * 1. 科学家在收集鸭子这种动物声音, 11 * 发现了这种动物的一个特点:发出声音之前,必须先在河里游一会,然后才会发出叫声 12 * 13 * 14 * 如何解决多态的缺点呢? 15 * 类型转化来解决 把父类类型的引用 ——> 子类类型的引用 16 * 17 * 为了解决多态的弊端,首先学习 类型转化 相关知识(父类 和 子类) 18 * 父类类型引用 子类类型的引用 19 * 1. 子类类型的引用 赋值 父类类型的引用 (安全的) 向上转型(天然允许) 20 * 2. 父类类型的引用 赋值 子类类型的引用 (可能是不安全的) 向下转型 21 * 22 * 对于2中的引用类型转化,编译器默认不允许,但是我们java语言中,有强制类型转化 23 * 目标类型 目标引用变量 = (目标类型)被转化的引用变量 24 * 25 * int a = 10; 26 * byte b =(byte) a; 27 * 28 * 接下来,引入另外一个关键字 instanceof 关键字(运算符) 29 * 该运算符,就可以用来判断,引用指向对象的实际类型 30 * 31 * 对象名(引用变量) instanceof 实际类型名 32 * 33 * instanceof 运算符的结果类型是boolean类型 34 * 35 */ 36 public class Demo1 { 37 38 39 public static void main(String[] args) { 40 41 Duck duck = new Duck(); 42 Dog dog = new Dog(); 43 Cat cat = new Cat(); 44 45 collectAndStudyByPoly(duck); 46 collectAndStudyByPoly(dog); 47 collectAndStudyByPoly(cat); 48 49 // 引用的类型转化 50 //referenceCast() 51 } 52 53 private static void referenceCast() { 54 // 子类类型的引用,赋值给父类类型的引用 55 Dog dog = new Dog(); 56 57 //子类引用赋值父类引用 58 Animal animal = dog; 59 60 //父类类型的引用 赋值给子类类型的引用 61 // dog = animal; 62 63 Animal an = new Animal(); 64 65 //Animal an = new Duck(); 66 67 // 因为如果父类引用,指向是一个父类对象,把父类类型的引用转化成子类类型的引用之后, 68 // 我们就可以在,子类引用上访问,子类自己的成员 69 // 编译器不允许,当时我们可以用强制类型转化 70 Duck duck1 = (Duck) an; 71 duck1.swim(); 72 } 73 74 /* 75 多态版本的解决方案 76 */ 77 public static void collectAndStudyByPoly(Animal animal) { 78 79 if (animal instanceof Duck) { 80 //对于鸭子,得先让他游动一会 81 Duck duck = (Duck) animal; 82 duck.swim(); 83 } 84 85 // 在父类Animal引用上,调用其shout方法,让动物发出声音 86 // 通过多态,保证,shout方法执行的时候,animal父类引用上调用shout()方法实际发出的叫声 87 animal.shout(); 88 89 //2. 收集声音并研究 90 } 91 } 92 93 94 class Animal { 95 String name = "xxxAnimal"; 96 97 public void eat() { 98 System.out.println(" eating"); 99 } 100 101 public void shout() { 102 System.out.println("animal shout"); 103 } 104 } 105 class Cat extends Animal{ 106 String name = "xxxCat"; 107 108 public void shout() { 109 System.out.println("喵喵喵"); 110 } 111 } 112 class Dog extends Animal { 113 String name = "xxxDog"; 114 115 public void shout() { 116 System.out.println("汪汪汪"); 117 } 118 119 } 120 121 /* 122 鸭子类 123 */ 124 class Duck extends Animal { 125 126 String name = "xxxDuck"; 127 128 public void shout() { 129 System.out.println("呱呱呱"); 130 } 131 132 /* 133 游泳行为 134 */ 135 public void swim() { 136 System.out.println("游泳"); 137 } 138 }