本篇文章是《零基礎學Java》專欄的第六篇文章,文章採用通俗易懂的文字、圖示及程式碼實戰,從零基礎開始帶大家走上高薪之路!
本文章首發於公眾號【程式設計攻略】
繼承
建立一個Person類
我們建立一個用於描述人
的類。我們怎麼抽象出一個人
這個類呢?我們以不同的角度做抽象,得到的屬性和行為都會有些區別。這裡,我們主要從人
的社會屬性來抽象。為了表示性別,我們先頂一個列舉型別,該列舉型別中有兩個值,用於表示男
、女
,程式碼如下:
public enum Sex {male,female}
上面的程式碼寫入單獨的一個原始碼檔案中:Sex.java
,因為上面的列舉型別是需要公開使用的,所以,也需要定義為public,因此,必須將之放在獨立的原始檔中 。
再定義Person這個類,程式碼如下:
public class Person{
private String name;//姓名
private Sex sex;//性別
private Date birthday;//出生日期
private String ID;//身份證號
//下面是一系列上述私有成員變數的setter和getter
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setSex(Sex sex){
this.sex = sex;
}
public Sex getSex(){
return sex;
}
public void setBirthday(Date date){
birthday = date;
}
public Date getBirthday(){
return birthday;
}
public void setID(String ID){
this.ID = ID;
}
public String getID(){
return ID;
}
public Person(String name, Sex sex, Date birthday, String ID){
this.name = name;
this.sex = sex;
this.birthday = birthday;
this.ID = ID;
}
}
我們編譯上面的Person.java
,它所涉及到的相關的類也會一併編譯完成,如:Sex.java
檔案。
定義學生
子類
如果我們考慮一個班級的組成,大致有三種類別的人員,學生
、教師
、輔導員
,我們依次進行定義。這三類都具有人
所具有的屬性,因此,我們沒必要在三個類中重新定義那些公共屬性,我們只需繼承Person
類即可。當然,這些類一定也具有不同於普通人
的屬性和行為。先定義學生
類。
public class Student extends Person {
private String studentID;//學號
private Date rollInDate;//入學時間
public void setStudentID(String studentID){
this.studentID = studentID;
}
public String getStudentID(){
return studentID;
}
public void setRollInDate(Date rollInDate){
this.rollInDate = rollInDate;
}
public Date getRollInDate(){
return rollInDate;
}
public Student(String name,
Sex sex,
Date birthday,
String ID,
String studentID,
Date rollInDate){
super(name,sex,birthday,ID);//對父類的構造方法的呼叫
//雖然Student能夠繼承父類中的私有成員,
//但是卻不能直接像使用本類中的成員變數一樣直接呼叫,
//因此,下句是不能呼叫的
//this.name = name;
this.studentID = studentID; //本類的成員變數,當然可以這麼寫
this.rollInDate = rollInDate;
}
}
Student類繼承了Person中所有的成員,包括私有的,但是對於子類雖然擁有繼承自父類的成員,卻不能直接使用。子類可以直接使用的繼承來的成員有public和protected修飾的,對於無修飾符的成員變數,如果子類同父類不在同一個包,也是不能使用的。示例如下:
class A{
private int x = 0;
protected int y = 1;
public int z = 2;
int m = 3;
A(){
System.out.println("在父類裡");
}
A(String s){
System.out.println("在父類裡 "+s);
}
}
public class B extends A{
//在此處加上int x;會怎麼樣呢?
B(){
System.out.println("在子類裡");
}
B(String s){
super(s);
System.out.println("在子類裡 "+s);
}
public static void main(String s[]){
B b=new B("對嗎?");
System.out.println(b.x);//不對哦
System.out.println(b.y);
System.out.println(b.z);
System.out.println(b.m);
}
}
繼承具有以下特徵:
-
子類只能繼承自一個父類,它繼承父類中的所有成員,甚至包括父類中private所修飾的成員變數或方法。子類中可以定義新的成員變數和成員方法,也可以對父類中的成員變數及成員方法進行重新定義,這種對方法的重新定義稱為
複寫(override)
-
如果改變了父類中某些功能,而這些功能在子類中未進行復寫,那麼修改父類的功能改變會影響到子類。
-
父類具有所有繼承自它的子類的共同的特徵和行為。
繼承最基本的作用:程式碼重用。 繼承最重要的作用:方法可以複寫。
super的使用
我們在Student
的構造方法中使用了supper(name,sex,birthday,ID);
,這是對父類的構造方法的呼叫。對於繼承關係的類,子類的構造方法總是會呼叫父類的構造方法:如果沒有顯式呼叫,系統會自動呼叫父類的無參構造方法,但是一旦顯式呼叫,系統就不會再呼叫父類中的無參構造方法。
class A{
A(){
System.out.println("in A()");
}
}
class B extends A{
B(){
System.out.println("in B()");
}
}
class C extends B{
C(){
System.out.println("in C()");
}
}
class TestExtends{
public static void main(String[] args){
C c=new C();
}
}
super的使用同this相似,但是它們兩者的本質是不同的,this是引用,而super不是,super指向的不是父類物件,它代表當前子類物件中繼承自父類的屬性和行為。如圖:
什麼時候使用super呢?當子類和父類中具有同名的成員時,例如,子類和父類中都有name這個屬性,如果要在子類物件中訪問繼承自父類中的name屬性,就需要使用 super
進行區分。
super可以用在什麼地方?
- 第一:super和this一樣可以用在非靜態方法中,不能用在靜態方法中。
- 第二:super可以用在構造方法中。一個構造方法第一行如果沒有
this(...);
,也沒有顯式的去呼叫super(...);
,系統會預設呼叫super(),因此,如果父類中沒有預設構造方法,則會出錯(大家如果註釋掉上面程式碼中的super,測試一下); - 注意:
super(...);
的呼叫只能放在構造方法的第一行。因此,super(....)
和this(....)
不能共存。super(...);
只是呼叫了父類中的構造方法,並不會建立父類物件。
複寫(override)
複寫指子類重定義了父類中的同名方法。什麼時候方法要進行復寫?如果父類中的方法已經無法滿足當前子類的業務需求,需要將父類中的方法功能進行重新進行定義。子類如果複寫父類中的方法之後,子類物件一定呼叫的是複寫之後的方法。
發生方法複寫的條件:
- 發生在具有繼承關係的兩個類之間
- 複寫發生在繼承關係中,必須具有
相同的方法名,相同的返回值型別,相同的引數列表
。 - 複寫後的方法不能比被複寫的方法擁有縮小的訪問許可權。
- 複寫後的方法所丟擲的異常必須和被覆蓋方法的所丟擲的異常一致,或者是其子類;
- 私有的方法不能被複寫,但可以在子類定義和父類中同名的方法(但不稱之為複寫)
- 構造方法無法被複寫。因為子類同父類的構造方法不同。
- 複寫指的是成員方法,和成員變數無關。
class A{
private int x=0;
protected int y=1;
public int z=2;
int m=3;
void t1(){
System.out.println("在父類t1裡");
}
}
public class B extends A{
void t1(){
super.t1();
System.out.println("在子類t1裡");
}
public static void main(String s[]){
B b=new B();
b.t1();
System.out.println(b.m);
}
}
final關鍵字
final關鍵字可以出現在類名前、方法前、區域性變數前、成員變數前、形參前,它具有如下作用:
- final修飾的類無法被繼承
- final修飾的方法無法被複寫
- final修飾的區域性變數,一旦賦值,不可再改變
- final修飾形參,形參的值在方法體中也無法改變
- final修飾的成員變數必須手動初始化,不能取它的預設值,一般和static聯用,如:
public static final int i = 100;
- final修飾的引用型別,該引用不可再重新指向其他的java物件。但是final修飾的引用指向的物件的屬性是可以修改的,切不可混為一談。
定義教師
子類
教師類除了它具有作為人
所具有的特徵之外,更多的是教師
的教務特徵和功能,由此,我們先定義一個描述課程的類:Course
,如下:
public class Course{
private String courseName;
private int courseId;
public Course(int courseId, String courseName){
this.courseId = courseId;
this.courseName = courseName;
}
public void setCourseName(String courseName){
this.courseName = courseName;
}
public String getCourseName(){
return courseName;
}
public void setCourseId(int courseId){
this.courseId = courseId;
}
//toString方法繼承自Object類
public String toString(){
System.out.println("課程編號:" + courseId + "\n課程名稱:" + courseName);
}
}
我們定義教師
類:Teacher
如下:
public class Teacher extends Person {
private String teacherID;//工號
private Date rollInDate;//入職時間
private Course[] teachingCourse; //教授的課程
public void setTeacherID(String studentID){
this.teacherID = teacherID;
}
public String getTeacherID(){
return teacherID;
}
public void setRollInDate(Date rollInDate){
this.rollInDate = rollInDate;
}
public Date getRollInDate(){
return rollInDate;
}
public void setTeachingCourse(Course[] teachingCourse){
this.teachingCourse = teachingCourse;
}
public Course[] getTeachingCourse(){
return teachingCourse;
}
public Teacher(String name,
Sex sex,
Date birthday,
String ID,
String teacherID,
Date rollInDate,
Course[] teachingCourse){
super(name,sex,birthday,ID);//對父類的構造方法的呼叫
//雖然Student能夠繼承父類中的私有成員,
//但是卻不能直接像使用本類中的成員變數一樣直接呼叫,
//因此,下句是不能呼叫的
//this.name = name;
this.teacherID = teacherID; //本類的成員變數,當然可以這麼寫
this.rollInDate = rollInDate;
this.teachingCourse = teachingCourse;
}
}
定義輔導員
類
輔導員是教師,但輔導員在教師的功能基礎上又多了一些管理功能,所以輔導員類應該繼承自教師類,程式碼如下:
public class Counsellor extends Teacher{
}
轉型(cast)
對於上面所定義的類,我們可以說:a student is a person
,a teacher is a person
,a counsellor is a teacher
,a counsellor is a person
。從這些說法,我們可以看出來一個子類物件的型別是可以看作父類型別的。因此,下面的語句是合法的:
Person p = new Student();
這種情況,我們稱之為向上轉型(upcast)
,這有點類似於基本型別中的自動型別轉換。
有時候,我們也需要把某個父類物件當作一個子類物件使用,我們不鼓勵這麼用,因為這是不自然的,就像我們不能說:a person is a counsellor
一樣,如果非要這麼做,我們必須像基本型別的強制型別轉換一樣使用強制轉換,我們稱之為向下轉型(downcast)
,如下例:
Teacher teacher = new Teacher();
Counsellor counsellor = (Counsellor)teacher;
上例中,我們使用了預設構造方法,但是,我們並沒有定義相關類的預設構造方法,我們只是為了說明轉型
這個概念,大家為了使得上例合法,要麼使用帶參的構造方法,要麼,修正前面定義的相關類。
轉型示例:
class T1{
int i = 1;
}
class T2 extends T1{
int i = 2;
}
public class TUpcast{
public static void main(String[] args){
T2 t2 = new T2();
System.out.println("t2.i=" + t2.i +"upcast t2.i=" + ((T1)t2).i);
//這說明子類中可以重新定義父類中的成員變數,同名的可以隱藏父類中的同名變數,也就是說他們是不同的變數
}
}
定義班級
類
一個班級的基本組成,有若干學生、若干教師、一個輔導員,我們定義班級類如下:
public class Classes{
public String className;
public int classId;
public Student[] students;
public Teacher[] teachers;
public Counsellor counsellor;
public Classes(int classId,
String className,
Student[] students,
Teacher[] teachers,
Counsellor counsellor){
this.classId = classId;
this.className = className;
this.students = students;
this.teachers = teachers;
this.counsellor = counsellor;
}
}
一旦某個類建立及測試後,它便具有了某種有用的功能,我們可以建立該類的物件,通過該物件,使用定義好的功能。除此之外,我們還可以通過使用類的繼承機制進行復用,也可以把物件放入一個新的類中,稱為成員物件,這種新類由其它類物件組成的情況,稱為組合(composition)。如上面的Classes
類就是由已有的類成員組合而成。
多型
我們由下面的例子來解釋什麼是多型,如下:
class Animal{
void eat(){
}
}
class Cat extends Animal{
void eat(){
System.out.println("魚好吃!");
}
void chasingMouse(){
System.out.println("追啊追,追的好開心!");
}
}
class Rabbit extends Animal{
void eat(){
System.out.println("蘿蔔比魚好吃!");
}
void run(){
System.out.println("我比烏龜跑得快!");
}
}
class Test{
public static void callEat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat = new Cat();
Rabbit rabbit = new Rabbit();
//下面的兩個calEat方法在執行的時候,會根據傳入的實參型別,選擇執行不同類中的eat
//這種情況就是多型
callEat(cat);
callEat(rabbit);
//animal變數存放的為其子類的物件,執行子類物件中的eat方法
Animal animal = cat;
animal.eat();
animal = rabbit;
animal.eat();
//animal.chasingMouse();
((Rabbit)animal).run(); //原本animal引用的就是Rabbit物件,downcast是可以的
((Cat)animal).chasingMouse();//原本animal引用的是Rabbit,rabbit是不能轉換為cat的
}
}
多型性是通過過載和複寫及upcast來體現的。使用多型可以使程式碼之間的耦合度降低。專案的擴充套件能力增強。