【从零开始的Java开发】1-4-3 多态:概念、实现、向上转型、向下转型、instanceof、类型转换、抽象类、抽象方法

多态,即多种形态。我们可以认为,封装和继承都是为了多态而准备的。

概念

引入:生物都会吃东西,如猫、狗、人。但猫、狗、人吃的东西不同,吃东西的具体动作也不同。这就是多态。

多态:允许不同类的对象对同意消息做出不同的响应。

多态可以分为:

  • 编译时多态:也称为设计时多态,一般通过方法重载来实现
  • 运行时多态:程序运行时动态决定调用哪个方法

Java中的多态大多都是运行时多态。

多态实现的必要条件:

  • 满足继承关系
  • 父类引用指向子类对象

程序中的继承的实现

在这里插入图片描述
Animal类:

public class Animal {
	// 属性:昵称,年龄
	private String name;
	private int month;

	// 方法:吃东西
	public void eat() {
		System.out.println("Animal eat!");
	}

	// 构造函数
	public Animal() {
		System.out.println("Animal构造函数!");
	}

	public Animal(String name, int month) {
		this.name = name;
		this.month = month;
		System.out.println("Animal 双参构造!");
	}

	// Getter Setter
	public String getName() {
		return this.name;
	}

	public int getMonth() {
		return this.month;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setMonth(int month) {
		this.month = month;
	}

}

Cat类:

public class Cat extends Animal {
	// 属性:体重
	private double weight;

	// 构造
	public Cat() {

	}

	public Cat(String name, int month, double weight) {
		super(name, month);
//		//下两行跟super是一样效果
//		this.setMonth(month);
//		this.setName(name);
		this.weight = weight;
	}

	// 方法:跑
	public void run() {
		System.out.println("Cat run!");
	}

	// 方法:重写eat
	@Override
	public void eat() {
		System.out.println("Cat eat!");
	}

	// Getter Setter
	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}
}

Dog类:

public class Dog extends Animal {
	// 属性:性别
	private String sex;

	// 构造函数
	public Dog() {

	}

	public Dog(String name, int month, String sex) {
		this.setName(name);
		this.setMonth(month);
		this.sex = sex;
	}

	// 方法:睡觉
	public void sleep() {
		System.out.println("Dog Sleep!");
	}

	// 方法:重写eat
	@Override
	public void eat() {
		System.out.println("Dog eat!");
	}

	// Getter Setter
	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}
}

向上转型

测试类:

public class Test {
	public static void main(String[] args) {
		Animal one=new Animal();
		Animal two=new Cat();
		Animal three =new Dog();
		
		//测试多态
		one.eat();
		two.eat();
		three.eat();
	}
}

输出:

Animal构造函数!
Animal构造函数!
Animal构造函数!
Animal eat!
Cat eat!
Dog eat!

ps:Animal构造函数!是Animal无参构造的输出,这里说明每实例化一个对象都会调用一次Animal的无参构造——但本章重点不在这里。

这里的Animal two=new Cat();Animal three =new Dog();其实是向上转型(也称为隐式转型、自动转型)——父类引用指向子类实例
好理解的联想:孩子的灵魂放到父亲的身体里,子类的实例化放到父类的引用里。

对于two这个对象,虽然它new的是Cat类,但它无法访问Cat自己的成员属性:(weight也无法访问)
在这里插入图片描述
也就是说,向上转型之后,父类引用指向子类实例,可以调用子类重写父类的方法以及父类派生的方法,子类所特有的方法无法直接使用。

向下转型

向下转型,也叫强制类型转换,是将子类引用指向父类对象,此处必须强转,可以调用子类特有的方法。
如:

//向下转型
		Cat temp=(Cat)two;
		temp.eat();
		temp.run();

输出:

Cat eat!
Cat run!

但是,DogCat不能强转。如:

Dog temp=(Dog)two;
temp.eat();

输出:

在这里插入图片描述

instanceof 运算符

可以用instanceof判断某个对象是否是某个类型的实例,是就返回true,反之返回false。

如:

Dog temp=(Dog)three;
		
System.out.println(three instanceof Dog);
System.out.println(three instanceof Animal);
System.out.println(three instanceof Cat);
System.out.println(three instanceof Object);

输出:

true
true
false
true

由此可知,instanceof可以放到强制类型转换的前面防止报错,这里的三个true分别是因为,twoCat类的,AnimalCat的父类,ObjectAnimal的父类。

类型转换案例

需求1

主人Master类:

public class Master {
	/*
	 * 喂宠物 喂猫:喂完让它跑 喂狗:喂完让他睡
	 */

	public void feed(Cat cat) {
		cat.eat();
		cat.run();
	}

	public void feed(Dog dog) {
		dog.eat();
		dog.sleep();
	}
}

主人测试类:

public class MasterTest {
	public static void main(String[] args) {
		Master master = new Master();
		Cat one = new Cat();
		Dog two = new Dog();

		master.feed(one);
		master.feed(two);
	}
}

这是我们只有猫狗两个对象的方法,若我们养了很多宠物,它们都要feed,如果一个个地写会很麻烦,那应该怎么办呢?

方案:传入的参数为猫狗…类的父类,方法中通过类型转换,调用指定子类的方法。

public void feed(Animal obj) {
		if (obj instanceof Cat) {
			Cat cat = (Cat) obj;
			cat.eat();
			cat.run();
		} else if (obj instanceof Dog) {
			Dog dog = (Dog) obj;
			dog.eat();
			dog.sleep();
		}
	}

测试一下,输出:

Animal构造函数!
Animal构造函数!
Cat eat!
Cat run!
Dog eat!
Dog Sleep!

需求2

如果主人时间多,就养狗,否则养猫。

方案1
Master类增加:

public Dog ManyTime() {
	System.out.println("时间多,养狗!");
	return new Dog();
}

public Cat LittleTime() {
	System.out.println("时间少,养猫!");
	return new Cat();
}

测试类:

boolean isManyTime = true;
Animal animal;
if (isManyTime) {
	animal = master.ManyTime();
} else {
	animal = master.LittleTime();
}
System.out.println(animal);

输出:

时间多,养狗!
Animal构造函数!
com.Animal.Dog@43a25848

方案2:
Master类:

public Animal raise(boolean isManyTime) {
	if (isManyTime) {
		System.out.println("时间多,养狗!");
		return new Dog();
	} else {
		System.out.println("时间少,养猫!");
		return new Cat();
	}
}

测试类:

boolean isManyTime = true;
Animal animal=master.raise(isManyTime);
System.out.println(animal);

输出:

时间多,养狗!
Animal构造函数!
com.Animal.Dog@43a25848

总结

向上转型

  • 父类引用指向具体实例(具体实例放进父类引用里,相当于小物件放进大盒子,是向上)
  • Animal animal=new Cat()

Animal animal;
Cat cat=new Cat();
animal=cat;

向下转型

  • 子类引用指向父类对象(大物件放进小盒子,是向下)
  • Cat cat=new Animal()

父类中有static的方法,这个方法是不允许被重写的。

Animal类有:

public static void say() {
		System.out.println("动物间打招呼~");
	}

Cat类有:

@Override
	public static void say() {
		System.out.println("Cat Hello~");
	}

则会报错:@Override表示这是一个重写的方法——
在这里插入图片描述
但如果去掉@Override就不会报错了。
在这里插入图片描述
那么,对象到底调用的是哪个say呢?

测试类1:

Animal one=new Animal();
Cat two=new Cat();
Dog three =new Dog();

one.say();
two.say();
three.say();

输出1:(省略了构造函数的输出)

动物间打招呼~
Cat Hello~
动物间打招呼~

测试类2:

Animal one=new Animal();
Animal two=new Cat();
Animal three =new Dog();

one.say();
two.say();
three.say();

输出2:

动物间打招呼~
动物间打招呼~
动物间打招呼~

这说明,父类引用指向子类实例,可以调用子类重写父类的方法以及父类派生的方法,但无法调用子类独有方法。
注意:父类中的静态方法无法被子类重写,所以向上转型后,只能调用到父类原有的静态方法。
这里Cat的say算是Cat类独有的方法,原因在上一章
在这里插入图片描述
如果就像调用子类独有的方法,强转回来即可。如:

Animal two=new Cat();		
two.say();
Cat twoo=(Cat)two;		
twoo.say();

输出:

动物间打招呼~
Cat Hello~

抽象

抽象类

在这里插入图片描述
语法没问题,但实例化Animal没有意义。

有没有一种方法,可以直接写出符合程序逻辑的代码?
有。abstract关键字可以。

Java中使用抽象类,限制实例化。抽象类就是用abstract修饰的类,如:

public abstract class Animal{
}

我们在Animal前加abstract修饰,则Animal two=new Animal();会报错。
在这里插入图片描述
抽象类:不允许实例化,可以通过向上转型,指向子类实例

应用场景:某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。

抽象类可以避免子类的设计随意性,也可以避免父类的无意义实例化。

抽象方法

引入:对于吃这个动作,每种动物有它自己的吃的方法。而每种动物的父类Animal有吃这个方法只是为了描述“动物都要吃”这件事情。
于是,我们把在父类中只是限定行为能力的方法设置为抽象方法——就是用abstract修饰的方法。

定义:public abstract void eat();

  • abstract放在返回值前
  • 不能有方法体
  • 子类必须重新实现抽象方法,否则,子类也是抽象类

若子类没有重新实现抽象方法,会报错,如:
在这里插入图片描述
为什么需要抽象类、抽象方法?不可以让Animal中的方法是空方法吗?
答:

  • 不一定非要用抽象类
  • 但我们需要去提醒每一个实际的类要去实现自己的方法
  • 所以可以用抽象类、抽象方法来提醒我们

总结

使用规则:

  • abstract定义抽象类
  • 抽象类不能直接实例化,只能被继承,可以通过向上转型完成对象实例
  • abstract定义抽象方法,不需要具体实现
  • 包含抽象方法的类一定是抽象类
  • 抽象类中可以没有抽象方法
  • 抽象类的子类必须重写父类的抽象方法,否则子类也是抽象类
  • staticfinalprivate不能和abstract并存,因为abstract是需要子类重写的,而staticfinalprivate是不能重写的

总结

多态的分类

  • 编译时多态(设计时多态):方法重载
  • 运行时多态:JAVA运行时系统根据调用该方法的实例类型来决定选择调用哪个方法

我们平时说的多态常常是运行时多态。

多态

  • 向上类型转换:将子类型转换为父类型——右边是子:隐式/自动类型转换,是小类型到大类型的转换
  • 向下类型转换:将父类型转换为子类型:强制类型转换,是大类型到小类型

通过instanceof运算符,来解决引用对象的类型,避免类型转换的安全性问题,提高代码的健壮性。

抽象类&抽象对象 的 应用场景
某个父类只是限定其子类应该包含怎样的方法,但不需要准备知道这些子类如何实现这些方法。

抽象类:用abstract修饰类

public abstract class Aniaml{
}

抽象方法:用abstract修饰方法

public abstract void eat();

注意

  1. 抽象类不能直接实例化
  2. 子类如果没有重写父类所有的抽象方法,则也要定义为抽象类
  3. 抽象方法所在的类一定是抽象类
  4. 抽象类中可以没有抽象方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值