Java的三大特征:封装、继承、多态
插入一个知识点
对象数组
数组是我们经常使用的一个数据容器,像我们之前使用int[]、char[]等。这些大都是基本数据类型的数组,除了这些我们还可以创建对象的数组,之前我们使用过的int[][]就是一种。元素是对象的数组就是对象数组。
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
-
对象数组的内存图分析
多态(polymorphism)
多态是继封装、继承之后,面向对象的第三大特性。也是面向对象思想的核心。
我们通过代码先看一个案例,引入多态性这个重要特性。
定义一个Animal类
public class Animal {
//String name = "animal";
public void eat(){
System.out.println("动物吃饭");
}
}
定义一个Dog类
public class Dog extends Animal {
//继承Animal类
//String name = "dog";
@Override
public void eat() {
System.out.println("狗啃骨头");
}
}
定义一个Person类
public class Person {
//喂狗吃饭
public void feed(Dog dog){
dog.eat();
}
}
测试类
public class Test1 {
public static void main(String[] args) {
Person p=new Person();
//测试人喂狗吃饭
p.feed(new Dog());
}
}
这时如果需要拓展功能,增加一个猫Cat类,让人喂Cat吃饭,应该如何做?
很简单,在Person类中增加喂猫的方法,那如果后期还要不断的增加新功能(新增各种动物类),就要不断增加Person类对应的方法。这里就出现了一个问题:违背了软件开发中的开闭原则(对修改关闭,对扩展开放),并且大量类同代码冗余,即每次新增的方法除了参数类型不同,其他都基本相同。
能不能用简单方式解决?
只需要在Person类中定义一个类似的方法,参数为Animal,其他全舍去,这就是多态的应用。
//修改后的Person类
public class Person {
//喂动物吃饭
public void feed(Animal animal){
animal.eat();
}
}
再次测试,功能正常,增加新的动物类测试,发现不需要修改Person类,功能依然正常。
帮助理解:狗和猫统称为动物
什么是多态
就像液体在不同的温度下呈现不同的状态一样。Java的多态也是指在不同的子类下,父类里面被子类重写的方法也会有不同的功能。
1、多态的形式
Java规定父类类型的引用变量可以接收子类类型的对象。这里父子类是相对的,有直系亲属的。
格式:
父类类型 变量名 = 子类对象;
父类类型:指子类继承的父类类型,或者实现的父接口类型。
所以说继承是多态的前提
例如:
//多态的引用形式
Animal a = new Cat();//Cat对象属于其父类Animal类型。
2、多态的执行过程
多态形式在执行的过程中:
- 编译阶段会先在父类类型中查看是否有要调用的方法,如果有的话(子类重写了父类的这个方法)编译通过,如果没有的话编译不通过。
- 编译过了之后在运行阶段会按照子类的方法去运行。
一句话概括了就是:编译看左边,运行看右边。
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a = new Cat();
// 编译时,a变量呈现Animal类型特征,即Animal类中有eat方法,a调用eat方法时编译才能成功
a.eat();//运行时,实际执行的是Cat类中重写的eat方法。
//a1.catchMouse();//错误,编译时左边父类Animal中没有此方法
}
}
3、多态的优点
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
- 提高程序的扩展性
- 降低类与类之间的耦合度
多态的应用形式
1、多态应用在成员变量和方法参数
- 方法的形参是父类类型,调用方法的实参是子类对象
- 实例变量声明父类类型,实际存储的是子类对象
代码如下:
public class OnePersonOnePet {
private Pet pet;
public void adopt(Pet pet) {
//形参是父类类型,实参是子类对象
this.pet = pet;
}
public void feed(){
pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同