學習完類與對象終于認識到什麼是類,什麼是對象了。接下來要看的就是java的三大特征:繼承、封裝、多态。
一、封裝(資料的隐藏)
在定義一個對象的特性的時候,有必要決定這些特性的可見性,即哪些特性對外部是可見的,哪些特性用于表示内部狀态。
通常,應禁止直接通路一個對象中資料的實際表示,而應通過操作接口來通路,這稱為資訊隐藏。
1.1、封裝的步驟
1).使用private 修飾需要封裝的成員變量。
2.)提供一個公開的方法設定或者通路私有的屬性
設定 通過set方法,命名格式: set屬性名(); 屬性的首字母要大寫
通路 通過get方法,命名格式: get屬性名(); 屬性的首字母要大寫
1.2、舉例
//對象不僅能再類中方法,還能在類的外部"直接"通路
public class Student{
public String name;
public void println(){
System.out.println(this.name);
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.name = "tom";
}
}
複制
在類中一般不會把資料直接暴露在外部的,而使用private(私有)關鍵字把資料隐藏起來
例如:
public class Student{
private String name;
}
public class Test{
public static void main(String[] args){
Student s = new Student();
//編譯報錯,在類的外部不能直接通路類中的私有成員
s.name = "tom";
}
}
複制
如果在類的外部需要通路這些私有屬性,那麼可以在類中提供對于的get和set方法,以便讓使用者在類的外部可以間接的通路到私有屬性
例如:
//set負責給屬性指派
//get負責傳回屬性的值
public class Student{
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.setName("tom");
System.out.println(s.getName());
}
}
複制
1.3、封裝的作用
1)架構
2)工具類
1.4、封裝的意義
1)隐藏代碼的實作細節
2)統一使用者的調用接口
3)提高系統的可維護性
二、方法的重載
類中有多個方法,有着相同的方法名,但是方法的參數各不相同,這種情況被稱為方法的重載。
方法的重載可以提供方法調用的靈活性。
例如:System.out.println()中的println方法,為什麼可以把不同類型的參數傳給這個方法?
例如:
public class Test{
public void test(String str){
}
public void test(int a){
}
}
複制
方法重載必須滿足一下條件:
1)方法名相同
2)參數清單不同(參數的類型、個數、順序的不同)
public void test(Strig str){}
public void test(int a){}
public void test(Strig str,double d){}
public void test(Strig str){}
public void test(Strig str,double d){}
public void test(double d,Strig str){}
3)方法的傳回值可以不同,也可以相同。
注:在java中,判斷一個類中的倆個方法是否相同,主要參考倆個方面:方法名字和參數清單
三、繼承
1)繼承是類和類之間的一種關系
除此之外,類和類之間的關系還有依賴、組合、聚合等。
2)繼承關系的倆個類,一個為子類(派生類),一個為父類(基類)。
子類繼承父類,使用關鍵字extends來表示
例如:
public class student extends Person{
}
3)子類和父類之間,從意義上講應該具有"is a"的關系.
例如:
student is a person
dog is a animal
4)類和類之間的繼承是單繼承
一個子類隻能"直接"繼承一個父類,就像是一個人隻能有一個親生父親
一個父類可以被多子類繼承,就像一個父親可以有多個孩子
注:java中接口和接口之間,有可以繼承,并且是多繼承。
5)父類中的屬性和方法可以被子類繼承
子類中繼承了父類中的屬性和方法後,在子類中能不能直接使用這些屬性和方法,是和這些屬性和方法原有的修飾符(public protected default private)相關的。
例如:
父類中的屬性和方法使用public修飾,在子類中繼承後"可以直接"使用
父類中的屬性和方法使用private修飾,在子類中繼承後"不可以直接"使用
注:具體細則在修飾符部分詳細說明
父類中的構造器是不能被子類繼承的,但是子類的構造器中,會隐式的調用父類中的無參構造器(預設使用super關鍵字)。
注:具體細節在super關鍵字部分詳細說明
6)Object類
java中的每一個類都是"直接" 或者 "間接"的繼承了Object類.是以每一個對象都和Object類有"is a"的關系。從API文檔中,可以看到任何一個類最上層的父類都是Object。(Object類本身除外)
AnyClass is a Object
例如:
System.out.println(任何對象 instanceof Object);
//輸出結果:true
注:任何對象也包含數組對象
例如:
//編譯後,Person類會預設繼承Object
public class Person{}
//Student是間接的繼承了Object
public class Student extends Person{}
在Object類中,提供了一些方法被子類繼承,那麼就意味着,在java中,任何一個對象都可以調用這些被繼承過來的方法。(因為Object是是以類的父類)
例如:toString方法、equals方法、getClass方法等
注:Object類中的每一個方法之後都會使用到.
四、super關鍵字
子類繼承父類之後,在子類中可以使用this來表示通路或調用子類中的屬性或方法,使用super就表示通路或調用父類中的屬性和方法。
4.1、super的使用
1)通路父類中的屬性
例如:
public class Person{
protected String name = "zs";
}
public class Student extends Person{
private String name = "lisi";
public void tes(String name)t{
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
複制
2)調用父類中的方法
例如:
public class Person{
public void print(){
System.out.println("Person");
}
}
public class Student extends Person{
public void print(){
System.out.println("Student");
}
public void test(){
print();
this.print();
super.print();
}
}
複制
3)調用父類中的構造器
例如:
public class Person{
}
public class Student extends Person{
//編譯通過,子類構造器中會隐式的調用父類的無參構造器
//super();
public Student(){
}
}
複制
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//編譯報錯,子類構造器中會隐式的調用父類的無參構造器,但是父類中沒有無參構造器
//super();
public Student(){
}
}
複制
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//編譯通過,子類構造器中顯式的調用父類的有參構造器
public Student(){
super("tom");
}
}
複制
注:不管是顯式還是隐式的父類的構造器,super語句一定要出現在子類構造器中第一行代碼。是以this和super不可能同時使用其調用構造器的功能,因為它們都要出現在第一行代碼位置。
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//編譯報錯,super調用構造器的語句不是第一行代碼
public Student(){
System.out.println("Student");
super("tom");
}
}
複制
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
//編譯通過
public class Student extends Person{
private int age;
public Student(){
this(20);
}
public Student(int age){
super("tom");
this.age = age;
}
}
複制
4.2、super使用的注意的地方
1)用super調用父類構造方法,必須是構造方法中的第一個語句。
2)super隻能出現在子類的方法或者構造方法中。
3)super 和 this 不能夠同時調用構造方法。(因為this也是在構造方法的第一個語句)
4.3、super 和 this 的差別
1)代表的事物不一樣:
this:代表所屬方法的調用者對象。
super:代表父類對象的引用空間。
2)使用前提不一緻:
this:在非繼承的條件下也可以使用。
super:隻能在繼承的條件下才能使用。
3)調用構造方法:
this:調用本類的構造方法。
super:調用的父類的構造方法
五、方法重寫(方法覆寫)
1)方法重寫隻存在于子類和父類(包括直接父類和間接父類)之間。在同一個類中方法隻能被重載,不能被重寫.
2)靜态方法不能重寫
a. 父類的靜态方法不能被子類重寫為非靜态方法 //編譯出錯
b. 父類的非靜态方法不能被子類重寫為靜态方法;//編譯出錯
c. 子類可以定義與父類的靜态方法同名的靜态方法(但是這個不是覆寫)
例如:
A類繼承B類 A和B中都一個相同的靜态方法test
B a = new A();
a.test();//調用到的是B類中的靜态方法test
A a = new A();
a.test();//調用到的是A類中的靜态方法test
可以看出靜态方法的調用隻和變量聲明的類型相關
這個和非靜态方法的重寫之後的效果完全不同
3)私有方法不能被子類重寫
子類繼承父類後,是不能直接通路父類中的私有方法的,那麼就更談不上重寫了。
例如:
public class Person{
private void run(){}
}
//編譯通過,但這不是重寫,隻是倆個類中分别有自己的私有方法
public class Student extends Person{
private void run(){}
}
4)重寫的文法
1.方法名必須相同
2.參數清單必須相同
3.通路控制修飾符可以被擴大,但是不能被縮小
public protected default private
4.抛出異常類型的範圍可以被縮小,但是不能被擴大
ClassNotFoundException ---> Exception
5.傳回類型可以相同,也可以不同,如果不同的話,子類重寫後的方法傳回類型必須是父類方法傳回類型的子類型
例如:父類方法的傳回類型是Person,子類重寫後的傳回類可以是Person也可以是Person的子類型
注:一般情況下,重寫的方法會和父類中的方法的聲明完全保持一緻,隻有方法的實作不同。(也就是大括号中代碼不一樣)
例如:
public class Person{
public void run(){}
protected Object test()throws Exception{
return null;
}
}
//編譯通過,子類繼承父類,重寫了run和test方法.
public class Student extends Person{
public void run(){}
public String test(){
return "";
}
}
複制
5)為什麼要重寫
子類繼承父類,繼承了父類中的方法,但是父類中的方法并不一定能滿足子類中的功能需要,是以子類中需要把方法進行重寫。
6)總結:
方法重寫的時候,必須存在繼承關系。
方法重寫的時候,方法名和形式參數 必須跟父類是一緻的。
方法重寫的時候,子類的權限修飾符必須要大于或者等于父類的權限修飾符。( private < protected < public,friendly < public )
方法重寫的時候,子類的傳回值類型必須小于或者等于父類的傳回值類型。( 子類 < 父類 ) 資料類型沒有明确的上下級關系
方法重寫的時候,子類的異常類型要小于或者等于父類的異常類型。
六、多态
允許不同類的對象對同一消息做出響應。即同一消息可以根據發送對象的不同而采用多種不同的行為方式。
相同類域的不同對象,調用相同的方法,執行結果是不同的
1)一個對象的實際類型是确定的
例如: new Student(); new Person();等
2)可以指向對象的引用的類型有很多
一個對象的實作類型雖然是确定的,但是這個對象所屬的類型可能有很多種。
例如: Student繼承了Person類
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
複制
因為Person和Object都是Student的父類型
注:一個對象的實際類型是确定,但是可以指向這個對象的引用的類型,卻是可以是這對象實際類型的任意父類型。
3)一個父類引用可以指向它的任何一個子類對象
例如:
Object o = new AnyClass();
Person p = null;
p = new Student();
p = new Teacher();
p = new Person();
複制
4)多态中的方法調用
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
}
//調用到的run方法,是Student從Person繼承過來的run方法
main:
Person p = new Student();
p.run();
複制
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
public void run(){
//重寫run方法
}
}
//調用到的run方法,是Student中重寫的run方法
main:
Person p = new Student();
p.run();
複制
注:子類繼承父類,調用a方法,如果a方法在子類中沒有重寫,那麼就是調用的是子類繼承父類的a方法,如果重寫了,那麼調用的就是重寫之後的方法。
5)子類中獨有方法的調用
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
public void test(){
}
}
main:
Person p = new Student();
//調用到繼承的run方法
p.run();
//編譯報錯,因為編譯器檢查變量p的類型是Person,但是在Person類中并沒有發現test方法,是以編譯報錯.
p.test();
複制
注:一個變量x,調用一個方法test,編譯器是否能讓其編譯通過,主要是看聲明變量x的類型中有沒有定義test方法,如果有則編譯通過,如果沒有則編譯報錯.而不是看x所指向的對象中有沒有test方法.
原理:編譯看左邊,運作不一定看右邊。
編譯看左邊的意思:java 編譯器在編譯的時候會檢測引用類型中含有指定的成員,如果沒有就會報錯。子類的成員是特有的,父類的沒有的,是以他是找不到的。
6)子類引用和父類引用指向對象的差別
Student s = new Student();
Person p = new Student();
變量s能調用的方法是Student中有的方法(包括繼承過來的),變量p能調用的方法是Person中有的方法(包括繼承過來的)。
但是變量p是父類型的,p不僅可以指向Student對象,還可以指向Teacher類型對象等,但是變量s隻能指向Studnet類型對象,及Student子類型對象。變量p能指向對象的範圍是比變量s大的。
Object類型的變量o,能指向所有對象,它的範圍最大,但是使用變量o能調用到的方法也是最少的,隻能調用到Object中的聲明的方法,因為變量o聲明的類型就是Object.
注:java中的方法調用,是運作時動态和對象綁定的,不到運作的時候,是不知道到底哪個方法被調用的。
7)重寫、重載和多态的關系
重載是編譯時多态
調用重載的方法,在編譯期間就要确定調用的方法是誰,如果不能确定則編譯報錯
重寫是運作時多态
調用重寫的方法,在運作期間才能确定這個方法到底是哪個對象中的。這個取決于調用方法的引用,在運作期間所指向的對象是誰,這個引用指向哪個對象那麼調用的就是哪個對象中的方法。(java中的方法調用,是運作時動态和對象綁定的)
8)多态的注意事項
多态情況下,父類 和 子類存在同名的成員變量,無論是靜态的還是非靜态的變量,預設通路的是父類中的成員變量。
多态情況下,父類 和 子類存在同名的非靜态方法,通路的是子類的非靜态方法。
多态情況下,父類 和子類存在同名的靜态方法,通路的是父類的靜态方法。
多态情況下,不能通路子類特有的屬性、方法。
多态滿足的條件:必須要有繼承關系。
多态情況下,子類 和 父類如果存在同名的成員,通路的都是父類,除了同名的非靜态變量通路的才是子類。
9)多态存在的條件
1)有繼承關系
2)子類重寫父類方法
3)父類引用指向子類對象
補充一下第二點,既然多态存在必須要有“子類重寫父類方法”這一條件,那麼以下三種類型的方法是沒有辦法表現出多态特性的(因為不能被重寫):
1)static方法,因為被static修飾的方法是屬于類的,而不是屬于執行個體的
2)final方法,因為被final修飾的方法無法被子類重寫
3)private方法和protected方法,前者是因為被private修飾的方法對子類不可見,後者是因為盡管被protected修飾的方法可以被子類見到,也可以被子類重寫,但是它是無法被外部所引用的,一個不能被外部引用的方法,怎麼能談多态呢
七、instanceof和類型轉換
7.1、instanceof
public class Person{
public void run(){}
}
public class Student extends Person{
}
public class Teacher extends Person{
}
複制
例如:
main:
Object o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false
---------------------------
Person o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//編譯報錯
System.out.println(o instanceof String);
---------------------------
Student o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
//編譯報錯
System.out.println(o instanceof Teacher);
//編譯報錯
System.out.println(o instanceof String);
複制
注1:
System.out.println(x instanceof Y);
該代碼能否編譯通過,主要是看聲明變量x的類型和Y是否存在子父類的關系.有"子父類關"系就編譯通過,沒有子父類關系就是編譯報錯.
之後學習到的接口類型和這個是有點差別的。
注2:
System.out.println(x instanceof Y);
輸出結果是true還是false,主要是看變量x所指向的對象實際類型是不是Y類型的"子類型".
例如:
main:
Object o = new Person();
System.out.println(o instanceof Student);//false
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false
複制
7.2、類型轉換
public class Person{
public void run(){}
}
public class Student extends Person{
public void go(){}
}
public class Teacher extends Person{
}
複制
1)為什麼要類型轉換
//編譯報錯,因為p聲明的類型Person中沒有go方法
Person p = new Student();
p.go();
//需要把變量p的類型進行轉換
Person p = new Student();
Student s = (Student)p;
s.go();
或者
//注意這種形式前面必須要倆個小括号
((Student)p).go();
2)類型轉換中的問題
//編譯通過 運作沒問題
Object o = new Student();
Person p = (Person)o;
複制
//編譯通過 運作沒問題
Object o = new Student();
Student s = (Student)o;
複制
//編譯通過,運作報錯
Object o = new Teacher();
Student s = (Student)o;
複制
即:
X x = (X)o;
運作是否報錯,主要是變量o所指向的對象實作類型,是不是X類型的子類型,如果不是則運作就會報錯。