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
中無法覆蓋,不能重寫