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

Posted karshey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始的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. 抽象类中可以没有抽象方法

以上是关于从零开始的Java开发1-4-3 多态:概念实现向上转型向下转型instanceof类型转换抽象类抽象方法的主要内容,如果未能解决你的问题,请参考以下文章

从零开始的Java开发1-4-4 多态与内部类:接口:定义并测试抽象方法常量默认方法静态方法重名默认方法和重名静态方法的解决方案继承;成员静态方法匿名 内部类

从零开始的Java开发 笔记目录(持续更新)

从零开始做第三方支付开发-----初步了解概念

从零开始的Java开发1-6-3 多线程:概念Thread类和Runnable接口创建线程线程的状态和生命周期sleep和join方法优先级同步线程间通信

阿里云名师课堂Java面向对象开发64:多态性

从零开始创建自己的区块链应用(JAVA版)