多态是继封装、继承之后,面对对象的第三大特征。
多态的现实意义:
- 现实事物经常会体现多种形态,比如学生,学生是人的一种,则一个具体的学生小明即是学生也是人,即出现两种形态;
- Java 作为面向对象的语言,同样可以描述一个事物的多种形态。如 Student 类继承了 Person 类,一个 Student 的对象既是 Student,又是 Person。
- 多态的体现为父类变量可以指向子类对象(前提条件是必须有父子类关系);
- 在使用多态后的父类变量在调用方法时,会调用子类重写后的方法;
多态的定义:父类类型 变量名 = new 子类类型();
多态的理解:
- 多态是同一个行为具有多种不同表现形式或形态的能力;
- 多态就是同一个接口,使用不同的实例而执行不同的操作;
多态中成员的特点:
- 多态成员变量,编译运行看左边;
Person p = new Student();
System.out.println(p.num); // num 是 Person 中的值
- 多态成员方法:编译看左边,运行看右边;
Person p = new Student();
System.out.println(p.show()) // 调用的是 Student 中重写的方法
instanceof
关键字:用来判断某个对象是否属于某种数据类型,返回类型为布尔类型:
public class Test {
public static void main(String[] args) {
Person p = new Student();
if (p instanceof Student) {
System.out.println("p 是 Student 类型");
} else {
System.out.println("p 不是 Student 类型");
}
}
}
// p 是 Student 类型
多态的转型:分为向上转型和向下转型
- 向上转型:多态本身就是向上转型的过程,
父类类型 变量 = new 子类类型()
。当不需要面对子类类型时,通过使用父类的功能就能完成相应的操作; - 向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用类型,
子类类型 变量 = (子类类型)父类类型变量
。当需要使用子类特有的功能时,可以使用向下转型;
public class Test {
public static void main(String[] args) {
Person person = new Student();
person.eat(); // student eat
Student student = (Student) person;
student.study(); // student study
}
}
class Person {
public void eat() {
System.out.println("people eat");
}
}
class Student extends Person {
@Override
public void eat() {
System.out.println("student eat");
}
public void study() {
System.out.println("student study");
}
}
class Teacher extends Person {
@Override
public void eat() {
System.out.println("teacher eat");
}
public void teach() {
System.out.println("teacher teach");
}
}
请问,题目的运行结果是什么?
public class Test {
public static void main(String[] args) {
Shape shape = new Shape();
shape.show(); // shape
Circle circle = new Circle();
circle.show(); // circle
CircularCone circularCone = new CircularCone();
circularCone.show(); // circular cone
}
}
class Shape {
public void show() {
showShape();
}
public void showShape() {
System.out.println("shape");
}
}
class Circle extends Shape {
@Override
public void showShape() {
System.out.println("circle");
}
}
class CircularCone extends Circle {
@Override
public void show() {
super.show();
}
@Override
public void showShape() {
System.out.println("circular cone");
}
}
多态分为两种:
- 编译时多态:方法的重载;
- 运行时多态:Java 运行时系统会根据调用该方法的实例的类型来决定选择调用哪个方法;
- 面对对象的三大特性:封装、继承、多态。 从一定的角度来看,封装和继承几乎都是为多态而准备的;
- 多态的定义:指允许不同的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用不同的行为方式(消息即函数调用);
- 实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用的对象的实际类型,根据实际类型调用其相应的方法;
多态的作用:消除类型之间的耦合关系。
多态存在的三个必要条件:继承、重写、父类引用指向子类对象。
多态的好处:
- 可替换性(subsitutability):多态对已存在的代码具有可替代性。 例如,多态对 Shape 类工作,对其他的 Shape,如 Circle 也同样工作;
- 可扩充性(extensibility):多态对代码具有可扩充性,增加新的子类不影响已存在的类的多态性、继承性,以及其他特性的运行和操作,实际上新加子类更容易获得多态功能。 例如,在实现了 CircularCone(圆锥),更容易实现半圆椎;
- 接口性(interface-ability):多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的;
- 灵活性(flexibility):在应用中体现了灵活多样的操作,提供了实用效率;
- 简化性(simplicity):多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运行和操作时,这个特点尤为突出和重要;
Demo 1:
在上面的例子中,当 Father 的 getName()
方法被注释掉以后,调用 father.getName()
方法会出错。因此,当父类引用指向子类方法时,必须调用那些父类中存在的方法,如果子类中对该方法进行了重写,那么在运行时就会动态调用子类中的方法,这就是多态。修改如下:
public class Test {
public static void main(String[] args) {
Test test = new Test();
// 注意:采用此种方式创建实例,否则静态方法中只能创建静态内部类的实例
Test.Father father = test.new Son();
System.out.println(father.getName()); // son
}
public class Father {
private String name = "father";
public String getName() {
return this.name;
}
}
public class Son extends Father {
private String name = "son";
@Override
public String getName() {
return this.name;
}
}
}
输出 son
。继续看下面的例子:
public class Test {
public static void main(String[] args) {
Test test = new Test();
// 注意:采用此种方式创建实例,否则静态方法中只能创建静态内部类的实例
Test.Shape shape1 = test.new Shape();
Test.Shape shape2 = test.new Circle();
Test.Circle circle = test.new Circle();
Test.CircleCone circleCone = test.new CircleCone();
shape1.show(circle); // Shape and Shape
shape1.show(circleCone); // Shape and CircleCone
shape2.show(circle); // Circle and Shape
shape2.show(circleCone); // Shape and CircleCone
}
public class Shape {
public void show(Shape shape) {
System.out.println("Shape and Shape");
}
public void show(CircleCone circleCone) {
System.out.println("Shape and CircleCone");
}
}
public class Circle extends Shape {
public void show(Circle circle) {
System.out.println("Circle and Circle");
}
public void show(Shape shape) {
System.out.println("Circle and Shape");
}
}
public class CircleCone extends Circle {
public void show(CircleCone circleCone) {
System.out.println("CircleCone and CircleCone");
}
public void show(Circle circle) {
System.out.println("CircleCone and Circle");
}
}
}
shape2.show(circle)
打印出来的是 Circle and Shape
,这里为什么不是 Circle and Circle
呢?这是因为,当超类变量引用子类对象时,被引用对象的类型而不是变量的类型决定了调用谁的成员方法。但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
首先,“当超类变量引用子类对象时,被引用对象的类型而不是变量的类型决定了调用谁的成员方法”,这句话的意思也就是说,当运行 shape2.show(circle)
时,是 Circle
类来决定调用谁的方法。但是,在其父类 Shape
中并没有定义过 show(Circle circle)
方法,也就无所谓 “子类覆盖”。因此,这里就涉及到方法调用的优先级。
优先级由高到低的依次为:this.show(O)
-> super.show(O)
-> this.show((super)O)
-> super.show((super)O)
。
shape2.show(circle)
,this
指的是 shape2
,但是在 Shape
中没有找到 show(Circle circle)
方法,于是到 Shape
的父类中去找,由于 Shape
没有父类,接着到第三优先级 this.show((super)O)
,this
仍然是 shape2
,O
为 Circle
,也就是 (super)Circle
,也就是 Circle
的父类 Shape
,在 Shape
中查找 show(Shape shape)
方法,Shape
中有这个方法,但是,shape2
指向的是一个 Circle
对象,在 Circle
中重写了 show(Shape shape)
方法,因此,最终调用的是 Circle.show(Shape shape)
方法,打印出 Circle and Shape
。
接着看下面的例子:
public class Test {
public static void main(String[] args) {
Shape shape = new Circle();
shape.show(); // circle
Circle circle = new CircleCone();
circle.show(); // circle cone
}
}
class Shape {
public void show() {
show2();
}
public void show2() {
System.out.println("shape");
}
}
class Circle extends Shape {
public void show2() {
System.out.println("circle");
}
}
class CircleCone extends Circle {
public void show2() {
System.out.println("circle cone");
}
}
由于 shape = new Circle()
,在运行到 shape.show()
时,会调用 Circle.show()
方法,在 Circle
类中并没有定义这个方法,因此,会调用 Circle
的父类 Shape.show()
方法。在 Shape.show()
方法中会调用 show2()
方法,这个方法已经被子类实现了,因此调用子类的 show2()
方法。