“我曾经被问到‘求教,Baddage先生,如果你向机器中输入错误的数字,可以得到正确的答案吗?’ 我无法恰当地理解产生这种问题的概念上的混淆。”
--Charles Babbage(1791-1871)
1、多态的概述
多态方法的调用允许一种类型表现处与其他相似类型之间的区别,只要他们是从同一个基类中导出而来的。这种区别是根据方法行为的不同而表现出来的,虽然这些方法都可以通过同一个基类来调用。
简单的说,某一个事物在不同状态下的多种状态
2、实现多态的三大前提
- 必须要有继承关系
- 要有方法的重写(思考可否不重写?)
不是必须要重写的,重写可以体现子类的专属特征。
- 要有父类的引用指向子类对象
使用多态第三个前提的注意事项:
1.必须是继承关系的类,才可以写成父类的引用指向子类对象
2.左边的父类,可以不必是子类对象的直接父类,也可以是父类的父类
代码体现
package day09;
class Demo1{
}
class Power{
}
class Water extends Power{
public void getPower(){
System.out.println("提供能量");
}
}
class GutaiWater extends Water{
@Override
public void getPower(){
System.out.println("可以嚼着吃");
}
}
public class DuoTaiDemo1 {
public static void main(String[] args) {
//Demo1 w1 = new GutaiWater();
// 错误的,Demo1类与GutaiWater类没有继承关系
Water w2 = new GutaiWater();//从右往左读,固态的水是水
Power p1 = new GutaiWater();
}
}
3、访问成员的特点
多态下,访问成员的特点:
1、成员变量
编译看左,允许看左。
2、成员方法
编译看左,运行看右。
思考:如何在多态的状态下,使用子类中特有的方法呢?
3、静态的成员方法
编译看左,运行看左。
package day09;
class Fu1{
int a1 = 10;
public void fun1(){
System.out.println("这是父类中的非静态方法fun1");
}
public static void fun2(){
System.out.println("这是父类中的静态方法fun2");
}
}
class Zi1 extends Fu1{
int a = 11;
@Override
public void fun1(){
System.out.println("子类中重写的非静态方法fun1");
}
public void show1(){
System.out.println("天天向上,好好学习!");
}
public static void fun2(){
System.out.println("这是子类中的静态方法fun2");
}
}
public class DuoTaiDemo2 {
public static void main(String[] args) {
//使用多态的方式创建一个子类对象
Fu1 f1 = new Zi1();
System.out.println(f1.a1); // 编译的时候,看左边父类中是否有该变量存在,若存在,编译不报错,运行的结果也是取父类中该变量的值。
f1.fun1();// 编译的时候,看左边父类中是否有该方法存在,若存在,编译不报错,运行的结果也是取子类的中该方法的实现。
// f1.show1();
// f1.fun2(); // 静态的成员方法是跟随类走的,什么类型的变量调用静态方法,就是该类中的静态方法
Fu1.fun2();
// Zi1 zi1 = new Zi1();
// System.out.println(zi1.a1);
}
}
4、多态的好处
1.提高了程序的维护性(继承)
2.提高了程序的扩展性(多态)
可以从通用的基类继承出新的数据类型,从而新添加一些功能,而那些操纵基类接口的方法不需要任何改动就可以应用于新类。
package day09;
import java.nio.channels.Pipe;
/*
多态的好处
1、提高了程序的维护性(由继承保证)
2、提高了程序的扩展性(由多态保证)
*/
class Animal{
String name;
int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("🐕吃🥩");
}
@Override
public void sleep() {
System.out.println("🐕侧着睡");
}
}
class Pig extends Animal{
@Override
public void eat() {
System.out.println("🐖吃杂食");
}
@Override
public void sleep() {
System.out.println("🐖趴着睡");
}
}
class Snake extends Animal{
@Override
public void eat() {
System.out.println("🐍吃🐀");
}
@Override
public void sleep() {
System.out.println("🐍盘着睡");
}
}
class Tiger extends Animal{
@Override
public void eat() {
System.out.println("🐅吃🥩");
}
@Override
public void sleep() {
System.out.println("🐅趴着睡");
}
}
//写一个工具类,对每一个动物调用吃和睡的功能
class AnimalTool{
private AnimalTool(){}
// public static void useDog(Dog dog){
// dog.eat();
// dog.sleep();
// }
//
// public static void usePig(Pig pig){
// pig.eat();
// pig.sleep();
// }
//
// public static void useSnake(Snake snake){
// snake.eat();
// snake.sleep();
// }
public static void useAnimal(Animal animal){ //Animal animal = new Tiger();
animal.eat();
animal.sleep();
}
}
public class DuoTaiDemo5 {
public static void main(String[] args) {
//我想一只🐕
Dog d1 = new Dog();
// d1.eat();
// d1.sleep();
//使用工具类的方式调用动物的吃和睡的功能
// AnimalTool.useDog(d1);
AnimalTool.useAnimal(d1);
//我想一只🐖
Pig p2 = new Pig();
// p2.eat();
// p2.sleep();
// AnimalTool.usePig(p2);
AnimalTool.useAnimal(p2);
//我想养🐍
//按照上面的写法,应该先写一个🐍的类,然后在类中重写eat和sleep方法
//在工具类中添加一个调用🐍的吃和睡的方法
Snake s1 = new Snake();
// AnimalTool.useSnake(s1);
AnimalTool.useAnimal(s1);
//随着我们要养动物的种类越来越多,每一种动物都需要经过上面两个步骤
//创建该动物的类我们能理解,这是必须要有的
//但是第二步在工具类进行添加修改其实是有问题。
//今后的开发中,工具类是不允许频繁修改的。
//但是我们不添加方法的话,就没法使用工具类调用具体动物的功能
//于是,我们就在想,怎么可以只在工具类中写一个方法,可以调用所有动物的功能呢?
//可以使用多态来实现。
//我现在养一只🐅
Tiger t1 = new Tiger();
AnimalTool.useAnimal(t1); // new Tiger()
}
}
5、多态的弊端
弊端:在多态的形式下,无法使用子类中特有的成员方法
解决方案:向下转型(曹操和曹植的故事)
曹操和曹植的故事,曹操是曹植的父亲,曹植是曹操的儿子
class 曹操{
public void skill(){
带兵打仗
}
}
class 曹植 extends 曹操{
@Override
public void skill(){
下棋
}
public void zuoShi(){
作诗
}
}
某一天,曹操带兵打仗出城了,城里只有曹植,这时候刘备待人过来攻打城池,但是小兵只听曹操的指令。
为了守护城池,曹植想到一个办法,装爹,粘上胡子,穿上爹的衣服,调用跟爹一样的skill方法。
//向上转型
曹操 c1 = new 曹植();
c1.skill();
// c1.zuoShi(); 不能调用
曹操打仗回来了,曹植看到父亲回来后,不用继续装爹,做回自己,脱掉爹的衣服,撕掉假胡子
//向下转型
曹植 c2 = (曹植) c1;
c2.skill();
c2.zuoShi();
代码展示:
package day09;
class Fu2{
public void fun1(){
System.out.println("Hello World");
}
}
class Zi2 extends Fu2{
@Override
public void fun1(){
System.out.println("Hello Java");
}
public void show1(){
System.out.println("编程有意思");
}
}
public class DuoTaiDemo3 {
public static void main(String[] args) {
Fu2 f1 = new Zi2();
f1.fun1();
// f1.show();//根据多态访问成员的特点,编译看左,父类中没有show1方法,编译报错。
//向下转型
Zi2 z1 = new Zi2();
z1.show1();
}
}
但是向下转型同样也会遇到问题,在Java中所有的转型都会得到检查,以便保证它是我们想得到的类型。如果不是,就会返回一个ClassCastException(类转型异常)。这种在运行期间对类型进行检查的行为被称为“运行时类型识别”(RTTI)。
6、抽象类
1、抽象方法
Java中提供一个叫做抽象方法的机制,这种方法是不完整的;仅有