目录
引出多态思想
举例:
水:固态,液态,气态
固态的水是水,液态的水是水,气态的水也是水
水果:西瓜,菠萝,葡萄
西瓜是水果,菠萝是水果,葡萄是水果
动物:狗,猫,兔子
狗是动物,猫是动物,兔子是动物
可以说狗是动物,西瓜是水果,但是反过来说却是错误的,不能说水果是西瓜,动物是狗
综上所述,这就是多态思想
多态
1.多态概述
某一个事物,在不同时刻表现出来的不同状态
2.多态的前提
1)有继承关系
2)有方法重写
其实没有方法重写也是可以的,但是没有意义。比如动物都有吃这个方法,但是每个具体的动物吃的实现不一样,表现出不同动物的特有属性
3)有父类引用指向子类对象
父类名 对象名 = new 子类名(...);
3.多态访问成员的特点
1)成员变量:编译看左,运行看左
即编译看父类中有没有成员变量,如果有,编译不报错;运行取决于父类中的成员变量
class Fu3 {
int num = 100;
}
class Zi3 extends Fu3 {
int num = 1000;
}
public class PolymorphicDemo1 {
public static void main(String[] args) {
//多态创建一个对象
Fu3 f = new Zi3();
System.out.println(f.num); //10
}
}
2)构造方法:
创建子类对象的时候,会先访问父类的构造方法,对父类的数据先进行初始化
3)成员方法:编译看左,运行看右
即编译看父类中有没有成员方法,如果有,编译不报错;运行取决于子类中的成员方法
class Fu3 {
int num = 100;
public void show() {
System.out.println("这是父类中的show()方法");
}
}
class Zi3 extends Fu3 {
int num = 1000;
@Override
public void show() {
System.out.println("这是子类中的show()方法");
}
}
public class PolymorphicDemo1 {
public static void main(String[] args) {
//多态创建一个对象
Fu3 f = new Zi3();
f.show();
}
}
4)静态成员方法:编译看左,运行看左
由于被static修饰的成员都是与类相关的,这里不是重写(静态成员方法不能被重写),所以运行的时候,访问的还是左边
class Fu3 {
public static void fun() {
System.out.println("这是父类中的静态fun方法");
}
}
class Zi3 extends Fu3 {
public static void fun() {
System.out.println("这是子类中的静态fun方法");
}
}
public class PolymorphicDemo1 {
public static void main(String[] args) {
//多态创建一个对象
Fu3 f = new Zi3();
f.fun();
}
}
4.多态的题目——”套娃“
1)看代码是否编译成功
class Fu4 {
public void show() {
System.out.println("fu show");
}
}
class Zi4 extends Fu4 {
public void show() {
System.out.println("zi show");
}
public void method() {
System.out.println("zi method");
}
}
public class Test1 {
public static void main(String[] args) {
Fu4 f = new Zi4();
// f.method(); //编译失败,父类中没有method方法
}
}
2)看代码,输出结果
class A2{
public void show(){
show2();
}
public void show2(){
System.out.println("我");
}
}
class B2 extends A2{
public void show2(){
System.out.println("爱");
}
}
class C2 extends B2{
public void show(){
super.show();
}
public void show2(){
System.out.println("你");
}
}
public class Test2 {
public static void main(String[] args) {
A2 a = new B2();
a.show(); //爱。B2类中隐藏了一个show方法,实现和父类一样,是继承过去的
B2 b = new C2();
b.show(); //你。
}
}
5.多态的猫狗案例
之前的方法
class Animal {
String name;
int age;
Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println("睡觉");
}
public void eat() {
System.out.println("吃");
}
}
class Dog extends Animal {
public Dog() {
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("狗侧着睡");
}
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
class Cat extends Animal {
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("猫趴着睡");
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class CatDogDemo {
public static void main(String[] args) {
//我想养一只狗
Dog d1 = new Dog("小白", 2);
d1.sleep();
d1.eat();
//我还想养一只狗
Dog d2 = new Dog("旺财", 3);
d2.sleep();
d2.eat();
//我还想养一只狗
Dog d3 = new Dog("小黑", 4);
d3.sleep();
d3.eat();
}
每new完一次对象的时候,都要调用一遍eat()和sleep(),用方法改进
class Animal {
String name;
int age;
Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println("睡觉");
}
public void eat() {
System.out.println("吃");
}
}
class Dog extends Animal {
public Dog() {
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("狗侧着睡");
}
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
class Cat extends Animal {
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("猫趴着睡");
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class CatDogDemo {
public static void main(String[] args) {
//我想养一只狗
Dog d1 = new Dog("小白", 2);
useDog(d1); //用方法改进
//我还想养一只狗
Dog d2 = new Dog("旺财", 3);
useDog(d2); //用方法改进
//我还想养一只狗
Dog d3 = new Dog("小黑", 4);
useDog(d3);
//我想养一只猫
Cat c1 = new Cat("Tom", 2);
useCat(c1);
//我还想养一只猫
Cat c2 = new Cat("奶茶", 3);
useCat(c2);
}
//对狗的睡觉和吃饭进行调用
public static void useDog(Dog dog){
dog.sleep();
dog.eat();
}
//对猫的睡觉和吃饭进行调用
public static void useCat(Cat cat){
cat.sleep();
cat.eat();
}
}
如果我现在不想养猫和狗了,我想养熊猫
第一步先创建这个类,第二步写一个调用熊猫吃和睡觉的方法
但是我们将来还可能养其他的动物,兔子,狮子,老虎等等
这些动物按照上面的实现都要写一个类,在测试类中写一个方法
当我的动物特别多的时候,显得特别臃肿
这时候我们用工具类改进
class Animal {
String name;
int age;
Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println("睡觉");
}
public void eat() {
System.out.println("吃");
}
}
class Dog extends Animal {
public Dog() {
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("狗侧着睡");
}
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
class Cat extends Animal {
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("猫趴着睡");
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class AnimalTool {
private AnimalTool() {
}
//对狗的睡觉和吃饭进行调用
public static void useDog(Dog dog){
dog.sleep();
dog.eat();
}
//对猫的睡觉和吃饭进行调用
public static void useCat(Cat cat){
cat.sleep();
cat.eat();
}
}
public class CatDogDemo {
public static void main(String[] args) {
//我想养一只狗
Dog d1 = new Dog("小白", 2);
AnimalTool.useDog(d1); //用工具类调用
//我还想养一只狗
Dog d2 = new Dog("旺财", 3);
AnimalTool.useDog(d2); //用工具类调用
//我还想养一只狗
Dog d3 = new Dog("小黑", 4);
AnimalTool.useDog(d3); //用工具类调用
}
}
但是,工具类理论上创建以后,不能随意更改
要想养其他的动物就要往工具类加一个方法
这样虽然能实现,但是不符合规范
继续用多态改进
class Animal {
String name;
int age;
Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println("睡觉");
}
public void eat() {
System.out.println("吃");
}
}
class Dog extends Animal {
public Dog() {
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("狗侧着睡");
}
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
class Cat extends Animal {
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("猫趴着睡");
}
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class Panda extends Animal {
public Panda() {
}
public Panda(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("熊猫随意睡");
}
@Override
public void eat() {
System.out.println("熊猫吃竹子");
}
}
class Tiger extends Animal {
public Tiger() {
}
public Tiger(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void sleep() {
System.out.println("老虎躺着睡");
}
@Override
public void eat() {
System.out.println("老虎吃鸡");
}
}
class AnimalTool {
private AnimalTool() {
}
//调用的时候,相当于Animal animal = new Panda("圆圆",3)
//Animal animal = new Dog("小白",3)
//Animal animal = new Tiger("小虎",2)
public static void useAnimal(Animal animal) {
animal.sleep();
animal.eat();
}
//利用二多态访问成员方法的特点,编译看左,运行看右
//实际调用的是子类对象中的方法
}
public class CatDogDemo {
public static void main(String[] args) {
//我想养一只狗
Dog d1 = new Dog("小白", 2);
AnimalTool.useAnimal(d1); //用多态改进
//养一只熊猫
Panda p2 = new Panda("圆圆", 3);
AnimalTool.useAnimal(p2); //用多态改进
//养一只老虎
Tiger t1 = new Tiger("小虎", 2);
AnimalTool.useAnimal(t1); //用多态改进
}
}
6.多态的好处
提高了程序的维护性(由继承保证)
提高了程序的扩展性(由多态保证)
7.多态的弊端
多态无法访问父类中的方法名与子类一样的方法
不能访问子类特有的功能
8.多态中的转型问题
如果想使用子类中的特有方法,还必须使用多态,怎么办呢?
想一想之前的数据类型知识,将子类看成一个小的数据类型,将父类看成大的数据类型。我们要将大的数据类型转换成小的数据类型,之前的做法是强制类型转换。
在继承关系中,java为我们提供了一个技术给我们使用——向下转型
将父类的引用强制转换成子类的引用
定义格式:
子类类名 变量名 = (子类类名)父类的引用
class Father2 {
public void show() {
System.out.println("这是父类中的show方法");
}
}
class Son2 extends Father2 {
@Override
public void show() {
System.out.println("这是子类中的show方法");
}
public void show2() {
System.out.println("这是子类中的特有方法");
}
}
public class PolymorphicDemo3 {
public static void main(String[] args) {
//多态创建对象
Father2 f = new Son2();
// f.show2(); 报错,父类中没有show2方法
//将父类的引用进行向下转型
//子类类名 变量名 = (子类类名)父类的引用
Son2 s = (Son2)f;
s.show2();
s.show();
}
}
向下转型需要注意的问题:
要求转型的类和父类引用存在继承关系,并且一开始创建多态的时候,使用的是该类
class Father2 {
public void show() {
System.out.println("这是父类中的show方法");
}
}
class Son2 extends Father2 {
@Override
public void show() {
System.out.println("这是子类中的show方法");
}
public void show2() {
System.out.println("这是子类中的特有方法");
}
}
class Demo extends Father2{
}
public class PolymorphicDemo3 {
public static void main(String[] args) {
//多态创建对象
Father2 f = new Son2();
// Demo d = (Demo)f; 报错
}
}
9.向下转型的例子及内存解释
class Animal2 {
public void eat() {
System.out.println("吃");
}
}
class Dog2 extends Animal2 {
@Override
public void eat() {
System.out.println("狗吃肉");
}
public void lookDoor() {
System.out.println("看门");
}
}
class Cat2 extends Animal2 {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫捉老鼠");
}
}
public class PolymorphicDemo4 {
public static void main(String[] args) {
//多态的形式创建一个对象
Animal2 a = new Dog2();
a.eat();
// a.lookDoor(); 报错
//向下转型访问子类中特有的方法
Dog2 d = (Dog2) a;
d.eat();
d.lookDoor();
//ClassCastException类转换异常
// Cat2 c = (Cat2) a; //此刻内存中是一个Dog2对象
// c.catchMouse();
}
}
Step1:程序先将class文件(Animal2.class,Dog2.class,Cat2.class,PolymorphicDemo4.class)加载到方法区中的class文件区。每个文件都存放着成员变量,成员方法
Step2:把每个class文件的方法加载到方法区
Aniaml2的方法区的内容:eat()方法(假设这一部分地址值为0x005)
Dog2的方法区的内容:eat()方法,lookDoor()方法(假设这一部分地址值为0x004)
Cat2的方法区的内容:eat()方法,catchMouse()方法(假设这一部分地址值为0x003)
PolymorphicDemo4的方法区的内容:(假设这一部分地址值为0x002)
静态区:
PolymorphicDemo4的静态区内容:静态的成员方法main()(假设这一部分地址值为0x001)
Step3:加载完毕,java虚拟机寻找到java的唯一入口main方法,去静态区中找,如果找到了将它加载到栈中去执行
Step4:读到Anamal2 a在栈中开辟空间,读到new Dog2在堆中开辟空间(里面存放着Dog2方法区的地址值0x004),(假设堆内存创建出来的对象地址值为0x0001),将地址值0x0001赋给a,a可以根据地址值找到堆内存中的这块区域
Step5:读到a.eat();,编译看左,看Animal2中有没有eat()方法,有不报错;运行看右,调用的是Dog2中的eat()方法,根据地址值0x0001找到堆内存中创建出来的Dog2对象区域,根据方法区的标记0x004找到eat()方法,把它加载到栈中来执行,执行结束,被释放,等待回收
Step6:读到a.lookDoor();,编译看左,看Animal2中有没有lookDoor()方法,没有报错
Step7:读到Dog2 d在栈中开辟空间,把a的地址值0x0001赋给它
Step8:读到d.eat();,根据地址值0x0001找到堆内存中创建出来的Dog2对象区域,根据方法区的标记0x004找到eat()方法,把它加载到栈中来执行,执行结束,被释放,等待回收
Step8:读到d.lookDoor();,根据地址值0x0001找到堆内存中创建出来的Dog2对象区域,根据方法区的标记0x004找到eat()方法,把它加载到栈中来执行,执行结束,被释放,等待回收
Step7:读到Cat2 c在栈中开辟空间,把a的地址值0x0001赋给它,
c是Cat2类型的,a是Dog2类型的,它俩之间不存在继承关系,也不是父类引用指向子类对象,报错
10.多态的练习
不同地方饮食文化与习惯的区别
class Person {
public void eat() {
System.out.println("吃");
}
}
class SouthPerson extends Person {
@Override
public void eat() {
System.out.println("南方人吃米饭");
}
public void playMaJiang(){
System.out.println("南方人打麻将");
}
}
class NorthPerson extends Person{
@Override
public void eat() {
System.out.println("北方人吃面食");
}
public void bath(){
System.out.println("北方人搓澡");
}
}
public class PolymorphicDemo5 {
public static void main(String[] args) {
//多态创建南方人对象
Person p = new SouthPerson();
p.eat();
// p.playMaJiang(); //报错
//向下转型
SouthPerson sp = (SouthPerson)p;
sp.playMaJiang();
//多态创建北方人对象
Person p2 = new NorthPerson();
p2.eat();
// p2.bath(); //报错
//向下转型
NorthPerson np = (NorthPerson)p2;
np.bath();
}
}