1. 程式人生 > 其它 >自學JAVA第五天:面向物件

自學JAVA第五天:面向物件

面向過程&面向物件

面向過程的思維模式

面向過程的思維模式是簡單的線性思維,思考問題首先陷入第一步做什麼、第二步做什麼的細節中。這種思維模式適合處理簡單的事情

面向物件的思維模式

面向物件的思維模式說白了就是分類思維模式。思考問題首先會解決問題需要哪些分類,然後對這些分類進行單獨思考。最後,才對某個分類下的細節進行面向過程的思索。這樣就可以形成很好的協作分工。比如:設計師分了10個類,然後將10個類交給了10個人分別進行詳細設計和編碼。顯然,面向物件適合處理複雜的問題,適合處理需要多人協作的問題
對於描述複雜的事物,為了從巨集觀上把握、從整體上合理分析,我們需要使用面向物件的思路來分析整個系統。但是,具體到微觀操作,仍然需要面向過程的思路去處理

OOP詳解

1、什麼是面向物件

Java的程式語言是面向物件的,採用這種語言進行程式設計稱為面向物件程式設計
(Object-OrientedProgramming, OOP)。
面向物件程式設計的本質就是:以類的方式組織程式碼,以物件的組織(封裝)資料。
抽象(abstract)

忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面。抽象並不打算了
解全部問題,而只是選擇其中的一部分,暫時不用關注細節。
例如:要設計一個學生成績管理系統,那麼對於學生,只關心他的班級、學號、成績等,而不用去關心他
的身高、體重這些資訊。 抽象是什麼?就是將多個物體共同點歸納出來,就是抽出像的部分!

封裝(Encapsulation)

  • 封裝是面向物件的特徵之一,是物件和類概念的主要特性。封裝是把過程和資料包圍起來,對資料的訪問只能通過指定的方式。
  • 在定義一個物件的特性的時候,有必要決定這些特性的可見性,即哪些特性對外部是可見的,哪些特性用於表示內部狀態。
  • 通常,應禁止直接訪問一個物件中資料的實際表示,而應通過操作介面來訪問,這稱為資訊隱藏。
  • 資訊隱藏是使用者對封裝性的認識,封裝則為資訊隱藏提供支援。
  • 封裝保證了模組具有較好的獨立性,使得程式維護修改較為容易。對應用程式的修改僅限於類的內部,因而可以將應用程式修改帶來的影響減少到最低限度。
    繼承(inheritance)
1. 繼承是一種聯結類的層次模型,並且允許和支援類的重用,它提供了一種明確表述共性的方法。
2. 新類繼承了原始類後,新類就繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為
新類的基類(父類)。
3. 派生類(子類)可以從它的基類(父類)那裡繼承方法和例項變數,並且派生類(子類)中可以修改或增
加新的方法使之更適合特殊的需要繼承性很好的解決了軟體的可重用性問題。比如說,所有的Windows應
用程式都有一個視窗,它們可以看作都是從一個視窗類派生出來的。但是有的應用程式用於文書處理,有
的應用程式用於繪圖,這是由於派生出了不同的子類,各個子類添加了不同的特性。

多型(polymorphism)

1.多型性是指允許不同類的物件對同一訊息作出響應。
2.多型性語言具有靈活、抽象、行為共享、程式碼共享的優勢,很好的解決了應用程式函式同名問題。

相同類域的不同物件,呼叫相同方法,表現出不同的結果
從認識論角度考慮是先有物件後有類。物件,是具體的事物。類,是抽象的,是對物件的抽象。
從程式碼執行角度考慮是先有類後有物件。類是物件的模板。

2、類與物件的關係

類是一種抽象的資料型別,它是對某一類事物整體描述/定義,但是並不能代表某一個具體的事物.

例如:我們生活中所說的詞語:動物、植物、手機、電腦等等。這些也都是抽象的概念,而不是指的某一
個具體的東西。

例如: Person類、Pet類、Car類等,這些類都是用來描述/定義某一類具體的事物應該具備的特點和行為
物件是抽象概念的具體例項

例如:張三就是人的一個具體例項,張三家裡的旺財就是狗的一個具體例項。能夠體現出特點,展現出功
能的是具體的例項,而不是一個抽象的概念.

【示例】

Student s = new Student(1L,"tom",20);
s.study();
Car c = new Car(1,"BWM",500000);
c.run();

物件s就是Student類的一個例項,物件c就是Car類的一個具體例項,能夠使用的是具體例項,而不是類。類只是給物件的建立提供了一個參考的模板而已.
但是在java中,沒有類就沒有物件,然而類又是根據具體的功能需求,進行實際的分析,最終抽象出來的.

3、物件和引用的關係

引用 "指向" 物件
使用類型別、陣列型別、介面型別宣告出的變數,都可以指向物件,這種變數就是引用型別變數,簡稱引用。
在程式中,創建出物件後,直接使用並不方便,所以一般會用一個引用型別的變數去接收這個物件,這個就是所說的引用指向物件.

方法回顧及加深

方法一定是定義在類中的,屬於類的成員。

1、方法的定義

格式: 修飾符 返回型別 方法名(引數列表)異常丟擲型別{...}
  1. 修飾符
public、static、abstract、final等等都是修飾符,一個方法可以有多個修飾符。例如程式入口
main方法,就使用了public static這個倆個修飾符
注:如果一個方法或者屬性有多個修飾符,這多個修飾符是沒有先後順序的
  1. 返回型別
方法執行完如果有要返回的資料,那麼就要宣告返回資料的型別,如果沒有返回的資料,那麼返回型別就必
須寫void.
只有構造方法(構造器)不寫任何返回型別也不寫void

【示例】

public String sayHello(){
return "hello";
}
public int max(int a,int b){
return a>b?a:b;
}
public void print(String msg){
System.out.println(msg);
}
  1. 方法名
遵守java中標示符的命名規則即可
  1. 引數列表
根據需求定義,方法可以是無參的,也可以有一個引數,也可以有多個引數
  1. 異常丟擲型別
如果方法中的程式碼在執行過程中,可能會出現一些異常情況,那麼就可以在方法上把這些異常宣告並丟擲,
也可以同時宣告丟擲多個異常,使用逗號隔開即可

【示例】

public void readFile(String file)throws IOException{
}
public void readFile(String file)throws IOException,ClassNotFoundException{
}

2、方法呼叫

在類中定義了方法,這個方法中的程式碼並不會執行,當這個方法被呼叫的時候,方法中的程式碼才會被一行一行順序執行。

  1. 非靜態方法
沒有使用static修飾符修飾的方法,就是非靜態方法.
呼叫這種方法的時候,是"一定"要使用物件的。因為非靜態方法是屬於物件的。(非靜態屬性也是一樣的)

【例子】

public class Student{
public void say(){}
}
public static void main(String[] args) {
        Student s = new Student();
	s.say();
    }
  1. 靜態方法
使用static修飾符修飾的方法,就是靜態方法.
呼叫這種方法的時候,"可以"使用物件呼叫,也"可以"使用類來呼叫,但是推薦使用類進行呼叫,因為靜態
方法是屬於類的。(靜態屬性也是一樣的)

【例子】

public class Student{
public static void say(){}
}
 public static void main(String[] args) {
        Student.say();
    }
  1. 類中方法之間的呼叫
    假設同一個類中有倆個方法,a方法和b方法,a和b都是非靜態方法,相互之間可以直接呼叫
public void a(){
b();
}
public void b(){
}

a和b都是靜態方法,相互之間可以直接呼叫

public static void a(){
b();
}
public static void b(){
}

a靜態方法,b是非靜態方法,a方法中不能直接呼叫b方法,但是b方法中可以直接呼叫a方法. 靜態方法不能呼叫非靜態方法
另外:在同一個類中,靜態方法內不能直接訪問到類中的非靜態屬性
總結:類中方法中的呼叫,兩個方法都是靜態或者非靜態都可以互相呼叫,當一個方法是靜態,一個方法是非靜態的時候,非靜態方法可以呼叫靜態方法,反之不能

3、呼叫方法時的傳參

  1. 形參和實參

【例子】

// a = x;
public void test(int a){
//..
}
 public static void main(String[] args) {
int x = 1;
t.test(x);
    }

引數列表中的a是方法test的形參(形式上的引數)
呼叫方法時的x是方法test的實參(實際上的引數)
注意:形參的名字和實參的名字都只是一個變數的名字,是可以隨便寫的,我們並不關心這個名字,而是關
心變數的型別以及變數接收的值

  1. 值傳遞和引用傳遞

呼叫方法進行傳參時,分為值傳遞和引用傳遞兩種。
如果引數的型別是基本資料型別,那麼就是值傳遞。
如果引數的型別是引用資料型別,那麼就是引用傳遞。
值傳遞是實參把自己變數本身存的簡單數值賦值給形參.
引用傳遞是實參把自己變數本身存的物件記憶體地址值賦值給形參.
所以值傳遞和引用傳遞本質上是一回事,只不過傳遞的東西的意義不同而已.
【示例:值傳遞】

public class Test{
public static void changeNum(int a){
a = 10;
}
public static void main(String[] args){
int a = 1;
System.out.println("before: a = "+a); //1
changeNum(a);
System.out.println("after: a = "+a); //1
}
}

【示例:引用傳遞】

public class Demo03 {
public static void changeName(Student s){
s.name = "tom";
}
public static void main(String[] args){
Student s = new Student();
System.out.println("before: name = "+s.name); //null
changeName(s);
System.out.println("after: name = "+s.name); //tom
}
}
class Student{
String name;
}

4、this關鍵字

在類中,可以使用this關鍵字表示一些特殊的作用
1、this在類中的作用
【區別成員變數和區域性變數】

public class Student{
private String name;
public void setName(String name){
//this.name表示類中的屬性name
this.name = name;
}
}

【呼叫類中的其他方法】

public class Student{
private String name;
public void setName(String name){
this.name = name;
}
public void print(){
//表示呼叫當前類中的setName方法
this.setName("tom");
}
}

注:預設情況下,setName("tom")和this.setName("tom")的效果是一樣的
【呼叫類中的其他構造器】

public class Student{
private String name;
public Student(){
//呼叫一個引數的構造器,並且引數的型別是String
this("tom");
}
public Student(String name){
this.name = name;
}
}

注:this的這種用法,只能在構造器中使用.普通的方法是不能用的.並且這句呼叫的程式碼只能出現在構造器中的第一句.
【示例】

public class Student{
private String name;
//編譯報錯,因為this("tom")不是構造器中的第一句程式碼.
public Student(){
System.out.println("hello");
this("tom");
}
public Student(String name){
this.name = name;
}
}

2、this關鍵字在類中的意義
this在類中表示當前類將來創建出的物件

【例子】

public class Student{
private String name;
public Student(){
System.out.println("this = "+this);
}
public static void main(String[] args){
Student s = new Student();
System.out.println("s = "+s);
}
}

執行後看結果可知,this和s列印的結果是一樣的,那麼其實也就是變數s是從物件的外部執行物件,而this是在物件的內部執行物件本身.
這樣也就能理解為什麼this.name代表的是成員變數,this.setName("tom")代表的是呼叫成員方法,因為這倆句程式碼從本質上講,和在物件外部使用變數s來呼叫是一樣的,s.name和s.setName("tom")。
【this和s打印出來的記憶體地址是一樣的,使用==比較的結果為true。】

public class Student{
public Student getStudent(){
return this;
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = s1.getStudent();
System.out.println(s1 == s2);//true
}
}

【 類中的this是和s1相等還是和s2相等呢?】

public class Student{
private String name;
public void test(){
System.out.println(this);
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.test();
s2.test();
}
}

注:這句話是要這麼來描述的,s1物件中的this和s1相等,s2物件中的this和s2相等,因為類是模板,模板中寫的this並不是只有一個,每個物件中都有一個屬於自己的this,就是每個物件中都一個屬於自己的name屬性一樣.

建立與初始化物件

使用new關鍵字建立物件
使用new關鍵字建立的時候,除了分配記憶體空間之外,還會給 建立好的物件 進行預設的初始化 以 及對類中構造器的呼叫

那麼對main方法中的以下程式碼:
Student s = new Student();
1)為物件分配記憶體空間,將物件的例項變數自動初始化預設值為0/false/null。(例項變數的隱式賦
值)
2)如果程式碼中例項變數有顯式賦值,那麼就將之前的預設值覆蓋掉。(之後可以通過例子看到這個現象)
例如:顯式賦值
private String name = "tom";
3)呼叫構造器
4)把物件記憶體地址值賦值給變數。(=號賦值操作)

構造器

類中的構造器也稱為構造方法,是在進行建立物件的時候必須要呼叫的。並且構造器有以下倆個特點:

  1. 必須和類的名字相同
  2. 必須沒有返回型別,也不能寫void

構造器的作用:

  1. 使用new建立物件的時候必須使用類的構造器
  2. 構造器中的程式碼執行後,可以給物件中的屬性初始化賦值
    【演示】
public class Student{
private String name;
public Student(){
name = "tom";
}
}

構造器過載
除了無參構造器之外,很多時候我們還會使用有參構造器,在建立物件時候可以給屬性賦值.

public class Student{
private String name;
public Student(){
name = "tom";
}
public Student(String name){
this.name = name;
}
}

構造器之間的呼叫
使用this關鍵字,在一個構造器中可以呼叫另一個構造器的程式碼
注意:this的這種用法不會產生新的物件,只是呼叫了構造器中的程式碼而已.一般情況下只有使用new關鍵
字才會建立新物件
【演示】

public class Student{
private String name;
public Student(){
this();
}
public Student(String name){
this.name = name;
}
}

預設構造器
在java中,即使我們在編寫類的時候沒有寫構造器,那麼在編譯之後也會自動的新增一個無參構造器,這個無參構造器也被稱為預設的構造器
【示例】

public class Student{
}
main:
//編譯通過,因為有無參構造器
Student s = new Student(); 

但是,如果我們手動的編寫了一個構造器,那麼編譯後就不會新增任何構造器了
【示例】

public class Student{
private String name;
public Student(String name){
this.name = name;
}
}
main:
//編譯報錯,因為沒有無參構造器
Student s = new Student();

記憶體分析

JAVA程式執行的記憶體分析
棧 stack:

  1. 每個執行緒私有,不能實現執行緒間的共享!
  2. 區域性變數放置於棧中
  3. 棧是由系統自動分配,速度快!棧是一個連續的記憶體空間

堆 heap:

  1. 放置new出來的物件
  2. 堆是一個不連續的記憶體空間,分配靈活,速度慢

方法區(也是堆)

  1. 被所有執行緒共享
  2. 用來存放程式中永遠是不變或唯一的內容。(類程式碼資訊、靜態變數、字串常量)

    引用型別的概念
  3. java中,除了基本資料型別之外的其他型別稱之為引用型別
  4. java中的物件是通過引用來操作的
    屬性(field,或者叫成員變數)
  5. 屬性用於定義該類或該類物件包含的資料或者說靜態屬性
  6. 屬性作用範圍是整個類體
  7. 屬性的預設初始化:
    在定義成員變數時可以對其初始化,如果不對其初始化,Java使用預設的值對其初始化。(數值:0,0.0char:u0000, boolean:false, 所有引用型別:null)
  8. 屬性定義格式:
[修飾符] 屬性型別 屬性名 = [預設值]

類的方法
方法是類和物件動態行為特徵的抽象。方法很類似於面向過程中的函式。面向過程中,函式是最基本單位,整個程式有一個個函式呼叫組成;面向物件中,整個程式的基本單位是類,方法是從屬於類或物件的
方法定義格式:

[修飾符] 方法返回值型別 方法名(形參列表) {
// n條語句
}

java物件的建立和使用

  • 必須使用 new 關鍵字建立物件。
Person person= new Person ();
  • 使用物件(引用) . 成員變數來引用物件的成員變數
person.age
  • 使用物件(引用) . 方法(引數列表)來呼叫物件的方法
person.setAge(23)

類中就是://靜態的資料 //動態的行為

封裝

我們程式設計要追求“高內聚,低耦合”。高內聚就是類的內部資料操作細節自己完成,不允許外部干涉;低耦合:僅暴露少量的方法給外部使用。
封裝(資料的隱藏)

在定義一個物件的特性的時候,有必要決定這些特性的可見性,即哪些特性對外部是可見的,哪些特性用於表示內部狀態。
通常,應禁止直接訪問一個物件中資料的實際表示,而應通過操作介面來訪問,這稱為資訊隱藏。

1、封裝的步驟

  1. 使用private 修飾需要封裝的成員變數。
  2. 提供一個公開的方法設定或者訪問私有的屬性
    設定 通過set方法,命名格式: set屬性名(); 屬性的首字母要大寫
    訪問 通過get方法,命名格式: get屬性名(); 屬性的首字母要大寫
    【演示】
//物件能在類的外部"直接"訪問
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());
}
}

2、作用和意義

  1. 提高程式的安全性,保護資料。
  2. 隱藏程式碼的實現細節
  3. 統一使用者的呼叫介面
  4. 提高系統的可維護性
  5. 便於呼叫者呼叫

良好的封裝,便於修改內部程式碼,提高可維護性

3、方法過載

類中有多個方法,有著相同的方法名,但是方法的引數各不相同,這種情況被稱為方法的過載。方法的過載可以提供方法呼叫的靈活性。
【演示:檢視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){}
  1. 方法的返回值可以不同,也可以相同。
    在java中,判斷一個類中的倆個方法是否相同,主要參考倆個方面:方法名字和引數列表

繼承

繼承的本質是對某一批類的抽象,從而實現對現實世界更好的建模
為什麼需要繼承?繼承的作用?
第一好處:繼承的本質在於抽象。類是對物件的抽象,繼承是對某一批類的抽象
第二好處:為了提高程式碼的複用性
【注】JAVA中類只有單繼承,沒有多繼承! 介面可以多繼承!

1、繼承

  1. 繼承是類和類之間的一種關係。除此之外,類和類之間的關係還有依賴、組合、聚合等
  2. 繼承關係的倆個類,一個為子類(派生類),一個為父類(基類)。子類繼承父類,使用關鍵字extends來表示。
public class student extends Person{
}
  1. 子類和父類之間,從意義上講應該具有"is a"的關係.
student is a person
dog is a animal
  1. 類和類之間的繼承是單繼承
一個子類只能"直接"繼承一個父類,就像是一個人只能有一個親生父親
一個父類可以被多子類繼承,就像一個父親可以有多個孩子

注:java中介面和介面之間,有可以繼承,並且是多繼承。
  1. 父類中的屬性和方法可以被子類繼承
    子類中繼承了父類中的屬性和方法後,在子類中能不能直接使用這些屬性和方法,是和這些屬性和方法原有的修飾符(public protected default private)相關的。
    例如 :
    父類中的屬性和方法使用public修飾,在子類中繼承後"可以直接"使用
    父類中的屬性和方法使用private修飾,在子類中繼承後"不可以直接"使用
    父類中的構造器是不能被子類繼承的,但是子類的構造器中,會隱式的呼叫父類中的無參構造器(預設使用super關鍵字)

2、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方法等

3、Super關鍵字

子類繼承父類之後,在子類中可以使用this來表示訪問或呼叫子類中的屬性或方法,使用super就表示訪問或呼叫父類中的屬性和方法

  1. super的使用
    【訪問父類中的屬性】
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);
}
}

【呼叫父類中的方法】

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();
}
}

【呼叫父類中的構造器】

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;
}
}

【super使用的注意的地方】

  1. 用super呼叫父類構造方法,必須是構造方法中的第一個語句。
  2. super只能出現在子類的方法或者構造方法中。
  3. super 和 this 不能夠同時呼叫構造方法。(因為this也是在構造方法的第一個語句)
    【super 和 this 的區別】
  4. 代表的事物不一樣:
    this:代表所屬方法的呼叫者物件
    super: 代表父類物件的引用空間。
  5. 使用前提不一致:
    this:在非繼承的條件下也可以使用。
    super:只能在繼承的條件下才能使用
  6. 呼叫構造方法
    this:呼叫本類的構造方法
    super:呼叫的父類的構造方法

4、方法重寫

方法的重寫(override)

  1. 方法重寫只存在於子類和父類(包括直接父類和間接父類)之間。在同一個類中方法只能被過載,不能被重寫
  2. 靜態方法不能重寫
  • 父類的靜態方法不能被子類重寫為非靜態方法 //編譯出錯
  • 父類的非靜態方法不能被子類重寫為靜態方法;//編譯出錯
  • 子類可以定義與父類的靜態方法同名的靜態方法(但是這個不是覆蓋)
    【例子】
A類繼承B類 A和B中都一個相同的靜態方法test
B a = new A();
a.test();//呼叫到的是B類中的靜態方法test
A a = new A();
a.test();//呼叫到的是A類中的靜態方法test
可以看出靜態方法的呼叫只和變數宣告的型別相關
這個和非靜態方法的重寫之後的效果完全不同

私有方法不能被子類重寫,子類繼承父類後,是不能直接訪問父類中的私有方法的,那麼就更談不上重寫了。
【例子】

public class Person{
private void run(){}
}
//編譯通過,但這不是重寫,只是倆個類中分別有自己的私有方法
public class Student extends Person{
private void run(){}
}

重寫的語法

  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 "";
}
}

為什麼要重寫?
子類繼承父類,繼承了父類中的方法,但是父類中的方法並不一定能滿足子類中的功能需要,所以子類中需要把方法進行重寫。
總結:

  1. 方法重寫的時候,必須存在繼承關係
  2. 方法重寫的時候,方法名和形式引數 必須跟父類是一致的
  3. 方法重寫的時候,子類的許可權修飾符必須要大於或者等於父類的許可權修飾符。( private < protected <public,friendly < public )
  4. 方法重寫的時候,子類的返回值型別必須小於或者等於父類的返回值型別。( 子類 < 父類 ) 資料型別沒有明確的上下級關係
  5. 方法重寫的時候,子類的異常型別要小於或者等於父類的異常型別

多型

1、認識多型

多型性是OOP中的一個重要特性,主要是用來實現動態聯編的,換句話說,就是程式的最終狀態只有在執行過程中才被決定而非在編譯期間就決定了。這對於大型系統來說能提高系統的靈活性和擴充套件性。
多型可以讓我們不用關心某個物件到底是什麼具體型別,就可以使用該物件的某些方法,從而實現更加
靈活的程式設計,提高系統的可擴充套件性。
允許不同類的物件對同一訊息做出響應。即同一訊息可以根據傳送物件的不同而採用多種不同的行為方式
相同類域的不同物件,呼叫相同的方法,執行結果是不同的

  1. 一個物件的實際型別是確定的
例如: new Student(); new Person();等
  1. 可以指向物件的引用的型別有很多
    一個物件的實現型別雖然是確定的,但是這個物件所屬的型別可能有很多種。
    例如: Student繼承了Person類
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();

因為Person和Object都是Student的父型別
注:一個物件的實際型別是確定,但是可以指向這個物件的引用的型別,卻是可以是這物件實際型別的任意父型別。

  1. 一個父類引用可以指向它的任何一個子類物件
    例如:
Object o = new AnyClass();
Person p = null;
p = new Student();
p = new Teacher();
p = new Person();
  1. 多型中的方法呼叫
public class Person{
public void run(){}
}
public class Student extends Person{
}
  1. 呼叫到的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方法,如果重寫了,那麼呼叫的就是重寫之後的方法。
子類中獨有方法的呼叫

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 編譯器在編譯的時候會檢測引用型別中含有指定的成員,如果沒有就會報錯。
子類的成員是特有的,父類的沒有的,所以他是找不到的。

子類引用和父類引用指向物件的區別

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中的方法呼叫,是執行時動態和物件繫結的,不到執行的時候,是不知道到底哪個方法被呼叫的。

2、重寫、過載和多型的關係

過載是編譯時多型

呼叫過載的方法,在編譯期間就要確定呼叫的方法是誰,如果不能確定則編譯報錯

重寫是執行時多型

呼叫重寫的方法,在執行期間才能確定這個方法到底是哪個物件中的。這個取決於呼叫方法的引用,在執行
期間所指向的物件是誰,這個引用指向哪個物件那麼呼叫的就是哪個物件中的方法。(java中的方法呼叫,
是執行時動態和物件繫結的)

3、多型的注意事項

  1. 多型是方法的多型,屬性沒有多型性
  2. 編寫程式時,如果想呼叫執行時型別的方法,只能進行型別轉換。不然通不過編譯器的檢查。但是如果兩個沒有關聯的類進行強制轉換,會報:ClassCastException。 比如:本來是狗,我把它轉成貓。就會報這個異常
  3. 多型的存在要有3個必要條件:要有繼承,要有方法重寫,父類引用指向子類物件

4、多型存在的條件

  1. 有繼承關係
  2. 子類重寫父類方法
  3. 父類引用指向子類物件
    補充一下第二點,既然多型存在必須要有“子類重寫父類方法”這一條件,那麼以下三種類型的方法是沒有辦法表現出多型特性的(因為不能被重寫):
  4. static方法,因為被static修飾的方法是屬於類的,而不是屬於例項的
  5. final方法,因為被final修飾的方法無法被子類重寫
  6. private方法和protected方法,前者是因為被private修飾的方法對子類不可見,後者是因為儘管被protected修飾的方法可以被子類見到,也可以被子類重寫,但是它是無法被外部所引用的,一個不能被外部引用的方法,怎麼能談多型呢

5、方法繫結(method binding)

執行呼叫方法時,系統根據相關資訊,能夠執行記憶體地址中代表該方法的程式碼。分為靜態繫結和動態繫結。
靜態繫結:
在編譯期完成,可以提高程式碼執行速度。
動態繫結:
通過物件呼叫的方法,採用動態繫結機制。這雖然讓我們程式設計靈活,但是降低了程式碼的執行速度。這也是JAVA比C/C++速度慢的主要因素之一。JAVA中除了final類、final方、static方法,所有方法都是JVM在執行期才進行動態繫結的。
多型:如果編譯時型別和執行時型別不一致,就會造成多型。
6、instanceof和型別轉換
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

2. 型別轉換

public class Person{
public void run(){}
}
public class Student extends Person{
public void go(){}
}
public class Teacher extends Person{
}

【為什麼要型別轉換】

//編譯報錯,因為p宣告的型別Person中沒有go方法
Person p = new Student();
p.go();
//需要把變數p的型別進行轉換
Person p = new Student();
Student s = (Student)p;
s.go();
或者
//注意這種形式前面必須要倆個小括號
((Student)p).go();

【型別轉換中的問題】

//編譯通過 執行沒問題
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型別的子型別,如果不是則執行就會報錯。

【總結】
1、父類引用可以指向子類物件,子類引用不能指向父類物件。
2、把子類物件直接賦給父類引用叫upcasting向上轉型,向上轉型不用強制轉型。
如Father father = new Son();
3、把指向子類物件的父類引用賦給子類引用叫向下轉型(downcasting),要強制轉型。
如father就是一個指向子類物件的父類引用,把father賦給子類引用son 即Son son =(Son)father;其中father前面的(Son)必須新增,進行強制轉換。
4、upcasting 會丟失子類特有的方法,但是子類overriding 父類的方法,子類方法有效
5、向上轉型的作用,減少重複程式碼,父類為引數,調有時用子類作為引數,就是利用了向上轉型。這樣
使程式碼變得簡潔。體現了JAVA的抽象程式設計思想。

修飾符

1、static修飾符

1、static變數

在類中,使用static修飾的成員變數,就是靜態變數,反之為非靜態變數。
靜態變數和非靜態變數的區別
靜態變數屬於類的,"可以"使用類名來訪問,非靜態變數是屬於物件的,"必須"使用物件來訪問.

public class Student{
private static int age;
private double score;
public static void main(String[] args) {
Student s = new Student();
//推薦使用類名訪問靜態成員
System.out.println(Student.age);
System.out.println(s.age);
System.out.println(s.score);
}
}

靜態變數對於類而言在記憶體中只有一個,能被類的所有例項所共享。例項變數對於類的每個例項都有一份,它們之間互不影響.

public class Student{
private static int count;
private int num;
public Student() {
count++;
num++;
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
Student s4 = new Student();
//因為還是在類中,所以可以直接訪問私有屬性
System.out.println(s1.num);
System.out.println(s2.num);
System.out.println(s3.num);
System.out.println(s4.num);
System.out.println(Student.count);
System.out.println(s1.count);
System.out.println(s2.count);
System.out.println(s3.count);
System.out.println(s4.count);
}
}

在載入類的過程中為靜態變數分配記憶體,例項變數在建立物件時分配記憶體,所以靜態變數可以使用類名來直接訪問,而不需要使用物件來訪問.

2、static方法

在類中,使用static修飾的成員方法,就是靜態方法,反之為非靜態方法。
靜態方法和非靜態方法的區別

靜態方法數屬於類的,"可以"使用類名來呼叫,非靜態方法是屬於物件的,"必須"使用物件來呼叫.

靜態方法"不可以"直接訪問類中的非靜態變數和非靜態方法,但是"可以"直接訪問類中的靜態變數和靜態方法
注意:this和super在類中屬於非靜態的變數.(靜態方法中不能使用)

public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public static void test(){
//編譯通過
System.out.println(count);
go();
//編譯報錯
System.out.println(num);
run();
}
}

非靜態方法"可以"直接訪問類中的非靜態變數和非靜態方法,也"可以"直接訪問類中的靜態變數和靜態方法

public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public void test(){
//編譯通過
System.out.println(count);
go();
//編譯通過
System.out.println(num);
run();
}
}

父類的靜態方法可以被子類繼承,但是不能被子類重寫

public class Person {
public static void method() {}
}
//編譯報錯
public class Student extends Person {
public void method(){}
}
例如:
public class Person {
public static void test() {
System.out.println("Person");
}
}
//編譯通過,但不是重寫
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
main:
Perosn p = new Student();
p.test();//輸出Person
p = new Person();
p.test();//輸出Perosn
和非靜態方法重寫後的效果不一樣

父類的非靜態方法不能被子類重寫為靜態方法 ;

public class Person {
public void test() {
System.out.println("Person");
}
}
//編譯報錯
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}

3、程式碼塊和靜態程式碼塊

【類中可以編寫程式碼塊和靜態程式碼塊】

public class Person {
{
//程式碼塊(匿名程式碼塊)
}
static{
//靜態程式碼塊
}
}

【匿名程式碼塊和靜態程式碼塊的執行】
因為沒有名字,在程式並不能主動呼叫這些程式碼塊
匿名程式碼塊是在建立物件的時候自動執行的,並且在構造器執行之前。同時匿名程式碼塊在每次建立物件的時候都會自動執行
靜態程式碼塊是在類載入完成之後就自動執行,並且只執行一次.
注:每個類在第一次被使用的時候就會被載入,並且一般只會載入一次

public class Person {
{
System.out.println("匿名程式碼塊");
}
static{
System.out.println("靜態程式碼塊");
}
public Person(){
System.out.println("構造器");
}
}


【匿名程式碼塊和靜態程式碼塊的作用】
匿名程式碼塊的作用是給物件的成員變數初始化賦值,但是因為構造器也能完成這項工作,所以匿名程式碼塊使用的並不多。
靜態程式碼塊的作用是給類中的靜態成員變數初始化賦值。

public class Person {
public static String name;
static{
name = "tom";
}
public Person(){
name = "zs";
}
}

main:
System.out.println(Person.name);//tom

注:在構造器中給靜態變數賦值,並不能保證能賦值成功,因為構造器是在建立物件的時候才指向,但是靜態變數可以不建立物件而直接使用類名來訪問.

4、建立和初始化物件的過程

Student s = new Student();

【Student類之前沒有進行類載入】

  1. 類載入,同時初始化類中靜態的屬性
  2. 執行靜態程式碼塊
  3. 分配記憶體空間,同時初始化非靜態的屬性(賦預設值,0/false/null)
  4. 呼叫Student的父類構造器
  5. 對Student中的屬性進行顯示賦值(如果有的話)
  6. 執行匿名程式碼塊
  7. 執行構造器
  8. 返回記憶體地址
public class Person{
private String name = "zs";
public Person() {
System.out.println("Person構造器");
print();
}
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
public class Student extends Person{
private String name = "tom";
{
System.out.println("Student匿名程式碼塊");
}
static{
System.out.println("Student靜態程式碼塊");
}
public Student(){
System.out.println("Student構造器");
}
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}


Student s = new Student();
Student類之前已經進行了類載入
1.分配記憶體空間,同時初始化非靜態的屬性(賦預設值,0/false/null)
2.呼叫Student的父類構造器
3.對Student中的屬性進行顯示賦值(如果有的話)
4.執行匿名程式碼塊
5.執行構造器
6.返回記憶體地址

5、靜態匯入

靜態導包就是java包的靜態匯入,用import static代替import靜態匯入包是JDK1.5中的新特性
意思是匯入這個類裡的靜態方法
好處:這種方法的好處就是可以簡化一些操作,例如列印操作System.out.println(…);就可以將其寫入一個靜態方法print(…),在使用時直接print(…)就可以了。但是這種方法建議在有很多重複呼叫的時候使用,如果僅有一到兩次呼叫,不如直接寫來的方便

import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
//之前是需要Math.random()呼叫的
System.out.println(random());
System.out.println(PI);
}
}

2、final修飾符

1、修飾類

用final修飾的類不能被繼承,沒有子類
例如:我們是無法寫一個類去繼承String類,然後對String型別擴充套件的,因為API中已經被String類定義為final的了
我們也可以定義final修飾的類:

public final class Action{
}
//編譯報錯
public class Go extends Action{
}

2、修飾方法

用final修飾的方法可以被繼承,但是不能被子類的重寫
例如:每個類都是Object類的子類,繼承了Object中的眾多方法,在子類中可以重寫toString方法、equals方法等,但是不能重寫getClass方法 wait方法等,因為這些方法都是使用fianl修飾的
我們也可以定義final修飾的方法:

public class Person{
public final void print(){}
}
//編譯報錯
public class Student extends Person{
public void print(){
}
}

3、修飾變數
用final修飾的變量表示常量,只能被賦一次值.其實使用final修飾的變數也就成了常量了,因為值不會再變了。
【修飾區域性變數】

public class Person{
public void print(final int a){
//編譯報錯,不能再次賦值,傳參的時候已經賦過了
a = 1;
}
}
public class Person{
public void print(){
final int a;
a = 1;
//編譯報錯,不能再次賦值
a = 2;
}
}

【修飾成員變數-非靜態成員變數】

public class Person{
private final int a;
}
只有一次機會,可以給此變數a賦值的位置:
宣告的同時賦值
匿名程式碼塊中賦值
構造器中賦值(類中出現的所有構造器都要寫)

【修飾成員變數-靜態成員變數】

public class Person{
private static final int a;
}
只有一次機會,可以給此變數a賦值的位置:
宣告的同時賦值
靜態程式碼塊中賦值

【修飾引用變數】

main:
final Student s = new Student();
//編譯通過
s.setName("tom");
s.setName("zs");
//編譯報錯,不能修改引用s指向的記憶體地址
s = new Student();

3、abstract修飾符

abstract修飾符可以用來修飾方法也可以修飾類,如果修飾方法,那麼該方法就是抽象方法;如果修飾類,那麼該類就是抽象類

1、抽象類和抽象方法的關係

抽象類中可以沒有抽象方法,但是有抽象方法的類一定要宣告為抽象類

2、語法

public abstract class Action{
public abstract void doSomething();
}
public void doSomething(){...}

對於這個普通方法來講:
"public void doSomething()"這部分是方法的宣告
"{...}"這部分是方法的實現,如果大括號中什麼都沒寫,就叫方法的空實現
宣告類的同時,加上abstract修飾符就是抽象類
宣告方法的時候,加上abstract修飾符,並且去掉方法的大口號,同時結尾加上分號,該方法就是抽象方法。

3、特點及作用

抽象類,不能使用new關鍵字來建立物件,它是用來讓子類繼承的
抽象方法,只有方法的宣告,沒有方法的實現,它是用來讓子類實現的
注:子類繼承抽象類後,需要實現抽象類中沒有實現的抽象方法,否則這個子類也要宣告為抽象類

public abstract class Action{
public abstract void doSomething();
}
main:
//編譯報錯,抽象類不能new物件
Action a = new Action();
//子類繼承抽象類
public class Eat extends Action{
//實現父類中沒有實現的抽象方法
public void doSomething(){
//code
}
}
main:
Action a = new Eat();
a.doSomething();

介面

1、介面的本質

普通類:只有具體實現
抽象類:具體實現和規範(抽象方法) 都有
介面:只有規範
【為什麼需要介面?介面和抽象類的區別?】

  • 介面就是比“抽象類”還“抽象”的“抽象類”,可以更加規範的對子類進行約束。全面地專業地實現了:規範和具體實現的分離。
  • 抽象類還提供某些具體實現,介面不提供任何實現,介面中所有方法都是抽象方法。介面是完全面向規範的,規定了一批類具有的公共方法規範
  • 從介面的實現者角度看,介面定義了可以向外部提供的服務
  • 從介面的呼叫者角度看,介面定義了實現者能提供那些服務
  • 介面是兩個模組之間通訊的標準,通訊的規範。如果能把你要設計的系統之間模組之間的介面定義好,就相當於完成了系統的設計大綱,剩下的就是添磚加瓦的具體實現了。大家在工作以後,做系統時往往就是使用“面向介面”的思想來設計系統。

2、介面與抽象類的區別

抽象類也是類,除了可以寫抽象方法以及不能直接new物件之外,其他的和普通類沒有什麼不一樣的。介面已經另一種型別了,和類是有本質的區別的,所以不能用類的標準去衡量介面。
宣告類的關鍵字是class,宣告介面的關鍵字是interface
抽象類是用來被繼承的,java中的類是單繼承
類A繼承了抽象類B,那麼類A的物件就屬於B型別了,可以使用多型一個父類的引用,可以指向這個父類的任意子類物件
注:繼承的關鍵字是extends
介面是用來被類實現的,java中的介面可以被多實現
類A實現介面B、C、D、E..,那麼類A的物件就屬於B、C、D、E等型別了,可以使用多型
一個介面的引用,可以指向這個介面的任意實現類物件
注:實現的關鍵字是implements

3、介面中的方法都是抽象方法

介面中可以不寫任何方法,但如果寫方法了,該方法必須是抽象方法

public interface Action{
public abstract void run();
//預設就是public abstract修飾的
void test();
public void go();
}

4、介面中的變數都是靜態常量(public static final修飾)

介面中可以不寫任何屬性,但如果寫屬性了,該屬性必須是public static final修飾的靜態常量。
注:可以直接使用介面名訪問其屬性。因為是public static修飾的
注:宣告的同時就必須賦值.(因為介面中不能編寫靜態程式碼塊)

public interface Action{
public static final String NAME = "tom";
//預設就是public static final修飾的
int AGE = 20;
}
main:
System.out.println(Action.NAME);
System.out.println(Action.AGE);

5、一個類可以實現多個介面

public class Student implements A,B,C,D{
//Student需要實現介面A B C D中所有的抽象方法
//否則Student類就要宣告為抽象類,因為有抽象方法沒實現
}
main:
A s1 = new Student();
B s2 = new Student();
C s3 = new Student();
D s4 = new Student();

注:
s1只能呼叫介面A中宣告的方法以及Object中的方法
s2只能呼叫介面B中宣告的方法以及Object中的方法
s3只能呼叫介面C中宣告的方法以及Object中的方法
s4只能呼叫介面D中宣告的方法以及Object中的方法
注:必要時可以型別強制轉換
例如 : 介面A 中有test() , 介面B 中有run()

A s1 = new Student();
s1.test();
B s2 = new Student();
s2.run();
if(s1 instanceof B){
((B)s1).run();
}

6、一個介面可以繼承多個父介面

public interface A{
public void testA();
}
public interface B{
public void testB();
}
//介面C把介面A B中的方法都繼承過來了
public interface C extends A,B{
public void testC();
}
//Student相當於實現了A B C三個介面,需要實現所有的抽象方法
//Student的物件也就同時屬於A型別 B型別 C型別
public class Student implements C{
public viod testA(){}
public viod testB(){}
public viod testC(){}
}
main:
C o = new Student();
System.out.println(o instanceof A);//true
System.out.println(o instanceof B);//true
System.out.println(o instanceof C);//true
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//編譯報錯
System.out.println(o instanceof String);

注:System.out.println(o instanceof X);

如果o是一個介面型別宣告的變數,那麼只要X不是一個final修飾的類,該程式碼就能通過編譯,至於其結果
是不是true,就要看變數o指向的物件的實際型別,是不是X的子類或者實現類了。

注:一個引用所指向的物件,是有可能實現任何一個介面的。(java中的多實現)

7、介面的作用

介面的最主要的作用是達到統一訪問,就是在建立物件的時候用介面建立
【介面名】 【物件名】= new 【實現介面的類】
這樣你像用哪個類的物件就可以new哪個物件了,不需要改原來的程式碼
假如我們兩個類中都有個function()的方法,如果我用介面,那樣我new a();就是用a的方法,new b()就是用b的方法
這個就叫統一訪問,因為你實現這個介面的類的方法名相同,但是實現內容不同
總結:
1、Java介面中的成員變數預設都是public,static,final型別的(都可省略),必須被顯示初始化,即介面中的成員變數為常量(大寫,單詞之間用"_"分隔)
2、Java介面中的方法預設都是public,abstract型別的(都可省略),沒有方法體,不能被例項化
3、Java介面中只能包含public,static,final型別的成員變數和public,abstract型別的成員方法
4、介面中沒有構造方法,不能被例項化
5、一個介面不能實現(implements)另一個介面,但它可以繼承多個其它的介面
6、Java介面必須通過類來實現它的抽象方法
7、當類實現了某個Java介面時,它必須實現介面中的所有抽象方法,否則這個類必須宣告為抽象類
8、不允許建立介面的例項(例項化),但允許定義介面型別的引用變數,該引用變數引用實現了這個介面的類的例項
9、 一個類只能繼承一個直接的父類,但可以實現多個介面,間接的實現了多繼承.