目录
①多态的理解
有了继承的铺垫,就进一步引申出了多态这一面向对象程序的又一大特点,多态是指一个类可以表现出多种形态(继承父类或实现接口而引申出多个形态的派生类)
不同对象在调用同一个方法时表现出的多种不同行为,比方说现有一个person类作为父类,和一个teacher类和student类分别作为person的子类,假设person类中有一个抽象方法名为work,这时在teacher和student类中实现work这个抽象方法时,显然应该实现不同的内容(比如teacher的work是教书,student的work是学习)
②多态的前提
- 有继承(extends)父类或者实现(implements)接口的现象
- 有方法的重写
示例用来理解①②两点:
abstract class person{
abstract void work();
}
//下面两个子类,继承相同类,但实现的抽象方法的内容不同,这就体现出多态这一特点
class teacher extends person{
void work(){
System.out.println("老师的工作是教书");
}
}
class student extends person{
void work(){
System.out.println("学生的工作是学习");
}
}
③多态的好处
使用父类类型作为参数,可以接收所有继承自此父类的子类对象,体现出多态的拓展性和便利性
示例:
/*首先Animal.java文件中,定义了一个Animal类作为父类,
Cat类、Dog类分别作为继承Animal类的子类*/
public class Animal {
String name;
int age;
Animal() {
}
Animal(String name, int age) {
this.name = name;
this.age = age;
}
void shout() {
System.out.println(this.name + "...动物叫");
}
}
class Cat extends Animal {
Cat(String name, int age) {
super(name, age);//super关键字调用父类的构造方法
}
void shout() {//在Cat类中重写Animal父类的shout方法
System.out.println(this.name + "...喵喵喵");
}
}
class Dog extends Animal {
Dog(String name, int age) {//super关键字调用父类的构造方法
super(name, age);
}
void shout() {//在Dog类中重写Animal父类的shout方法
System.out.println(this.name + "...汪汪汪");
}
}
/*然后是Main.java类中,用来测试,
用Animal类、Cat类、Dog类分别示例化3个对象(a,c,d),并传入参数给构造方法构造
然后分别传入a,c,d给func函数,在func函数中调用shout方法*/
public class Main {
public static void main(String[] args) {
Animal a = new Animal("动物名", 19);
func(a);
Cat c = new Cat("小猫咪", 1);
func(c);
Dog d = new Dog("小狗狗", 2);
func(d);
}
//func函数的形参可接收Animal类及其子类的对象
public static void func(Animal a) {
a.shout();
}
}
//输入结果:
/*
动物名...动物叫
小猫咪...喵喵喵
小狗狗...汪汪汪
*/
通过上面这个示例,可以体会到多态的好处,父类类型作为func函数的参数,可以接收所有此类的子类对象,如果没有这一特点,对于func函数,我们就需要编写3个互相重载的函数(即函数的参数列表不同)
④向上转型
1)理解
向上转型是Java多态的一个重要语法现象,正常来说用类实例化一个对象时,应该写成
A类型 对象名 = new A类型();
的格式,当类碰上继承,就出现了接下来->2)这种情况:
2)向上转型的语法格式
- 子类类型 子类对象 = new 子类类型();父类类型 上转型对象 = 子类对象;
- 父类类型 上转型对象 = new 子类类型();
//上面两种写法是一样的,只不过绿色这种是实例化对象和转型同步操作,即"出生就向上转型"
用子类实例化一个对象,但引用给父类对象,这就是向上转型
(
可以理解为把小范围数据类型赋值给大范围数据类型,如把short类型变量s赋值给int类型变量i,
即:
short s = 27;
int a = b;
)
3)对象向上转型的特点
向上转型的对象——
- 调用的变量是父类中的变量,且父类中必须有该变量,否则编译报错
- 调用的方法是子类中的方法,且该方法一定是从父类中继承过来的(可重写)
- 不能调用子类中独有的方法(这一点和上一点相互照应)!
- 自编顺口溜(*^_^*)->"变量只用老子的,方法先看儿子的,儿子独有的不用"
示例用来理解:
public class Main {
public static void main(String[] args) {
//父类类型 父类对象 = new 子类类型();
Game g1 = new Gensin();//g1是Gensin类对象,向上转型成Game类对象
LOL g = new LOL();
Game g2 = g;//g2是LOL类对象,向上转型成Game类对象
System.out.println("对象g1访问到的name是:" + g1.name + ";对象g2访问到的name是:" + g2.name);//访问name变量,访问的变量一定是Game父类中的
g1.func();//这个方法是Gensin子类继承并重写的,调用的方法是Gensin子类中重写后的
g2.func();//这个方法是LOL子类继承但没重写的,调用的方法是Game父类继承给LOL子类的
//g1.func_Gensin();//这个方法是Gensin子类独有的,g1无法调用!
}
}
class Game {
String name = "游戏";
void func() {
System.out.println("游戏...启动");
}
}
class Gensin extends Game {
String name = "原神";
void func() {//重写Game父类的func方法
System.out.println("原神...启动!");
}
void func_Gensin() {//写一个Gensin类独有的方法(即该方法不是继承的)
System.out.println("原来,你也玩原神?");
}
}
class LOL extends Game {
String name = "英雄联盟";
//这里没有重写Game中的func方法
}
4)向上转型的用处
说实话,本人自己在初学多态这一块时,也很懊恼,感觉关系很杂乱,理了很久甚至还编了上面那个绿色的顺口溜,才大概理清。为什么要有向上转型这种语法呢,向上转型有什么实际用处?
先来看一段示例代码:
class Animal {
void work() {
System.out.println("上帝认为,不同的动物应该有不同的工作");
}
void eat() {
System.out.println("上帝认为,不同的动物应该吃不同的食物");
}
void play() {
System.out.println("上帝认为,不同的动物喜欢玩不一样的玩具");
}
}
class Dog extends Animal {
void work() {
System.out.println("看家(不是拆家!)");
}
void eat() {
System.out.println("小狗吃狗粮");
}
void play() {
System.out.println("小狗爱用嘴接飞盘");
}
}
class Cat extends Animal {
void work() {
System.out.println("抓小脑鼠");
}
void eat() {
System.out.println("小猫吃猫粮");
}
void play() {
System.out.println("小猫爱用爪爪摸球");
}
}
假如你开了一家宠物店,有一个孤寡老人在家太孤独,想买一只小动物陪自己,并顺便完成一些工作,比如说买小狗可以看家,买小猫咪可以抓老鼠,还得准备一些生活用品,比如小狗要吃狗粮,小猫要吃猫粮。
对应的代码就有两种实现思路:
public class Main {
public static void main(String[] args) {
//思路1:常规做法
Dog d = new Dog();
d.work();
d.eat();
d.play();
//思路2:向上转型
Animal a = new Dog();
a.work();
a.eat();
a.play();
}
}
听说老人要买小狗,你狗粮、狗链、飞盘什么跟狗相关的杂七杂八的东西都准备好了,突然这时老人改变想法想买猫了(像极了你的甲方?),
这个时候,如果你用的是思路1,那你可惨了,你得改掉用Dog实例化的对象d,以及用它调用的3个方法。如果你用的是思路2,你就能优雅的开一瓶82年的拉菲,优雅地把示例化对象的那行代码的Dog类改成->Cat类,然后优雅地下班(*^_^*)
来对比修改需求后的代码:
public class Main {
public static void main(String[] args) {
//常规做法
Cat c = new Cat();//示例化的对象要改成Cat类实例化的c
c.work();
c.eat();
c.play();//这三行原本用d调用的,都得改成c来调用
//向上转型
Animal a = new Cat();//实例化的子类对象改成Cat
a.work();
a.eat();
a.play();//这三行完全不用管
}
}
你可能觉得改几个名字算不上什么麻烦,当然这只是一个小栗子,实际开发中如果你只会单纯地用一般类去实例化对象,总有一天会来一个需求多变的甲方折磨你的(恶人必被恶人磨hhh)
⑤向下转型
1)为什么要有向下转型这一语法?
上面花了那么大篇幅讲了多态的好处,其实多态还有一个坏处,而且这个坏处前面已经涉及到了->利用多态向上转型这一语法实例化的对象,不能调用子类中独有的方法!如果你忘了这一点,可以回想我顺口溜"变量只用老子的,方法先看儿子的,儿子独有的不用"的第三句话->儿子独有的不用
这个弊端如果不能解决,向上转型的子类只能调用继承父类的方法(可重写),就会限制子类的扩充性、丰富性
向下转型就是用来解决这一弊端的!
看示例:
class Animal {
void work() {
System.out.println("上帝认为,不同的动物应该有不同的工作");
}
}
class Dog extends Animal {
void work() {
System.out.println("看家(不是拆家!)");
}
void swim(){
System.out.println();
}
}
class Rabbit extends Animal {
void work() {
System.out.println("我想不到兔子能干什么...将就一下");
}
}
上述代码中,定义了一个Animal类作为父类,Dog类和Rabbit类作为子类,两个子类都对Animal这个父类的work方法重写了,其中Dog类有一个单独的swim方法(冷知识,兔子很怕水,下水容易寄,所以swim这个方法如果写父类里继承给兔子的话,显然不合适吧?所以单独写在Dog类里)
如果利用多态向上转型的语法,去实例化对象a,然后调用swim方法,显然行不通
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.swim();
//报错,swim这个方法在父类Animal中不存在,a是Dog向上转型对象,只能调用继承自父类的方法
}
}
2)向下转型的语法格式
首先,向下转型必须先出现向上转型,所以一定先包含向上转型的语法格式:
- 父类类型 上转型对象1 = new 子类类型A();//这一行先是向上转型
- 子类类型A 下转型对象 = (子类类型A)上转型对象1;
注意点:
- 必须先向上转型,子类类型A 子类对象 = (子类类型A)new 父类类型();//这种写法是错误的,因为右边new出来的对象没有经过向上转型
- 第2句的括号(子类类型A)不可以省略
(
可以理解为把大范围数据类型赋值给小范围数据类型,如把int类型变量i赋值给short类型变量s,
即:
int i = 10;
short s = (short)i;//这个括号显式强转在Java中是不可省略的!
)
-
第2句的括号(子类类型A)必须和第一句的new 子类类型A对应,这很好理解->假如第一句是Dog类示例化的对象向上转型成Animal类,那么第二句也得是向下转型为Dog类而不是Cat类或Rabbit类(
物种突变!?)
3)示例用来理解1)2)两点
代码续接⑤/1)
public class Main {
public static void main(String[] args) {
Animal a = new Dog();//先向上转型
Dog dog = (Dog) a;//再向下转型
dog.swim();//成功调用swim方法
Rabbit rabbit = (Rabbit) a;
//运行报错,原因:a是由Dog类示例化的对象向上转型得到的,无法向下转型为Rabbit类!
}
}
4)instanceof关键字
作用:
instanceof关键字可以用来判断某个对象是否属于类(包括其父类)
语法格式:
对象 instanceof 类
- 情况1:如果instanceof左边的对象确实属于右边的类,那么整体结果为true
- 情况2:否则结果就是false
实际用途:
在运用向下转型语法时,可以利用instanceof关键字配合if条件语句来避免(减少)转型出错的情况
示例:那么⑤/3)中的代码就可以优化成如下代码,增强代码的健壮性——
public class Main {
public static void main(String[] args) {
Animal a = new Dog();//先向上转型
if (a instanceof Dog) {
Dog dog = (Dog) a;//再向下转型
dog.swim();//成功调用swim方法
} else if (a instanceof Rabbit) {
Rabbit rabbit = (Rabbit) a;
}
}
}
写了好久,终于可以睡觉了┭┮﹏┭┮