Java基础学习笔记(十二)—— 多态
You want something. Go get it!
| @Author:TTODS
什么是多态?
多态是同一个行为具有多个不同表现形式或形态的能力。(下面的例子可以帮助理解)
我们先建一个Person类,再建一个Person
类的子类Student
类,Student类中都重写了toString
的方法,且Student
比Person
类多了一个School
的成员变量。
package first;
public class Person{
String name;
int age;
Person(String _name,int _age){
name = _name;
age = _age;
}
public String toString() {
return String.format("Person [ name:%s , age:%d]",name,age);
}
}
package first;
public class Student extends Person{
String school;
Student(String _name,int _age,String _school){
super(_name,_age);//调用父类的构造方法
school = _school;
}
public String toString() {
return String.format("Student [ name:%s , age:%d, school:%s]",name,age,school);
}
public static void main(String[] args) {
Person A = new Person("小A",20); //Person类型的引用指向Person类型的实例
Person B = new Student("小B",20,"XX学校");//Person类型的引用指向Student类型的实例
Student C = new Student("小C",10,"XX学校");//Student类型的引用指向Student类型的实例
System.out.println(A);
System.out.println(B);
System.out.println(C);
}
}
输出:
Person [ name:小A , age:20]
Student [ name:小B , age:20, school:XX学校]
Student [ name:小C , age:10, school:XX学校]
从上例可以看到一个奇怪的用法,其中的变量B
是一个Person
类型的引用,但却指向了一个Student
的实例,但这并不难理解,Student
是Person
的子类,或者说Student
也是一个Person
.值得注意的是对于上面三个Person
引用调用的toString
方法却有所不同,准确的说是调用了实例的方法。现在因该不难理解"一样的类型的同一个方法有不同的表现形式"这句话了。
多态发生的前提条件
- 继承。多态发生一定要子类和父类之间。
- 覆盖。子类覆盖了父类的方法。
- 声明的变量类型是父类类型,但实例则指向子类实例。
多态有什么用?
先看下面这例子:狼人杀是前段时间很火的一个游戏,我们先建一个玩家类,如下:Player
类有一个成员变量name
.
package first;
public class Player{
protected String name;
Player(String n){
name = n;
}
void dead() {
System.out.printf("%s(玩家) 出局了,OOO~\n",name);
}
建两个Player
类的子类,Seer
类和Villager
类,他们都重写dead
方法。
package first;
public class Seer extends Player{
Seer(String n){
super(n);//调用父类的构造函数
}
void dead() {
System.out.printf("%s(先知) 出局了,OOO~\n",name);
}
}
package first;
public class Villager extends Player{
Villager(String n){
super(n);
}
void dead() {
System.out.printf("%s(村民) 出局了,OOO~\n",name);
}
}
再建一个Werewolf
类,它也重写了dead
方法(虽然此例中不需要用),此外他新增了一个kill
方法和用于运行的main方法。
package first;
public class Werewolf extends Player{
Werewolf(String n){
super(n);
}
void dead() {
System.out.printf("%s(狼人) 出局了,OOO~\n",name);
}
void kill(Player p) {
p.dead();
}
public static void main(String[] arg) {
Werewolf werewolf1 = new Werewolf("小T");
Seer seer = new Seer("小白");
Villager villager1 = new Villager("小红");
werewolf1.kill(seer);
werewolf1.kill(villager1);
}
}
输出
小白(先知) 出局了,OOO~
小红(村民) 出局了,OOO~
从上例中可以看到,在Werewolf
类中的kill
方法接受的参数是Player类型,在kill中调用的是Player
的dead
;但是kill
却可以接收Seer
类型和Villager
类型的变量,并且分别调用它们的dead
方法。这给我们带来了极大的便利,因为我们没必要重载多个接受不同参数的kill
方法。
instanceof 关键字
有时我们需要用instanceof关键字来判断一个对象的具体类型,方法如下:
改写第一个例子中的Student
类的main
方法
package first;
public class Student extends Person{
String school;
Student(String _name,int _age,String _school){
super(_name,_age);
school = _school;
}
public String toString() {
return String.format("Student [ name:%s , age:%d, school:%s]",name,age,school);
}
public static void main(String[] args) {
Person A = new Person("小A",20);
Person B = new Student("小B",20,"XX学校");
Student C = new Student("小C",10,"XX学校");
System.out.println("A instanceof Person: "+(A instanceof Person));
System.out.println("A instanceof Student: "+(A instanceof Student));
System.out.println("B instanceof Person: "+(B instanceof Person));
System.out.println("B instanceof Student: "+(B instanceof Student));
System.out.println("C instanceof Person: "+(C instanceof Person));
System.out.println("C instanceof Student: "+(C instanceof Student));
System.out.println("C instanceof Object: "+(C instanceof Object));
}
}
输出
A instanceof Person: true
A instanceof Student: false
B instanceof Person: true
B instanceof Student: true
C instanceof Person: true
C instanceof Student: true
C instanceof Object: true
不支持多态的方法
先了解这样一个概念:方法绑定,即将一个方法调用和一个方法主体关联起来。方法绑定又分为前期绑定和后期绑定,前期绑定就是在程序运行之前(比如编译过程)进行绑定。但是回到我们多态发生的特殊情景,由于引用类型与实例类型并不相同,编译器只知道有一个Person
类型的引用,并不知道它指向的是一个Student
的实例,它无法知道应该调用谁的方法,于是就有了后期绑定即运行时绑定。(关于方法绑定在《on java8》(《java编程思想第5版》)第九章中有详细介绍)。
Java 中除了 static
和 final
方法(private
方法也是隐式的 final
)外,其他所有方法都是后期绑定。也就是说static
、final
和private
方法都是直接调用引用类型的方法。
package first;
public class Person{
String name;
int age;
Person(String _name,int _age){
name = _name;
age = _age;
}
static void sleep() {
System.out.println("Person sleep()");
}
final void code() {
System.out.println("Person code()");
}
private void eat() {
System.out.println("Person eat()");
}
public String toString() {
return String.format("Person [ name:%s , age:%d]",name,age);
}
}
package first;
public class Student extends Person{
String school;
Student(String _name,int _age,String _school){
super(_name,_age);
school = _school;
}
public String toString() {
return String.format("Student [ name:%s , age:%d, school:%s]",name,age,school);
}
public void eat() {
System.out.println("Student eat()");
}
static void sleep() {
System.out.println("Student sleep()");
}
public static void main(String[] args) {
Person A = new Person("小A",20);
Person B = new Student("小B",20,"XX学校");
Student C = new Student("小C",10,"XX学校");
//B.eat(); //编译错误,eat()是Person的private方法,在Student中不可视,虽然Student中也写了eat()方法,但这是一个全新的eat方法并不是继承。
B.code();
B.sleep();
}
}
输出
Person code()
Person sleep()
①eat()
是Person
的private
方法,在Student
中不可视,虽然Student
中也写了eat()
方法,但这是一个全新的eat方法并不是继承。
②code()
是Person
的final
方法,在Student
中无法覆盖,不能重写