JavaSE - [3] 面向對象之(2)

繼承性的理解

為描述和處理 個人信息,定義類Person:

public class Person {
    public String name;
    public int age;
    public Date date;
}

為描述和處理 學生信息,定義類Student:

public class Student {
    public String name;
    public int age;
    public Date date;
    public String school;
}

通過繼承,簡化Student類的定義:

public class Student extends Person{
    public String school;
}

為什么要有繼承?
? 多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,
那么多個類無需再定義這些屬性和行為,只要繼承那個類即可。
? 此處的多個類稱為 子類( 派生類),單獨的這個類稱為父類(基類
或超類)。可以理解為:“子類 is a 父類”
? 類繼承語法規則:
class Subclass extends SuperClass{ }
作用:
? 繼承的出現減少了代碼冗余,提高了代碼的復用性。
? 繼承的出現,更有利于功能的擴展。
? 繼承的出現讓類與類之間產生了關系,提供了多態的前提。
? 注意:不要僅為了獲取其他類中某個功能而去繼承
子類繼承了父類,就繼承了父類的方法和屬性。
? 在子類中,可以使用父類中定義的方法和屬性,也可以創建新的數據和
方法。
? 在Java 中,繼承的關鍵字用的是“extends”,即子類不是父類的子集,
而是對父類的“擴展” 。
關于繼承的規則:
? 的 子類不能直接訪問父類中私有的(private)

繼承性的使用

子類繼承了父類,就繼承了父類的方法和屬性。
? 在子類中,可以使用父類中定義的方法和屬性,也可以創建新的數據和
方法。
? 在Java 中,繼承的關鍵字用的是“extends”,即子類不是父類的子集,
而是對父類的“擴展” 。
關于繼承的規則:
? 的 子類不能直接訪問父類中私有的(private)

繼承性的再說明

Java只能單繼承,不可以多繼承

方法的重寫(override/overwrite)

定義:在子類中可以根據需要對從父類中繼承來的方法進行改造,也稱
為方法的重置、覆蓋。在程序執行時,子類的方法將覆蓋父類的方法。
? 要求:

  1. 子類重寫的方法必須和父類被重寫的方法具有相同的方法名稱、參數列表
  2. 子類重寫的方法的返回值類型不能大于父類被重寫的方法的返回值類型
  3. 子類重寫的方法使用的訪問權限不能小于父類被重寫的方法的訪問權限
    ?子類不能重寫父類中聲明為private權限的方法
  4. 子類方法拋出的異常不能大于父類被重寫方法的異常
    ? 注意:
    子類與父類中同名同參數的方法必須同時聲明為非static的(即為重寫),或者同時聲明為
    static的(不是重寫)。因為static方法是屬于類的,子類無法覆蓋父類的方法。
關鍵字 — super

在Java類中使用super來調用父類中的指定操作:
?super可用于訪問父類中定義的屬性
?super可用于調用父類中定義的成員方法
?super可用于在子類構造器中調用父類的構造器
?注意:
?尤其當子父類出現同名成員時,可以用super表明調用的是父類中的成員
?super的追溯不僅限于直接父類
?super和this的用法相像,this代表本類對象的引用,super代表父類的內存
空間的標識
調用父類的構造 器
?子類中所有的構造器 默認都會訪問父類中 空參數的構造器
? 當父類中沒有空參數的構造器時,子類的構造器必須通過this(參 參
表 數列表)或者super( 參數列表)語句指定調用本類或者父類中相應的
構造器。同時,只能”二選一”,且必須放在構造器的首行
?如果子類構造器中既未顯式調用父類或本類的構造器,且父類中又
沒有無參的構造器,則編譯出錯


this和super的區別

子類對象的實例化過程
Object 類的理解

Object類是所有Java類的祖先。每個類都使用 Object 作為超類。所有對象(包括數組)都實現這個類的方法。
在不明確給出超類的情況下,Java會自動把Object作為要定義類的超類。
可以使用類型為Object的變量指向任意類型的對象。
Object類有一個默認構造方法pubilc Object(),在構造子類實例時,都會先調用這個默認構造方法。
Object類的變量只能用作各種值的通用持有者。要對他們進行任何專門的操作,都需要知道它們的原始類型并進行 @類型轉換 百度百科
例如:

Object obj = new MyObject();
MyObject x = (MyObject)obj;
多態性的使用

多態性,是面向對象中最重要的概念,在Java中的體現:
對象 的 多態性:父類的引用指向子類的對象
?可以直接應用在抽象類和接口上
?Java引用變量有兩個類型: 編譯時類型和 運行時類型。編譯時類型由聲明
該變量時使用的類型決定,運行時類型由實際賦給該變量的對象決定。簡
稱:編譯時,看左邊;運行時,看右邊。
? 若編譯時類型和運行時類型不一致 , 就出現了對象的多態性 (Polymorphism)
? 多態情況下 , “ 看左邊 ” : 看的是父類的引用(父類中不具備子類特有的方法)
“ 看右邊 ” : 看的是子類的對象(實際運行的是子類重寫父類的方法)
對象的多態 —在Java中,子類的對象可以替代父類的對象使用
?一個變量只能有一種確定的數據類型
?一個引用類型變量可能指向(引用)多種不同類型的對象

Person p = new Student();
Object o = new Person();//Object類型的變量o,指向Person類型的對象
o = new Student(); //Object類型的變量o,指向Student類型的對象

? 子類可看做是特殊的父類 , 所以父類類型的引用可以指向子類的對象:向上轉型(upcasting)
一個引用類型變量如果聲明為父類的類型,但實際引用的是子類
對象,那么該變量就 不能 再訪問子類中添加的屬性和方法

Student m = new Student();
m.school = "pku"; // 合法,Student 類有school 成員變量
Person e = new Student();
e.school = "pku"; // 非法,Person 類沒有school 成員變量

屬性是在編譯時確定的,編譯時e 為Person 類型,沒有school 成員變量,因而編譯錯誤。
虛擬方法調用(Virtual Method Invocation)
? 正常的方法調用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
? 虛擬方法調用( 多態情況下 下)
子 類中定義了與父類同名同參數的方法,在多態情況下,將此時父類的方法稱為虛擬方法,父類根據賦給它的不同子類對象,動態調用屬于子類的該方法。這樣的方法調用在編譯期是無法確定的。

Person e = new Student();
e.getInfo(); // 調用Student 類的getInfo() 方法

? 編譯時類型和運行時類型
時 編譯時e 為Person 類型,而方法的調用是在運行時確定的,所以調用的是Student類的getInfo() 方法。—— 動態綁定

虛擬 方法調用舉例
方法

前提:
Person類中定義了welcome()方法,各個
子類重寫了welcome()。
執行:
多態的情況下,調用對象的welcome()方法,
實際執行的是子類重寫的方法。

方法的重載與重寫
  1. 二者的定義細節:略
  2. 從編譯和運行的角度看:
    重載,是指允許存在多個同名方法,而這些方法的參數不同。編譯器根據方法不
    同的參數表,對同名方法的名稱做修飾。對于編譯器而言,這些同名方法就成了
    不同的方法。它們的調用地址在編譯期就綁定了。Java的重載是可以包括父類
    和子類的,即子類可以重載父類的同名不同參數的方法。
    所以:對于重載而言,在方法調用之前,編譯器就已經確定了所要調用的方法,
    這稱為“早綁定”或“靜態綁定”;
    而對于多態,只有等到方法調用的那一刻,解釋運行器才會確定所要調用的具體
    方法,這稱為“晚綁定”或“動態綁定”。
    引用一句Bruce Eckel的話:“不要犯傻,如果它不是晚綁定,它就不是多態。”
多態作用:

? 提高了代碼的通用性,常稱作接口重用
? 前提 :
?需要存在繼承或者實現關系
?有方法的重寫
? 成員方法:
?編譯時:要查看引用變量所聲明的類中是否有所調用的方法。
?運行時:調用實際new的對象所屬的類中的重寫方法。
? 成員 變量:
?不具備多態性,只看引用變量所聲明的類。

instanceof 關鍵字的使用

x instanceof A :檢驗x 是否為類A 的對象,返回值為boolean 型。
? 要求x所屬的類與類A必須是子類和父類的關系,否則編譯錯誤。
? 如果x屬于類A的子類B,x instanceof A值也為true。

對象類型轉換

對象類型轉換 (Casting )
? 基本數據類型的Casting: :
? 自動類型轉換:小的數據類型可以自動轉換成大的數據類型
如long g=20; double d=12.0f
? 強制類型轉換:可以把大的數據類型強制轉換(casting)成小的數據類型
如 float f=(float)12.0; int a=(int)1200L
?對 對Java 對象的強制類型轉換稱為造型
? 從子類到父類的類型轉換可以自動進行
? 從父類到子類的類型轉換必須通過造型( 強制類型轉換) 實現
? 無繼承關系的引用類型間的轉換是非法的
? 在造型前可以使用instanceof 操作符測試一個對象的類型


對象類型轉換
向下轉型的幾個常見問題

1、父類引用指向子類對象,而子類引用不能指向父類對象。
2、把子類對象直接賦給父類引用叫upcasting向上轉型,向上轉型不用強制轉換嗎,如:
Father f1 = new Son();
3、把指向子類對象的父類引用賦給子類引用叫向下轉型(downcasting),要強制轉換,如:
f1 就是一個指向子類對象的父類引用。把f1賦給子類引用 s1 即 Son s1 = (Son)f1;
其中 f1 前面的(Son)必須加上,進行強制轉換。

子類繼承父類

?若子類重寫了父類方法,就意味著子類里定義的方法徹底覆蓋了父類里的
同名方法,系統將不可能把父類里的方法轉移到子類中。
?對于實例變量則不存在這樣的現象,即使子類里定義了與父類完全相同的
實例變量,這個實例變量依然不可能覆蓋父類中定義的實例變量

Object 類結構的剖析

Object類是所有Java類的根父類
? 如果在類的聲明中未使用extends關鍵字指明其父類,則默認父類
為java.lang.Object類


Object的主要結構
== 操作符與equals 方法

==:
?基本類型比較值:只要兩個變量的值相等,即為true。
int a=5; if(a==6){…}
?引用類型比較引用(是否指向同一個對象):只有指向同一個對象時,==才
返回true。
Person p1=new Person();
Person p2=new Person();
if (p1==p2){…}
?用“==”進行比較時,符號兩邊的 數據類型必須兼容(可自動轉換的基本
數據類型除外),否則編譯出錯
equals() :所有類都繼承了Object, ,了 也就獲得了equals() 方法 。 還可以重寫 。
? 只能比較引用類型 , 其作用與 “==” 相同, 比較是否指向同一個對象 。
? 格式:obj1.equals(obj2)
? 特例用 :當用equals() 方法進行比較時 ,類 對類File 、String 、Date 及包裝類
( (Wrapper Class) ) 來說 , 是比較類型及內容而不考慮引用的是否是同一個對
象 ;
? 原因:在這些類中重寫了Object 類的equals() 方法 。
? 當自定義使用equals()時 時 , 可以重寫 。 用于比較兩個對象的 “ 內容 ” 是否都
相等

重寫 equals()

? 對稱性:如果x.equals(y)返回是“true”,那么y.equals(x)也應該返回是
“true”。
? 自反性:x.equals(x)必須返回是“true”。
? 傳遞性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,
那么z.equals(x)也應該返回是“true”。
? 一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你
重復x.equals(y)多少次,返回都是“true”。
? 任何情況下,x.equals(null),永遠返回是“false”;
x.equals(和x不同類型的對象)永遠返回是“false”。

總結==與 equals()

面試題:== 和equals的區別
1.== 既可以比較基本類型也可以比較引用類型。對于基本類型就是比較值,對于引用類型
就是比較內存地址
2.equals 的話,它是屬于java.lang.Object 類里面的方法,如果該方法沒有被重寫過默認也是 ==; 我們可以到 看到String類 等類的 的equals 方法是被重寫過的,而且String 類在日常開發中
了 用的比較多,久而久之,形成了equals 是比較值的錯誤觀點。
3.具體 要看自定義類里有沒有 重寫Object 的equals 方法來 判斷。
4.寫 通常情況下,重寫equals.會比較類中的相應屬性是否都相等

toString()的使用

toString() 方法在Object 類中定義 ,是 其返回值是String 類型 , 返回類名和它
的引用地址 。
? 在行 進行String 與其它類型數據的連接操作時 ,用 自動調用toString() 方法
Date now=new Date();
System.out.println(“now=”+now); 相當于
System.out.println(“now=”+now.toString());
? 可以根據需要在用戶自定義類型中重寫toString() 方法
如 如String 類重寫了toString() 方法 , 返回字符串的值 。
s1=“hello”;
System.out.println(s1);// 相當于System.out.println(s1.toString());
? 基本類型數據轉換為String 類型時 ,的 調用了對應包裝類的toString() 方法
?int a=10; System.out.println(“a=”+a);
面試題

public class Test1 {
    public static void main(String[] args) {
        char[] arr = new char[] { 'a', 'b', 'c' };
        System.out.println(arr);//
        int[] arr1 = new int[] { 1, 2, 3 };
        System.out.println(arr1);//
        double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
        System.out.println(arr2);//
    }
}

打印結果

abc
[I@15db9742
[D@6d06d69c
包裝類的理解

? 針對八種基本數據類型定義相應的引用類型—包裝類(封裝類)
? 有了類的特點,就可以調用類中的方法,Java才是真正的面向對象


包裝類
基本數據類型轉換為包裝類

基本 數據類型包裝成包裝類的實例 --- 裝箱
? 通過包裝類的構造器實現:
int i = 500; Integer t = new Integer(i);
? 還可以通過字符串參數構造包裝類對象:
Float f = new Float(“4.56”);
Long l = new Long(“asdf”); //NumberFormatException
? 獲得 包裝類對象中包裝的基本類型變量 --- 拆箱
? 調用包裝類的.xxxValue()方法:
boolean b = bObj.booleanValue();
? JDK1.5之后,支持自動裝箱,自動拆箱。但類型必須匹配。


包裝類
基本數據類型包裝類與 String 的相互轉換

字符串轉換成基本數據類型
? 通過包裝類的構造器實現:
int i = new Integer(“12”);
? 通過包裝類的parseXxx(String s)靜態方法:
Float f = Float.parseFloat(“12.1”);
? 基本數據類型轉換成字符串
? 調用字符串重載的valueOf()方法:
String fstr = String.valueOf(2.34f);
? 更直接的方式:
String intStr = 5 + “”


相互轉換
包裝類常見面試題 o1 和 o2 一樣嗎
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);//
Object o2;
if (true)
    o2 = new Integer(1);
else
    o2 = new Double(2.0);
System.out.println(o2);//
static 關鍵字的引入

當我們編寫一個類時,其實就是在描述其對象的屬性和行為,而并沒有產生實質上的對象,只有通過new關鍵字才會產生出對象,這時系統才會分配內存空間給對象,其方法才可以供外部調用。我們有時候希望無論是否產生了對象或無論產生了多少對象的情況下,某些特定的數據在內存空間里只有一份,例如所有的國人都有個國家名稱,每一個中國人都共享這個國家名稱,不必在每一個中國人實例對象中都單獨分配一個用于代表國家名稱的變量。

例子

class Circle{
private double radius;
public Circle(double radius){this.radius=radius;}
public double findArea(){return Math.PIradiusradius;}}
?創建兩個Circle對象
?Circle c1=new Circle(2.0); //c1.radius=2.0
?Circle c2=new Circle(3.0); //c2.radius=3.0
?Circle類中的變量radius是一個實例變量(instance variable),它屬于類的
每一個對象,不能被同一個類的不同對象所共享。
?上例中c1的radius獨立于c2的radius,存儲在不同的空間。c1中的radius
變化不會影響c2的radius,反之亦然。
如果想讓一個類的所有實例共享數據,就用類變量!
類屬性、類方法的設計思想
? 類屬性作為該類各個對象之間共享的變量。 在設計類時, 分析 哪
些屬性 不因對象的不同而改變 ,將這些屬性設置為類屬性。相應
的方法設置為類方法。
? 如果方法與調用者無關,則這樣的方法通常被聲明為類方法,由
于 不需要創建對象就可以調用類方法 ,從而簡化了方法 的 調用。

靜態變量與實例變量的對比

使用范圍:
?在Java類中,可用static修飾屬性、方法、代碼塊、內部類
? 被修飾后的成員具備以下特點:
?隨著類的加載而加載
?優先于對象存在
?修飾的成員,被所有對象所共享
?訪問權限允許時,可不創建對象,直接被類調用
實例對象需要創建對象才可以引用,而靜態變量可以通過類名調用,也可使使用對象調用

類變量和實例變量的內存解析
類變量和實例變量的內存解析

類變量和實例變量的內存解析
static 修飾方法

沒有對象的實例時,可以用 類名. 方法名()的形式訪問由static修飾的類方法。
?在static 方法內部只能訪問類的 的static 修飾的屬性或方法, 不能訪問類的 非
static 的結構
因為不需要實例就可以訪問static 方法,因此static 方法內部不能有this 。(也 也
有 不能有super ? YES!)
? static 修飾的方法不能被重寫

設計模式與單例設計模式

詳細設計請找目錄中的單例設計模式@小豬童鞋簡書 目錄檢索

理解 main()方法的語法

? 由于Java虛擬機需要調用類的main()方法,所以該方法的訪問權限必須是
public,又因為Java虛擬機在執行main()方法時不必創建對象,所以該方法必須
是static的,該方法接收一個String類型的數組參數,該數組中保存執行Java命令
時傳遞給所運行的類的參數。
? 又因為main() 方法是靜態的,我們不能直接訪問該類中的非靜態成員,必須創
建該類的一個實例對象后,才能通過這個對象去訪問類中的非靜態成員,這種情
況,我們在之前的例子中多次碰到。
此處,Something類的文件名叫OtherThing.java

class Something {
    public static void main(String[] something_to_do) {
        System.out.println("Do something ...");
    }
}

上述程序是否可以正常編譯、運行?
可以的,但是如果是使用 public 修飾的類,文件名就必須和main的主類名一致

類中代碼塊結構的使用

代碼塊(或初始化塊)的作用:
? 對 對Java 類或對象進行 初始化
? 代碼塊(或初始化塊)的分類:
? 一個類中代碼塊若有修飾符,則只能被static修飾,稱為 靜態代碼塊
(static block),沒有使用static修飾的,為 非靜態代碼塊 。
? static 代碼塊化 通常用于初始化static 的屬性

class Person {
public static int total;
static {
total = 100;// 為total 賦初值
}
…… //其它屬性或方法聲明
}

? 靜態代碼塊:用static 修飾的代碼塊

  1. 可以有輸出語句。
  2. 可以對類的屬性、類的聲明進行初始化操作。
  3. 不可以對非靜態的屬性初始化。即:不可以調用非靜態的屬性和方法。
  4. 若有多個靜態的代碼塊,那么按照從上到下的順序依次執行。
  5. 靜態代碼塊的執行要先于非靜態代碼塊。
  6. 靜態代碼塊隨著類的加載而加載,且只執行一次。
    ? 非靜態代碼塊:沒有static 修飾的代碼塊
  7. 可以有輸出語句。
  8. 可以對類的屬性、類的聲明進行初始化操作。
  9. 除了調用非靜態的結構外,還可以調用靜態的變量或方法。
  10. 若有多個非靜態的代碼塊,那么按照從上到下的順序依次執行。
  11. 每次創建對象的時候,都會執行一次。且先于構造器執行。
屬性賦值的先后順序(完結篇)
屬性賦值的順序
final 修飾類和方法

在Java中聲明類、變量和方法時,可使用關鍵字final來修飾,表示“最終的”。
?final 標記的類不能被繼承 。提高安全性,提高程序的可讀性。
?String類、System類、StringBuffer類
?final 標記的方法不能被子類重寫。
?比如:Object類中的getClass()。
?final 標記的變量( 成員變量或局部變量) 即稱為常量 。名稱大寫,且只
能被賦值一次。
?final標記的成員變量必須在聲明時或在每個構造器中或代碼塊中顯式賦
值,然后才能使用。
?final double MY_PI = 3.14;
面試題

public int addOne(final int x) {
    return ++x;
    // return x + 1;  //修改
}
抽象類與抽象方法的使用

隨著繼承層次中一個個新子類的定義,類變得越來越具體,而父類則更一
般,更通用。類的設計應該保證父類和子類能夠共享特征。有時將一個父
類設計得非常抽象,以至于它沒有具體的實例,這樣的類叫做抽象類。
? 用abstract關鍵字來修飾一個類,這個類叫做抽象類。
? 用abstract來修飾一個方法,該方法叫做抽象方法。
?抽象方法:只有方法的聲明,沒有方法的實現。以分號結束:
比如:public abstract void talk();
? 含有抽象方法的類必須被聲明為抽象類。
? 抽象類不能被實例化。抽象類是用來被繼承的,抽象類的子類必須重
寫父類的抽象方法,并提供方法體。若沒有重寫全部的抽象方法,仍
為抽象類。
? 不能用abstract修飾變量、代碼塊、構造器;
? 不能用abstract修飾私有方法、靜態方法、final的方法、final的類。


抽象類
抽象的應用場景舉例

? 解決方案
Java 允許類設計者指定:超類聲明一個方法但不提供實現,該方法的實現由子類提供。
這樣的方法稱為 抽象方法 。有一個或更多抽象方法的類稱為 抽象類 。
?Vehicle是一個抽象類,有兩個抽象方法


實例
創建抽象類的匿名子類對象
public abstract class Test3 {
    public abstract void sayHello1();
    public abstract void sayHello2();
    public void sayHello3() {
        System.out.println("Test3.sayHello3()");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Test3 test3 = new Test3() {
            
            @Override
            public void sayHello2() {
                System.out.println("Test1.main(...).new Test3() {...}.sayHello2()");
            }
            
            @Override
            public void sayHello1() {
                System.out.println("Test1.main(...).new Test3() {...}.sayHello1()");
            }
        };
        test3.sayHello1();
        test3.sayHello2();
        test3.sayHello3();
    }
}

測試

Test1.main(...).new Test3() {...}.sayHello1()
Test1.main(...).new Test3() {...}.sayHello2()
Test3.sayHello3()
接口的理解

? 一方面,有時必須從幾個類中派生出一個子類,繼承它們所有的屬性和方
法。但是,Java不支持多重繼承。有了接口,就可以得到多重繼承的效果。
?另一方面,有時必須從幾個類中抽取出一些共同的行為特征,而它們之間又
沒有is-a的關系,僅僅是具有相同的行為特征而已。例如:鼠標、鍵盤、打
印機、掃描儀、攝像頭、充電器、MP3機、手機、數碼相機、移動硬盤等都
支持USB連接。
?接口就是規范,定義的是一組規則,體現了現實世界中“如果你是/要...則
必須能...”的思想。繼承是一個"是不是"的關系,而接口實現則是 "能不能"
的關系。
? 接口的本質是契約,標準,規范,就像我們的法律一樣。制定好后大家都
要遵守。


實例

實例

? 接口(interface)是抽象方法和常量值定義的集合。
? 接口的特點:
?用interface來定義。
?接口中的所有成員變量都默認是由public static final修飾的。
?接口中的所有抽象方法都默認是由public abstract修飾的。
?接口中沒有構造器。
?接口采用多繼承機制。

接口的定義與使用
public interface Runner {

    public static final int ID = 1;

    public abstract void start();

    public abstract void run();

    public abstract void stop();

    // 等價于
    /**
     * int ID = 1; 
     * void start(); 
     * public void run(); 
     * void stop();
     */
}

? 定義Java類的語法格式:先寫extends,后寫implements
? class SubClass extends SuperClass implements InterfaceA{ }
? 一個類可以實現多個接口,接口也可以繼承其它接口。
? 實現接口的類中必須提供接口中所有方法的具體實現內容,方可實
例化。否則,仍為抽象類。
? 接口的主要用途就是被實現類實現。(面向接口編程)
? 與繼承關系類似,接口與實現類之間存在多態性
? 接口和類是并列關系,或者可以理解為一種特殊的類。從本質上講,
接口是一種特殊的抽象類,這種抽象類中只包含常量和方法的定義
(JDK7.0及之前),而沒有變量和方法的實現。

接口的多實現與接口的繼承性

一個類可以實現多個無關的接口
與繼承關系類似,接口與實現類之間存在多態性

接口和抽象類之間的對比
接口和抽象類之間的對比
創建接口匿名實現類的對象
public class Test1 {
    public static void main(String[] args) {
        Runner runner = new Runner() {

            @Override
            public void stop() {
                System.out.println("Test1.main(...).new Runner() {...}.stop()");
            }

            @Override
            public void start() {

                System.out.println("Test1.main(...).new Runner() {...}.start()");
            }

            @Override
            public void run() {
                System.out.println("Test1.main(...).new Runner() {...}.run()");
            }
        };
        runner.start();
        runner.run();
        runner.stop();

    }
}

在開發中,常看到一個類不是去繼承一個已經實現好的類,而是要么繼承抽象類,要么實現接口。
面試題

interface A {
    int x = 0;
}

class B {
    int x = 1;
}

class C extends B implements A {
    public void pX() {
        System.out.println(x); //此處有錯 super.x 就沒錯了
    }

    public static void main(String[] args) {
        new C().pX();
    }
}

或者修改為

interface A {
    int y = 0;
}

class B {
    int x = 1;
}

class C extends B implements A {
    public void pX() {
        System.out.println(y);
    }

    public static void main(String[] args) {
        new C().pX();
    }
}
Java8 中接口的新特性

Java 8中,你可以為接口添加靜態方法和默認方法。從技術角度來說,這是完
全合法的,只是它看起來違反了接口作為一個抽象定義的理念。
靜態方法:使用 static 關鍵字修飾。可以通過接口直接調用靜態方法,并執行
其方法體。我們經常在相互一起使用的類中使用靜態方法。你可以在標準庫中
找到像Collection/Collections或者Path/Paths這樣成對的接口和類。
默認方法:默認方法使用 default 關鍵字修飾。可以通過實現類對象來調用。
我們在已有的接口中提供新方法的同時,還保持了與舊版本代碼的兼容性。
比如:java 8 API中對Collection、List、Comparator等接口提供了豐富的默認
方法。

接口中的默認方法

? 若一個接口中定義了一個默認方法,而另外一個接口中也定義了一個同名同
參數的方法(不管此方法是否是默認方法),在實現類同時實現了這兩個接
口時,會出現: 接口沖突。
? 解決辦法:實現類必須覆蓋接口中同名同參數的方法,來解決沖突。
? 若一個接口中定義了一個默認方法,而父類中也定義了一個同名同參數的非
抽象方法,則不會出現沖突問題。因為此時遵守: 類優先原則。接口中具有
相同名稱和參數的默認方法會被忽略。
沖突

interface A {
    int y = 0;
    static void help() {
        System.out.println("A.help()");
    }
}

interface D {
    int y = 0;
    static void help() {
        System.out.println("D.help()");
    }
}
class B {
    int x = 1;
}

class C extends B implements A,D{
    public void pX() {
        System.out.println(A.y);
    }

    public static void main(String[] args) {
        new C().pX();
        A.help();
    }
}
內部類的分類

?當一個事物的內部,還有一個部分需要一個完整的結構進行描述,而這個內
部的完整的結構又只為外部事物提供服務,那么整個內部的完整結構最好使
用內部類。
?在Java中,允許一個類的定義位于另一個類的內部,前者稱為內部類,后者
稱為外部類。
? Inner class一般用在定義它的類或語句塊之內,在外部引用它時必須給出完
整的名稱。
?Inner class的名字不能與包含它的外部類類名相同;
?: 分類: 成員 內部類(static成員內部類和非static成員內部類)
局部內部類(不談修飾符)、匿名內部類

成員內部類的特點

? 成員內部類作為 類 的 成員的角色:
? 和外部類不同,Inner class還可以聲明為private或protected;
? 可以調用外部類的結構
? Inner class 可以聲明為static的,但此時就不能再使用外層類的非static的成員
變量;
? 成員內部類 作為類的角色:
? 可以在內部定義屬性、方法、構造器等結構
? 可以聲明為abstract類 ,因此可以被其它的內部類繼承
? 可以聲明為final的
? 編譯以后生成OuterClass$InnerClass.class字節碼文件(也適用于局部內部類)
【注意】

  1. 非static的成員內部類中的成員不能聲明為static的,只有在外部類或static的成員
    內部類中才可聲明static成員。
  2. 外部類訪問成員內部類的成員,需要“內部類.成員”或“內部類對象.成員”的方式
  3. 成員內部類可以直接使用外部類的所有成員,包括私有的數據
  4. 當想要在外部類的靜態成員部分使用內部類時,可以考慮內部類聲明為靜態的
class Outer {
    private int s;

    public class Inner {
        public void mb() {
            s = 100;
            System.out.println("在內部類Inner中s=" + s);
        }
    }

    public void ma() {
        Inner i = new Inner();
        i.mb();
    }
}

public class InnerTest {
    public static void main(String args[]) {
        Outer o = new Outer();
        o.ma();
    }
}
如何實例化成員內部類
生聲明

實例化

class Outer {
    private int s;

    public class Inner {
        public void mb() {
            s = 100;
            System.out.println("在內部類Inner中s=" + s);
        }
    }

    public void ma() {
        Inner i = new Inner();
        i.mb();
    }
}
public class InnerTest {
    public static void main(String args[]) {
        Outer o = new Outer();
        o.ma();
    }
}

如何 使用局部內部類
? 只能在聲明它的方法或代碼塊中使用,而且是先聲明后使用。除此之外的任何地方都不能使用該類
? 但是它的對象可以通過外部方法的返回值返回使用,返回值類型只能是局部內部類的父類或父接口類型

局部內部類的特點

? 內部類仍然是一個獨立的類,在編譯之后內部類會被編譯成獨立的.class文件,但
是前面冠以外部類的類名和$符號,以及數字編號。
? 只能在聲明它的方法或代碼塊中使用,而且是先聲明后使用。除此之外的任何地方
都不能使用該類。
? 局部內部類可以使用外部類的成員,包括私有的。
? 局部內部類可以使用外部方法的局部變量,但是必須是final的。由局部內部類和局
部變量的聲明周期不同所致。
? 局部內部類和局部變量地位類似,不能使用public,protected,缺省,private
? 局部內部類不能使用static修飾,因此也不能包含靜態成員

匿名內部類

匿名內部類不能定義任何靜態成員、方法和類,只能創建匿名內部類的一
個實例。一個匿名內部類一定是在new的后面,用其隱含實現一個接口或
實現一個類。
? 格式:
new 父類構造器(實參列表)| 實現接口(){
// 匿名內部類的類體部分
}
? 匿名內部類的特點
? 匿名內部類必須繼承父類或實現接口
? 匿名內部類只能有一個對象
? 匿名內部類對象只能使用多態形式引用

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1 面向對象No6 面向對象 OO Object Oriented 編程時以對象為單元,封裝數據和邏輯,以此提...
    征程_Journey閱讀 1,183評論 0 2
  • 本文出自 Eddy Wiki ,轉載請注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 1,225評論 0 5
  • 什么是對象 1.對象的概念:對特定一個存在事物2.什么是面向對象:以自我為中心對待一個特定事物存在,需要自己描述出...
    joshul閱讀 723評論 0 1
  • java繼承 繼承的概念 繼承是java面向對象編程技術的一塊基石,因為它允許創建分等級層次的類。 繼承就是子類繼...
    863cda997e42閱讀 687評論 0 1
  • 面向對象筆記 一、 對象在內存中的存放方法以及被調用過程 class文件首先被加載到方法區中的class文件內容區...
    VictorBXv閱讀 484評論 0 2