4.面向對象上

以下是《瘋狂Java講義》中的一些知識,如有錯誤,煩請指正。

類和對象

定義類

[修飾符] class 類名 {….類體…..}
  • Java類名通常以大寫字母開頭,如果類名稱由多個單詞組成,則每個單詞的首字母均應為大寫。
  • 類的修飾符可以是public、final、abstract; 或省略這三個。
  • 一個類里可以包含三種最常見的成員:構造器、屬性、方法。注意如果沒有為一個類編寫構造器,該系統會自動為該類提供一個構造器;一旦程序員提供了一個構造器,系統將不再為該類提供構造器。

定義成員變量

[修飾符] 類型  成員變量名[=默認值];
  • 修飾符: 可以是public、protected、private、static、final,其中前三個只能出現其一,并可與后兩個組合修飾。
  • 可以是基本類型或者引用類型
  • 成員變量名:采用匈牙利標記法:在以Pascal標記法的變量前附加小寫序列說明該變量的類型。在Java我們一般使用匈牙利標記法,基本結構為scope_typeVariableName,它 使用1-3字符前綴來表示數據類型,3個字符的前綴必須小寫,前綴后面是由表意性強的一個單詞或多個單詞組成的名字,而且每個單詞的首寫字母大寫,其它字 母小寫,這樣保證了對變量名能夠進行正確的斷句。例如,定義一個整形變量,用來記錄文檔數量:intDocCount,其中int表明數據類型,后面為表 意的英文名,每個單詞首字母大寫。這樣,在一個變量名就可以反映出變量類型和變量所存儲的值的意義兩方面內容,這使得代碼語句可讀性強、更加容易理解。 byte、int、char、long、float、 double、boolean和short。

定義方法

[修飾符] 方法返回值類型 方法名(形參列表) {….方法體….}
  • 修飾符:同成員變量定義
  • 返回值類型:可以是基本類型或者引用類型,需要配合return。無返回值使用void
  • 方法名:方法的名字的第一個單詞應以小寫字母作為開頭,后面的單詞則用大寫字母開頭。 同變量命名規則。
  • static修飾符修飾的成員屬于類本身。類變量、類方法、靜態變量、靜態方法。靜態成員不能直接訪問非靜態成員。如果靜態方法中需要訪問普通方法,只能重新創建一個對象。作為調用者調用改方法。

定義構造器

[修飾符] 構造器名(形參列表) {……}
  • 修飾符:可以是public protected private
  • 構造器名必須和類名相同
  • 類的構造器如果定義了返回值類型或者加void,這個構造器將被作為方法處理。
  • 實際上,類的構造器是有返回值的,當使用new關鍵字來調用構造器時,構造器返回該類的實例。構造器的返回值是隱式的

創建對象

// 定義p變量的同時,為p變量賦值
// 引用型變量p里存放的僅僅是一個引用,它指向實際的對象
// 引用變量存放在棧內存中,實際對象存放在堆內存中
Person p = new Person();
// 多個引用變量指向同一個對象
Person p2=p;

如果堆內存里的對象沒有任何變量指向該對象,這個對象就變成了垃圾,Java的垃圾回收機制將回收該對象,釋放該對象所占的內存。即切斷引用變量和對象之間的關系,將引用變量賦值為null。

調用實例/方法

類.類變量|方法
實例.實例變量|方法

對象的this引用
this 關鍵字總是指向調用該方法的對象.

  • 構造器中引用該構造器執行初始化的對象
  • 在方法中引用調用該方法的對象

特別的,Java允許允許一個成員直接調用另一個成員,此時可以省略this前綴。一般來說,如果調用static修飾的成員時省略了主調(調用成員、方法的對象),默認使用該類作為主調;如果調用沒有用static修飾的成員時省略了主調,默認使用this作為主調.

如果普通方法中有個局部變量和成員變量同名,程序又需要在該方法中訪問被覆蓋的成員變量,則必須使用this前綴。

方法

Java中方法不能獨立存在,必須屬于一個類或對象。

方法的參數傳遞機制
值傳遞:將實際參數值的副本傳入方法內,參數本身不會受到任何影響。
特別注意:引用類型的參數傳遞仍然是值傳遞。

class DataWrap
{
    int a;
    int b;
}
public class ReferenceTransferTest
{
    public static void swap(DataWrap dw)
    {
        // 下面三行代碼實現dw的a、b兩個成員變量的值交換。
        // 定義一個臨時變量來保存dw對象的a成員變量的值
        int tmp = dw.a;
        // 把dw對象的b成員變量值賦給a成員變量
        dw.a = dw.b;
        // 把臨時變量tmp的值賦給dw對象的b成員變量
        dw.b = tmp;
        System.out.println("swap方法里,a成員變量的值是"
            + dw.a + ";b成員變量的值是" + dw.b);
        // 把dw直接賦為null,讓它不再指向任何有效地址。
        dw = null;
    }
    public static void main(String[] args)
    {
        DataWrap dw = new DataWrap();
        dw.a = 6;
        dw.b = 9;
        swap(dw);
        System.out.println("交換結束后,a成員變量的值是"
            + dw.a + ";b成員變量的值是" + dw.b);//9,6;9,6
    }
}

上面的程序中dw僅僅是一個引用變量,在main棧區會創建dw引用變量,之后再swap棧區會創建其副本,完成對堆內存中的對象的引用。在swap方法中操作堆內存中的DataWrap對象,因此最后在main方法中dw引用的DataWrap成員變量的值交換了。

形參個數可變的方法
如果在定義方法時,在最后一個參數的類型后增加三點…,則表明該形參接受多個參數值,多個參數值被當成數組傳入。長度可變的形參只能位于最后一個參數,并一個方法里只能有一個可變長度的參數。

public class Varargs
{
    // 定義了形參個數可變的方法
    public static void test(int a , String... books)
    {
        // books被當成數組處理
        for (String tmp : books)
        {
            System.out.println(tmp);
        }
        // 輸出整數變量a的值
        System.out.println(a);
    }
    public static void main(String[] args)
    {
        // 調用test方法
        test(5 , "瘋狂Java講義" , "輕量級Java EE企業應用實戰");
    }
}

也可以使用數組形參來定義方法

public static void test(int a , String[] books)

但是調用時必須傳入數組:

test(5 , new String[] {"瘋狂Java講義" , "輕量級Java EE企業應用實戰"});

遞歸方法
遞歸就是在方法中再次調用自己。遞歸一定要向已知方向遞歸.

方法重載
Java 允許在一個類里定義多個同名方法,只要形參列表不同即可.

成員變量和局部變量

  • 成員變量指的是在類范圍里定義的變量;局部變量指的是在一個方法內定義的變量。
  • 不管是成員變量還是局部變量都遵守相同的命名規則。
  • 局部變量可分為三種:形參、方法局部變量、代碼塊局部變量,除了形參外,其他局部變量都必須顯式地初始化。
  • 成員變量不用顯式初始化,只要定義了一個類屬性或實例屬性,系統會在類的準備階段或者創建該類實例時,進行默認初始化,成員變量默認初始化是復制規則與數組動態初始化時數組元素賦值規則完全相同。
  • Java允許局部變量和成員變量重名。這樣局部變量會覆蓋成員變量,可以通過this來調用實例的屬性.

成員變量初始化

  1. 當類被加載時,類成員就在內存中分配了一塊空間,并指定默認值。
  2. 當對象被創建時,實例成員就在內存中分配了內存空間,并指定初始值。
  3. 為實例變量賦值。
    實例變量與實例共存亡;類變量與類本身共存亡。
Person p1 = new Person();
Person p2 = new Person();
// 為實例變量賦值
p1.name = "張三";
p2.name = "孫悟空";
//為類變量賦值
p1.eyeNum = 2;
p2.eyeNum = 3;

局部變量的初始化
局部變量僅在方法內有效。它總是保存在所在方法的棧內存中。如果局部變量是基本類型的變量,直接把這個變量的值保存在該變量對應的內存中;如果是引用變量,則局部變量保存的是地址,通過該地址引用到該變量實際引用的對象或數組。棧內存中的變量無須系統垃圾回收,當方法執行完成時,局部變量便會自動銷毀。

變量使用
局部變量的作用范圍越小,它在內存中停留的時間就越短,程序運行性能就越好。

隱藏和封裝

訪問控制符

  • private 私有的。在同一個類里能被訪問。
  • default 默認的,不加任何訪問控制符。包訪問權限。
  • protected 受保護的。子類中也能訪問
  • public 公共的。在任何地方都可以訪問

基本原則:

  1. 絕大部分成員變量應該使用private修飾,只有static修飾的、類似全局變量的成員變量才能使用public修飾。工具方法也應該使用private修飾。
  2. 父類的大部分方法可能僅希望被其子類重寫,而不希望被外界直接調用,應該使用protected修飾。
  3. 希望暴露出來給其他類自由調用的方法應該使用public修飾。因此類的構造器通過使用public修飾,從而允許在其他地方創建該類的實例。

package
Java允許將一組功能相關的類放在一個package下。

包名應該全部是小寫字母,可以使用公司域名倒寫來作為包名。

javac -d . Hello.java

以上是在當前路徑下編譯改文件,結果保存在當前路徑新建的包文件夾中。-d用于設置編譯生成class文件的保存位置。

java lee.Hello

以上是執行時的命令。注意優先搜索CLASSPATH下的子路徑,然后按照與包層次對應的目錄結構查找class文件。

當需要導入兩個包包含同一個名稱的類時,不能使用import,需要使用類的全名

java.sql.Date d = new java.sql.Date();

import 引入包格式。分為兩種:

  • 非靜態導入,導入的是包下所有的類。如:import package.subpackage.*;
  • 靜態導入,導入的是類的靜態屬性。如:import static package.className.*;

java常用包

  • java.lang.*,系統自動導入該包下所有類
  • java.util.*, 工具類、集合框架
  • java.net.* ,
  • java.io.*,
  • java.text.*,
  • java.sql.*,
  • java.awt.*,
  • java.swing.*.

構造器

構造器是一個特殊的方法,用于在創建對象時執行初始化。注意:當系統執行構造器的執行體之前,系統已經創建了一個對象,只是這個對象還不能被外部程序訪問。

構造器重載
構造器的重載和方法的重載一樣,都是方法名相同,形參列表不相同。在構造器中可通過this來調用另外一個重載的構造器。

類的繼承

Java的繼承是單繼承,每個子類最多只有一個直接父類。
子類繼承父類的語法格式如下:修飾符 class subclass extends superclass {}
子類擴展了父類,將可以獲得父類的全部屬性和方法,但不能獲得父類構造器.

重寫父類方法
方法的重寫要遵循“兩同兩小一大”,指的是:方法名相同,形參列表相同。返回值類型更小或相同,拋出的異常更小或相同,訪問控制權限要更大。
如果父類方法是private的權限,子類無法重寫該方法。

重載與重寫?
重載是overload,重寫是override。前者是同一個類的多個同名方法之間,后者是發生在子類與父類同名方法之間。當然父類與子類也可能發生重載:子類進程父類方法,并定義了一個與父類方法函數名相同,參數列表不同的方法。

super
通過關鍵字super來調用父類的被覆蓋的實例方法或隱藏屬性。沒有被覆蓋的屬性可以直接調用。

調用父類構造器
子類構造器總會調用父類構造器。
如果子類構造器沒有顯式使用super調用父類構造器,子類構造器默認會調用父類無參數的構造器。
創建一個子類實例時,總會先調用最頂層父類的構造器。

多態

Java 引用變量有兩個類型:一個是編譯時的類型,一個是運行時的類型,編譯時的類型由聲明該變量時使用的類型決定,運行時的類型由實際賦給該變量的對象決定。如果編譯時類型和運行時的類型不一致,這就有可能出現所謂的多態。
兩個相同類型的引用變量,由于它們實際引用的對象的類型不同,當它們調用同名方式時,可能呈現出多種行為特征,這就是多態。

class BaseClass
{
    public int book = 6;
    public void base()
    {
        System.out.println("父類的普通方法");
    }
    public void test()
    {
        System.out.println("父類的被覆蓋的方法");
    }
}
public class SubClass extends BaseClass
{
    //重新定義一個book實例變量隱藏父類的book實例變量
    public String book = "輕量級Java EE企業應用實戰";
    public void test()
    {
        System.out.println("子類的覆蓋父類的方法");
    }
    public void sub()
    {
        System.out.println("子類的普通方法");
    }
    public static void main(String[] args)
    {
        // 下面編譯時類型和運行時類型完全一樣,因此不存在多態
        BaseClass bc = new BaseClass();
        // 輸出 6
        System.out.println(bc.book);
        // 下面兩次調用將執行BaseClass的方法
        bc.base();
        bc.test();
        // 下面編譯時類型和運行時類型完全一樣,因此不存在多態
        SubClass sc = new SubClass();
        // 輸出"輕量級Java EE企業應用實戰"
        System.out.println(sc.book);
        // 下面調用將執行從父類繼承到的base()方法
        sc.base();
        // 下面調用將執行從當前類的test()方法
        sc.test();
        // 下面編譯時類型和運行時類型不一樣,多態發生
        BaseClass ploymophicBc = new SubClass();
        // 輸出6 —— 表明訪問的是父類對象的實例變量
        System.out.println(ploymophicBc.book);
        // 下面調用將執行從父類繼承到的base()方法
        ploymophicBc.base();
        // 下面調用將執行從當前類的test()方法
        ploymophicBc.test();//子類覆蓋的父類方法
        // 因為ploymophicBc的編譯類型是BaseClass,
        // BaseClass類沒有提供sub方法,所以下面代碼編譯時會出現錯誤。
        // ploymophicBc.sub();
    }
}

第三個引用變量比較特殊,編譯時類型是BaseClass,運行時類型是SubClass。當調用引用變量的Test方法時,實際執行的是SubClass中覆蓋后的Test方法。
子類對象賦給父類引用變量,是向上轉型,系統自動完成。當調用引用變量的方法時總是表現出子類方法的行為特征。而不是父類。對于子類中新的方法因為編譯時為BaseClass就無法調用了,所以運行時無法調用。
實例變量不具備多態,輸出BaseClass類的實例變量。官方的話:引用變量訪問實例變量時,總是試圖訪問編譯時類型所定義的成員變量。

引用變量的類型轉換
強制類型轉換: 類型轉換運算符是小括號,語法如下(type)variable
注意:

  • 基本類型之間的轉換只能在數值類型之間進行。數值類型和布爾類型之間不能進行類型轉換
  • 引用類型之間的轉換只能在具有繼承關系的兩個類型之間進行。如果試圖將一個父類實例轉換成子類類型,則這個對象必須實際上是子類實例才行。否則會引起ClassCastException。可以使用instanceof判斷是否可以成功轉換。

instanceof
前一個操作通常是一個引用類型的變量,后一個操作通常是一個類(也可以是接口)。如果是返回true 否返回false。

public class InstanceofTest
{
    public static void main(String[] args)
    {
        // 聲明hello時使用Object類,則hello的編譯類型是Object,
        // Object是所有類的父類, 但hello變量的實際類型是String
        Object hello = "Hello";
        // String與Object類存在繼承關系,可以進行instanceof運算。返回true。
        System.out.println("字符串是否是Object類的實例:"
            + (hello instanceof Object));
        System.out.println("字符串是否是String類的實例:"
            + (hello instanceof String)); // 返回true。
        // Math與Object類存在繼承關系,可以進行instanceof運算。返回false。
        System.out.println("字符串是否是Math類的實例:"
            + (hello instanceof Math));
        // String實現了Comparable接口,所以返回true。
        System.out.println("字符串是否是Comparable接口的實例:"
            + (hello instanceof Comparable));
        String a = "Hello";
//      // String類與Math類沒有繼承關系,所以下面代碼編譯無法通過
//      System.out.println("字符串是否是Math類的實例:"
//          + (a instanceof Math));
    }
}

初始化塊

格式:[修飾符]{//可執行代碼}
修飾符只能是static。static修飾的初始化塊稱為靜態初始化塊。
系統總是先調用初始化塊,然后是構造器。初始化塊只在創建Java對象時隱式執行。

初始化塊是構造器的補充,是一段固定執行的代碼,不能接受參數。實際上初始化塊在使用javac命令編譯類之后,初始化塊會被還原到每個構造器中,且位于構造器所有代碼的前面。

順序:以此執行父類的初始化塊、構造器、初始化塊、構造器...

靜態初始化塊
靜態初始化塊將在類初始化階段執行靜態初始化塊,比普通初始化塊優先級更高。同樣也需要上溯父類的靜態初始化塊。

class Root
{
    static{
        System.out.println("Root的靜態初始化塊");
    }
    {
        System.out.println("Root的普通初始化塊");
    }
    public Root()
    {
        System.out.println("Root的無參數的構造器");
    }
}
class Mid extends Root
{
    static{
        System.out.println("Mid的靜態初始化塊");
    }
    {
        System.out.println("Mid的普通初始化塊");
    }
    public Mid()
    {
        System.out.println("Mid的無參數的構造器");
    }
    public Mid(String msg)
    {
        // 通過this調用同一類中重載的構造器
        this();
        System.out.println("Mid的帶參數構造器,其參數值:"
            + msg);
    }
}
class Leaf extends Mid
{
    static{
        System.out.println("Leaf的靜態初始化塊");
    }
    {
        System.out.println("Leaf的普通初始化塊");
    }
    public Leaf()
    {
        // 通過super調用父類中有一個字符串參數的構造器
        super("瘋狂Java講義");
        System.out.println("執行Leaf的構造器");
    }
}
public class Test
{
    public static void main(String[] args)
    {
        new Leaf();
        new Leaf();
    }
}

靜態初始化塊與靜態成員變量定義的執行順序與源程序中排列順序相同。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 第四章 面向對象 面向對象思想 面向對象思想的引入前面我們講過數組,當有多個數組都需要遍歷時,我們可以將遍歷的代碼...
    chongsheng閱讀 568評論 0 0
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 3,858評論 1 10
  • 123.繼承 一個類可以從另外一個類繼承方法,屬性和其他特征。當一個類繼承另外一個類時, 繼承類叫子類, 被繼承的...
    無灃閱讀 1,415評論 2 4
  • 樓下的A姐來我們屋小坐,拿了桔子,桔子裝在四方形的一個小紙筐里。 “給你們裝桔子皮的。”A姐說。 “自己疊的?” ...
    鉛筆芒種閱讀 565評論 0 1
  • 今天,我們的科學老師本來是想給我們看一段關于科學的視頻的,她發現我們一5班教室的電腦有問題,于是她就給我們看了一...
    嚴當當閱讀 243評論 0 0