Java返璞歸真(一) - 基礎

面向對象

什麼是面向對象?

      面向對象就是當有需求時,找到這個可以解決你需求的東西(類)進行操作。

      例如週末放假,肚子餓了,下樓找一個快餐店(類),坐下來點個飯,點飯要找服務員(對象),這個菜服務員是無法做出來的,是需要廚師才能做出來的,服務員找到可以解決這個問題的人,即廚師(對象)。

      類與對象之間的概念無絕對的,是相對的,樹是一個類,杉樹也是一個類,因爲杉樹也有細分,同理杉樹也可以是一個對象,而這個對象是相對於樹這個類而言,具體是類還是對象,是視具體業務場景、理解而言。

 

什麼是面向對象編程?

     面向對象編程就是找到所要實現這個功能的類,找到了這個類後創建出一個合適的對象,通過對象來調用其所需的功能(行爲 / 方法)。

      如果沒有這個類,則需要自己創建出一個類,在通過對這個類做一些基礎的屬性(成員變量)定義,再去創建這個類的對象,賦予屬性(成員變量)對應的值,在拿着這個對象去調用所定義好的方法(函數)。

      項目就是由一個個類組成的,如同人的世界,由黃種人、白種人、黑種人等等組成的,而這些不同的人種有不同的屬性(成員變量),而具有絕對屬性(成員變量)值的人就是對象了。

 

面向對象的作用

      更加符合了人類的思想,需要什麼就去特定的地方找,而到了這個地方,對象需要什麼,則由對象調用者(大腦 / 需求)來決定,如果這個地方沒有能夠實現需求的,那麼久自己創造一個,當以後還需要用到時,只需要再次回到這個地方來使用即可。

 

面向對象與面向過程的區別

      面向對象:把數據和對數據的操作方法放在一起,作爲一個相互依存的整體,即對象;對同類對象對象抽象出其共性,形成類;類中的大多數數據,只能用本類的方法進行處理,且類通過一個簡單的外部接口與外界發生關係;對象與對象之間通過消息進行通信,而程序流程由用戶在使用中決定。

      面向過程:程序結構是按照功能劃分爲若干個基本模塊,這些模塊形成一個樹狀結構,各個模塊之間的關係儘可能的簡單,在功能上相對獨立,每一個模塊內部均是由順序、選擇、循環三種基本結構組成,其模塊實現的具體方法是使用子程序,程序的流程是在寫程序時就已經決定了,執行時是自頂而下順序執行,逐步求精。

 

類區分

      常見的有接口、普通類、內部類、抽象類、靜態內部類、枚舉類、註解類、泛型類。

 

接口

      修飾關鍵字:interface Test = abstract interface Test

      接口是用於頂層設計的一個特殊抽象類,該接口沒有構造器,而且接口的形參修飾符,默認是固定的public static final修飾的,方法默認是abstract修飾的,即抽象方法。

      接口之間是可以互相繼承,並且接口的繼承是多繼承的,但接口之間是不能互相實現,即只能用繼承來實現關聯關係。

      接口不可以繼承或任何一個類,但類可以實現接口多個接口。

      接口是沒有構造方法,不能用於實例化對象。

      實現接口的類,必須要實現該接口的所有方法,除非將該類置爲抽象類。

 

      JDK1.8:

      default:默認方法,可以帶方法體,並且可以在實現了中重寫,可以通過接口的引用調用。

      static:靜態方法,可以帶方法體,不可以在實現類中重寫,可以通過接口名調用。

      default和static不可以在同一接口方法中使用。

 

普通類

      修飾符:class Test

      有五大成員,屬性(成員變量)、構造器、方法、內部類、代碼塊。

      只能單繼承,但可以實現多個接口,並且可以直接創建對象。

      注:接口是提高程序的重用性、可擴展性、降低了耦合度,所以實現了多實現(繼承)。

/**

 * 爲什麼Java類無法多繼承

 * @author Levi

 */

public class A  {

         public void test(int i){

                  System.out.println(i);

         }

}

class B {

         public void test(int i){

                  System.out.println(i);

         }

}

/*

 * 多基礎的情況,根本無法知道調用的是A的還是B的,重寫時,也不知道是重寫那個的

 */

class C extends A,B{

}

 

內部類

如同人是一個,裏面還有一個器官類。

 

      可以被4中修飾符修飾,也可以被final、abstract、static修飾

      可以有屬性(成員變量)、方法,也可以調用外部類的屬性(成員變量)、方法。

      在運行時class文件的名是使用【外部類$內部類】命名的。

      可以繼承別的類,也可以實現接口,但是不能被別的類繼承。

      可以和其他外部類同名,但不可以與本外部類同名。

 

      創建內部類對象:

            外部類.內部類 對象名 = new 外部類.內部類();

            外部類.內部類 對象名 = 外部類.new 內部類();

 

      調用內部類的方法:

            new 外部類.內部類().方法;

 

      局部內部類:是調用方法的局部變量要使用final修飾(JDK1.8之前),在JDK1.8後就自動添加了;局部內部類創建對象只能通過外部的方法返回或者在外部方法中使用開始調用內部類的方法。

            外部類.this.屬性(成員變量)名 / 方法名,來調用或者區分。

 

抽象類

      修飾關鍵字:abstract class Test

      抽象類常用於中層設計,並不是一個具體類,所以是無法實例化,但抽象類是可以有構造器的。

      抽象類是可以被繼承和實現接口,繼承抽象類的子類,必須要實現抽象類的所有抽象方法,以達到抽象類的具體化,除非將子類也設置爲抽象類。

      抽象類的方法無法確定具體執行的功能,所以抽象方法時沒有方法體的,格式:public abstract void test();

 

靜態內部類

     修飾符:static class Test

      就是在內部類上加static修飾符,其特點是全局唯一,只加載一次,優先於非靜態,任何一次修改都是全局性的影響,在使用上並不依賴於實例對象,生命週期屬於類級別,從JVM加載開始到JVM結束。

 

      靜態內部類不需要有指向外部類的引用,但非靜態內部類需要持有對外部類的引用。

      靜態內部類不能訪問外部類的非靜態成員,只能訪問外部類的靜態成員,非靜態內部類能夠訪問外部類的靜態和非靜態成員。

      一個非靜態內部類不能脫離外部類實體被創建,一個非靜態內部類可以訪問外部類的數據和方法,因爲就在外部類的裏面。

      靜態內部類不用先創建外部類,可以把靜態內部類看做外部類的靜態變量,使用就不用外部類實例,而非靜態就需要先實例化。

 

枚舉類

      修飾符:enum Test

      枚舉類是一種特殊的Java類,適用於一些方法在運行時,它想要的數據不能使任意的,而且必須是一定範圍內的值,可以直接使用枚舉來解決。

枚舉類中聲明的每一個枚舉值代表枚舉類的一個實例對象,而且枚舉類也支持靜態導入,並且在JDK1.5後,擴展了switch同時也支持傳入一個枚舉類型。

     

其中enum關鍵字就類似class關鍵字,只不過class關鍵字默認是繼承Object,而enum定義的枚舉類默認繼承於java.lang.Enum抽象類。

      反編譯之後會發現其中每一個枚舉值都是枚舉類的具體實例對象,只不過是靜態常量java.lang.Enum類枚舉類的特性。

 

註解類

      修飾符:@interface Test

      註解類也是一個特殊的Java類,可以對類、屬性(成員變量)、方法、參數、包做標註,本身是沒有什麼具體功能的,是在運行時,可以根據這個標註做統一的處理。

      註解大體可以分爲三類,標記註解、一般註解、元註解,其本質就是繼承了java.lang.annotation.Annotation這個類,其具體實現類是Java運行時產生的動態代理,即在編譯器生成類文件時,標註可以被嵌入字節碼中,Java虛擬機可以保留標註的內容,在運行時可以獲取到標註的內容,從而根據業務做進一步的處理。

      在使用時,通過反射獲取註解時,返回的是Java運行時生成的動態代理對象$Proxy1,通過代理對象調用自定義註解(接口)的方法,會最終調用AnnotationInvocationHandler的invoke方法,該方法會從memberValues這個Map中檢索出對應的值,而memberValues的來源是Java常量池。

 

泛型類

      修飾符:interface/class Test<泛型>

      就是可以通過給容器加限定的形式規定只能存儲一種類型的對象,就像給容器貼上標籤說明該容器只能存儲什麼樣類型的對象,即只存儲整數值類型,那麼可以用Test<Integer>。

      在JDK5之前,對象保存到集合中,就會失去其特性,取出時,通常要手動進行類型的強制轉換,這樣就不可避免引發程序的一些安全性問題。

      也就是在JDK5之後,泛型允許程序在編寫代碼時,就限制容器要處理的類型,從而把原來程序運行時問題,轉變爲編譯時問題,以此提高程序的可讀性和穩定性,特別是大型的項目這一點更爲突出。

     

      泛型是提供給javac編譯器使用的,用於限定容器的輸入類型,讓編譯器在源代碼級別上,阻擋向容器中插入非法數據。

     

      擦除過程:編譯器編譯完帶有泛型的java程序後,生成的class文件中將不在帶有泛型信息,以此使程序允許效率不受影響,這個過程稱爲擦除。

      補償過程:在運行時,通過獲取元素的類型進行轉換動作,不用在代碼中在強制類型轉換。

 

      聲明好泛型類型後,只能存放特定的類型元素。

      泛型類必須是引用類型。

      使用泛型後取出元素不需要類型轉換。

 

      注: Java是面向對象的語音,而基本數據類型則不是對象,於是每一個接班類型都有對應的包裝類。

 

      Java類的五大成員有屬性(成員變量)、構造器、方法、內部類(局部內部類)、代碼塊。

      注:這五大成員被不同的關鍵字修飾會有不同的效果,如同穿上不同顏色的衣服會產生不同的效果。

      接下來一一說明。

 

屬性(成員變量)

      也稱爲實例變量,代表這個類的信息,而這個信息的表現可以通過方法(行爲),表現出來的形態取決於對象,而且屬性(成員變量)的類型也取決於這個類的用途。

 

構造器

      也稱爲構造器,是用來初始化屬性(成員變量)的,因爲有一些屬性(成員變量)在類一開始就需要進行賦值,如同人一出生就有了確定的血型。

 

方法(函數)

      就是一個類的表現形態(行爲),方法經常和屬性(成員變量)共同使用,方法也是一個功能的實現,而這個方法的具體產出結果,則是由對象的調用者來決定。

 

內部類

      同上。

 

代碼塊

      隨着類的構造器加載而加載的,用於屬性(成員變量)的初始化。

 

常見鍵字用途

static

      只要被static修飾都是放於內存中的靜態域,並且是隨着的加載而加載,隨着類的消失而消失,而且無論創建了多少個對象,始終只有一個,即被static修飾的,都是被所有類所共享的。

      JavaJVM解讀:https://blog.csdn.net/Su_Levi_Wei/article/details/80239654

     

      static可以修飾屬性(成員變量)、方法、代碼塊、內部類,調用只需要直接 類名.X(被static修飾的)。

         被static修飾的方法、代碼塊、內部類,只能調用static修飾的屬性(成員變量)、方法,而非static修飾的方法、代碼塊、內部類可以調用static修飾的。

/**

 * 加載順序

 * @author Levi

 */

public class StaticLoad {

         protected static void main(String[] args) {

                 System.out.println("====  開始 ====");

                 /*

                  * 結果:

                  * A 靜態代碼塊

                  * B 靜態代碼塊

                  * A 非靜態代碼塊

                  * A 構造器

                  * B 非靜態代碼塊

                  * B 構造器

                  *

                  * 解讀:

                  * 1、B先去尋找父級,尋找到A類,加載A

                  * 2、static代碼塊是隨着類的加載而加載的,加載A,自然也加載代碼塊

                  * 3、找到父類,加載完A後,B加載自己,自然也加載靜態代碼塊了

                  * 4、B加載自己構造器之前,要先去加載父級A的構造器,

                  *                  而非靜態代碼塊是隨着構造器的加載而加載的,自然A也就先加載代碼塊

                  * 5、A加載完代碼塊也就在加載自己的構造器

                  * 6、B在加載完父級A後,回到自身加載構造器,但是由於非靜態的代碼塊的特殊性,

                  *       那麼自然先加載代碼塊,加載完後,在加載自身的。

                  */

                 new B();

             }

}

class A{

    static {

        System.out.println("A 靜態代碼塊");

    }

          

    {

        System.out.println("A 非靜態代碼塊");

    }

          

    public A() {

        System.out.println("A 構造器");

    }

}

class B extends A{

     static {

            System.out.println("B 靜態代碼塊");

        }

            {

            System.out.println("B 非靜態代碼塊");

        }

        public B() {

            System.out.println("B 構造器");

        }   

}

 

final

      可以修飾類、屬性(成員變量)、方法、內部類,被final修飾的類、屬性(成員變量)、方法、內部類都代表的是最終。

 

      final修飾的類,不可以被繼承。

      final修飾的屬性(成員變量),屬性(成員變量)的值不能改,並且屬性(成員變量)名要大全部大寫。

      final修飾的方法,不可以被重寫。

      final修飾的內部類,不能被其他內部類基礎。

 

      final常常和static一起使用,static final表示的是全局。

     

      final也可以用於局部變量、成員變量。

 

abstract

      可以修飾類、方法。

 

      abstract修飾的類,會變成抽象類,可以有構造器(是類都有構造器),但是不可以創建對象。

      abstract修飾的類被繼承了,如果父類有抽象方法,子類要重寫,不重寫則子類要聲明爲abstract。

      abstract修飾的方法,那麼該類一定是抽象類,並且該方法是沒有代碼體的,即{}。

 

      abstract不可以和static、final一起使用。

      abstract修飾符只有public、protected、default,不可以和private共同使用。

     

權限修飾符

      public:公共,無視包環境。

      protected:在不同的包,有繼承關係的調用。

      default:默認,缺省,在本包下調用。

      private:私有,在本類下調用。

     

 

package

      常作爲一個小模塊的分類,有利於分層分類,使得重名的類名在編譯時不會產生覆蓋,解決了類重名的問題,關注類底層加載過程。

 

import

      通常作爲類庫的類調用時倒入,也作爲調用自定義類,在不同的包下進行一個調用,使得代碼的複用性更強。

 

main

      作爲程序的入口,可以有修飾符、返回值(void / 引用類型 / 基本數據類型)、形參列表。

 

this

      可以修飾屬性(成員變量)、方法、構造器,代表的是當前對象,作用是區分成員變量和局部變量,區分本類自身和重載的方法、構造器。

      當聲明類中的屬性(成員變量)、方法、構造器,是代表this這個類的屬性(成員變量)、方法、構造器,即調用的是本類的,而不是父類的。

 

super

      用於修飾屬性(成員變量)、方法、構造器,常用於與繼承關係的子父類中的。

      主要用於顯示聲明調用父類的屬性(成員變量)、方法、構造器,一般用於區分子父類之間的屬性(成員變量)、方法、構造器(非private修飾),用於顯示的調用父類構造器,也可以區分父類重載的構造器。

     

類的三大特性

封裝

      就跟之前的例子是一樣的,去快餐店吃飯,你不需要知道廚師是怎麼做出這個飯,你只需要知道你可以吃到飯,實現你的需求。

      封裝就是如上的例子,廚師隱藏對客戶需求的實現細節,只提供一個窗口給客戶,讓客戶提出需求(set方法),並提取結果(get方法)。

     

      一般是通過Java提供的四種權限修飾符來實現封裝,在定義類的屬性(成員變量)時,一般會使用private來修飾,從而向外部隱藏,只提供get、set方法給調用者來使用屬性(成員變量)。

      這樣做的好處在於提高重用性,即set一次即可;提高安全性,使得調用者無法直接修改類的屬性(成員變量);向外部隱藏細節,調用者不需要底層是怎麼運行的,只需要知道結果,另外通過get和set方法,可以過濾非法的數據,如青蛙的手腳都是成雙成對的出現的。

       對於編程者來說,代碼有利於閱讀,有利於編寫程序,使得程序在同一個屬性(成員變量)下可以實現多個功能,便於調用者調用。

 

       可用於屬性(成員變量)、方法、構造器,不可以用於代碼塊,因爲代碼塊是隨着類的加載而加載的,以及分爲靜態和非靜態的初始化過程。

 

       構造器和方法都可以構成重載:方法同名,或構造器同名,並且形參類型、個數、順序不同,但是與返回類型無關。

       在set方法或構造器,常用this關鍵字來區分成員變量和局部變量,以及顯示的調用本類的重載方法。

/**

 * 重寫爲什麼和返回類型無關

 * @author Levi

 */

public class J03OverloadingRetValue {

        

         public String test(String name) {

                  return "";

         }

        

         public String test(String name,int age) {

                  return "";

         }

        

         /*

          * 如果和返回類型相關的,那麼根本就不知道調用的是哪個方法。

          */

         public int test(String name,int age) {

                  return 1;

         }

}

 

繼承

      這個繼承就是生活中的繼承的意識,還是拿上面的快餐店的例子,在快餐店老闆退休了,他的兒子繼承了這家店,他兒子覺得這裝修顏色有點單調,把牆體的顏色改爲黃色和紅色,他兒子還想把名字給換掉。

     

      子類可以繼承父類的屬性(成員變量)、方法,但是都受權限修飾符而定(private私有,是無法繼承的),當父類無法滿足子類時,子類可以重寫父類的方法,如改變快餐店的接待方式,另外子類也可以擴展自己的屬性(成員變量)、方法。

      重寫說明:

      必須要是繼承關係。

      父類方法權限修飾符要是符合子類環境,如果不符合則無法重寫,即私有方法無法重寫,但子類依然可以繼承,但限於權限修飾符子類無法使用而已。

      子類重寫父類方法的方法名、形參列表,必須和父類一樣,返回類型必須是父類返回類型相同或是此返回類型的子類。

      子類重寫的方法的修飾符必須和父類相同或高於父類。

 

      繼承的好處就是提高了代碼的複用性;符合人類思想,使得類與類之間產生關係,便於程序的編寫;提高了可擴展性,子類對父類的內容擴展,使得子類的功能更加富有彈性。

      缺點就是耦合性密切。

 

多態

      街道辦的工作人員和快餐店老闆是很熟悉了,快餐店老闆有多個兒子,在去街道辦幫其中一個兒子辦點事時,快餐店老闆是可以代表其兒子,拿的身份證是其兒子的身份證,同理,快餐店老闆的兒子在某些情況下也可以代表快餐店老闆本人。

 

      多態就是一種事物的多種表現形態,根據繼承關係,使得代碼的表現形式更多有彈性,這個彈性是指向下和向上轉型。

      多態使得程序更有彈性,當一個父類被多個子類繼承時,接收的類型只需要指明是父類類型就可以了,不需要使用單一的類型來接收,這個就是向上轉型,這樣就使得父類可以使用子類的方法,使得程序代碼更富有健壯性和彈性,子類也不用創造大量的構造方法,大大的提高了代碼的效率。

      另外當子類繼承了父類時,在某些方法的返回值,可以指定是其父類,那麼在使用時,可以根據業務邏輯判斷要返回的是哪個子類。

 

      多態的條件是,必須要是繼承關係,而且子類要重寫父類的方法,不然會導致沒有意義,最後調用的還是父類的方法。

 

基礎集合

名稱

特點

Iterable

迭代器接口,實現此接口的對象允許實用foreach語句

 

Collection

集合父接口

 

List

可重複的有序集合容器,擁有特有的迭代器

 

ArrayList

線程不安全,效率高,特點是查找快,增刪慢,允許存儲null。

底層實用典型的數組來存儲,所以每次增刪都要移動數組所有元素的下角標,所以增刪慢。

 

LinkedList

增加了Vector和Stack的特性和方法。

底層採用鏈表數組的方式,每個元素的第一個交表是指向上一個元素,最後一個角標指向最後一個元素,第一個元素的角標和最後一個元素的角標都是null。

適合頻繁增刪,不適合查找。

 

Vector

古老的集合類,是線程安全的,效率低,現在基本不採用

 

Stack

擁有棧特性的線程安全類,特點是FILO,先進後出,後出先進的特性。

 

Set

不可重複的無序集合容器

 

HashSet

線程不安全,存儲元素是無序,不可重複,允許存儲null,存取快。

內部爲HashCode表數據結構,底層是哈希表 = 數組 + 鏈表/紅黑二叉樹(鏈表元素大於8個之上就轉換爲二叉樹)

 

LinkedHashSet

不可重複,順序輸出,適合頻繁遍歷,較少刪除、操作。

包含了HashSet集合,但是在此基礎上增加了一條鏈表來記錄順序,所以有序,是一個雙鏈表實現

 

TreeSet

不可重複,可以給元素排序,沒有索引,不能使用普通的for循環,查找效率高,結構爲二叉排序樹。

按照添加元素進去的升序(小到大),如果要降序(大到小)進行排序則需要重寫Comparator的compare()方法。

如果是自定義類,要求自定義類實現Comparable接口,否則無法添加進集合中,會報錯。

如果實現了Comparable接口之後,則在重寫comparaTo()方法時,則可以按照需求進行已排序,而comparaTo()接口相等則返回正整數,不相等返回負數。

如果在自定義實現了Comparable接口之後,不符合要求可以使用Comparator接口,重寫compare()方法。

Comparable中的compareTo方法比Comparator中的compare方法優先級低。

另外:

如果重寫了Comparable或Comparator接口中的方法, 建議同時重寫hashCode和equals方法,避免使用Comparable或Comparator接口中的方法,比較時如果相等,而內容不相等不會不讓其添加。

注:

建議使用set時,是先計算hashCode碼,在進行一個比較,如果使用equals,則先進行equals在計算hashCode碼,建議hashCode和equals方法同時重寫。

因爲hashCode是計算一個元素位於內存存儲的一個散列值,但這個值不是惟一的。所以,建議同時重寫equals方法,避免出現相同的hashCode碼。

兩個元素equals相同,那麼他們的hashCode碼必須相同。

Map

在集合的意義上來說,是一個和Collection同級的接口類。

擁有Key和Value,單位是entry。

Key和Value是成對出現的。

Key是不可重複的(Set)。

Value是可重複的(Collection)。

Key和Value可以同時爲null,但key爲null只能有一個。

 

HashMap

存儲無序,可以添加null鍵和null值

 

LinkedHashMap

按照添加進去的key袁術進行排序和遍歷

 

TreeMap

底層使用二叉樹,按照Key所在類的指定屬性(成員變量)進行排序,要求Key是同一個類對象,對Key考慮使用定製排序和自然排序。

 

HashTable

古老的線程安全實現類,效率低,不能添加null鍵和null值。

 

Properties

常用來處理屬性(成員變量)文件。

鍵和值都String類型。

             

 

補充

      Collections:是一個集合工具類,裏面都是靜態方法,都是用於操作集合。

      java.util.EnumSet:枚舉集合類,保證了集合中的元素不重複。

java.utilsEnumMap:key是enum類型,value可以是任意類型。

 

常用IO流

字節流

類別

名稱

特點

輸入流

 

 

InputStream

 

對應輸出流

OutputStream

 

FileInputStream

用於操作電腦中的各種文件都可以,因爲電腦的文件都是使用底層二進制碼來存儲的。

對應輸出流

FileOutputStream

 

BufferedInputStrem

緩衝字節流,內部具備一個8K的數組,但是不具備有操作五年級的功能,類似一個大的管道,使得數據傳輸速度更快。

對應輸出流

BufferedOutputStream

 

SequenInputStream

序列輸入流(合併流),可以將N個文件的內容合併成一個文件,也可以把一個文件拆分成爲多個文件。

對應輸出流

SequenOutputStream

 

ObjectInputStream

對象輸出流,是根據Java的序列化和反序列化的機制,但是如果是自定義類一定要實現Seriablizable纔可以加個這個自定義類的對象永遠保存在硬盤中。

建議:在序列化過程中,給予其一個UID值,是的在保存到文件中的狀態取出時不會改變,因爲當類發生改變時,在取出時,就不會出現報錯,因爲類發生改變時,再次取出,JVM認爲當前類狀態不一致。

而JVM計算出來的UID是根據成員變量來計算的,與方法無關,所以建議自定義一個,而且類發生改變UID也不會發生改變。

對應輸出流

ObjectOutputStream

           

 

字符流

類別

名稱

特點

輸出流

 

 

 

Writer

 

對應輸入流

Reader

 

OutputStreamWriter

轉換流,一般應用於在項目字符集不一致時,可以使用轉換流讀出,使得不會出現亂碼。

 

對應輸入流

InputStreamReader

 

FileWriter

讀取字符文件

對應輸入流

FileReader

 

BufferedWriter

是一種緩存的字符流,內部有一個8K的數組,但是不具備有操作文件的功能,類似一個大管道,傳輸更快。

對應輸入流

BufferedReader

 

PrintWriter

打印流,是一個字節和字符流之間的一個橋樑,可以配合System,out/in配合使用,使得輸出或者輸入的位置可以自定義。

常見於適用於異常

           

 

其他常用

      System.out/in:標準的輸出流,可以調用System.out/in來指定輸出或輸入位置。

      Closeable接口:如果未實現這個接口的話,那麼這個流則無法關閉。

      AutoCloseable接口:JDK7的特性,會自動關閉流。

      JDK7.0的異常新特性:在try()在()中寫入要關閉的流,就可以實現自動關閉。

      Properties:實現了Hashtable的類,一般用於配置文件,這個類的特點是沒有泛型,而且這個類的鍵和值都String類型。

     

反射

      Java類在加載時,會經歷雙親委派機制,當程序主動使用某個類時,如果該類還沒有被加載到內存中,則系統會通過加載、鏈接、初始化三個步驟對該類進行初始化,類加載是指把類的class文件讀入內存中,併爲之創建一個java.lang.Class對象,也就是說程序使用任何類的時候,JVM都會爲其創建一個class對象。

      Java反射就是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性(成員變量)和方法,對於任何一個對象,都能夠調用它的任意方法和屬性(成員變量),並且能改變它的屬性(成員變量),反射機制允許程序在運行時取得任何一個已知名稱的class的內部信息,包括其修飾符、屬性(成員變量)、方法等,並可以在運行時改變屬性(成員變量)內容或者調用方法。

      如此之下,就可以更靈活的編寫代碼,代碼可以在運行時裝配,無需在組件之間進行源碼鏈接,降低代碼的耦合度,即動態的裝配。

      在以前使用Spring的xml配置時,都會在spring配置文件上,配置上要掃描的包,而Spring內部就是通過反射獲得這些包下的類,進而創建一個對象。

     

      可用於判斷運行時對象所屬的類;對象鎖具有的成員變量和方法;甚至可以調用到private方法;注意的是final是沒有辦法改變。

 

      查閱Java API:java.lang.reflect.AccessibleObject

 

參考工廠設計模式:

/**

 * 工廠模式

 * @author Levi

 */

public class J01FactoryMode {

    public static void main(String[] args) {

         /*

          * 工廠製造車是視市場環境來決定今年要增產哪些車型的。

          * 製造哪些車,怎麼製造,工廠老闆並不關心。

          * 並且後期要製造新的車,客戶端的代碼是不用直接修改的,降低了客戶端代碼和實現類的耦合度,傳不同的參數實現不同的對象

          */

         Car car = Factory.getInstanceCar("BMW");

         car.manufacture();

    }

}

 

/**

 *

 * @author Levi

 */

abstract class Car {

    /**

     * 製造車

     * @author Levi void

     */

    public abstract void manufacture();

   

}

 

/**

 * 具體的車

 * @author Levi

 */

class Bmw extends Car {

 

    @Override

    public void manufacture() {

         System.out.println("開始製造BMW這款車");

         System.out.println("...");

         System.out.println("BMW製造完成");

    }

}

 

/**

 * 工廠

 * @author Levi

 */

class Factory {

    public static Car getInstanceCar(String type) {

         if(type.equalsIgnoreCase("BMW")) {

             return new Bmw();

         }

         return null;

    }

}

 

常見疑惑

java.lang.Object

      Object是Java默認提供的一個類,這個類是所有類的父類,也就是說任何一個類定義的時候,沒有明確的繼承一個父類,默認就是Object的子類。

      但繼承是有子父和祖宗概念的,即所有的類最終的祖宗就是Object。

      所以所有的類都默認實現了這個類的所有方法。

     

      好處是Object是一個超類,使得語言更加靈活,可以在擴展類庫時,使得其可以兼容所有類庫的類。

 

      注:接口是沒有默認父類的,而且接口是用於頂層設計的,接口是不能繼承類,而Object是一個類,所以接口是沒有繼承Object。

 

hashCode()

      Hash翻譯爲散列,也有音譯爲哈希的,把任意長度的輸入數據,如字符、字符串、數字等輸入,通過算法,計算得到一個數值,這個數值就是Hash編碼,即Hash是一種算法。

     

      在Java中,對象的hashCode默認是通過對象的內存地址計算出來的一個數值,可以重寫hashCode方法來編寫自定義類的hash算法,默認是調用父類的hashCode方法,hashCode相當於是一個對象的編碼,一般不直接調用。

      其作用主要是可用於集合中的數據查找,提升查找的速度。

 

      hashCode()返回的是該對象的哈希碼值,同一個對象的哈希碼值是唯一的,即不同對象,hashCode值是不同的。

      equals()是返回比較的結果,默認比較的是對象的內存地址。

如果兩個對象equals相同,那麼這2個對象的hashCode碼必須一樣。

      注:自定義類重寫equals(),是告訴JVM如何比較這兩個對象,即使得比較兩個對象的時候,比較屬性(成員變量)是否相等,而不是內存地址。

 

toString()

      調用toString()和直接調用System.out.println()方法輸出一個對象的時候,輸出結果是一樣的。

      查看源碼:會發現sysout會自動調用對象的toString()方法。

     

/**

     * Prints an object.  The string produced by the <code>{@link

     * java.lang.String#valueOf(Object)}</code> method is translated into bytes

     * according to the platform's default character encoding, and these bytes

     * are written in exactly the manner of the

     * <code>{@link #write(int)}</code> method.

     *

     * @param      obj   The <code>Object</code> to be printed

     * @see        java.lang.Object#toString()

     */

    public void print(Object obj) {

        write(String.valueOf(obj));

    }

 

      toSting()方法,默認返回對象的描述信息:

      java.lang.Object@de6ced

      包名.類名@十六進制地址值

 

爲什麼使用Set要同時重寫hashCode和equals方法?

      Set底層存儲是根據hashCode散列計算的值來存儲的,equals就是當2個hashCode值一樣是用於判斷內容是否相同,相同則不加入,不相同則加入。

      如果2個值的hashCode相同,equals不相同,則會在其元素位置上,在開闢一個空間出來,同時存儲2個元素。

     

      即,如果只重寫了equals方法,兩個值的equals返回true,集合是不允許出現重複的,只能插入一個。此時,如果沒有重寫hashCode方法,那麼久無法定位到同一個位置,集合還是會插入這個值,這樣就出現了重複了,那麼重寫的equals方法就沒有意義了,所以一定要同時同寫。

      注:equals相同,hashCode一定相同。相反,hashCode相同,equals不一定相同。

public class HashCodWithEquals {

    public static void main(String[] args) {

         Test t = new Test("AA", 12);

         Test tt = new Test("AA", 12);

         System.out.println(t == tt);

         System.out.println(t.equals(tt));

         System.out.println(t.hashCode());

         System.out.println(tt.hashCode());

    }

}

class Test {

    private String name;

    private int age;

 

    public Test() {

 

    }

 

    public Test(String name, int age) {

         this.name = name;

         this.age = age;

    }

 

    public String getName() {

         return name;

    }

 

    public void setName(String name) {

         this.name = name;

    }

 

    public int getAge() {

         return age;

    }

 

    public void setAge(int age) {

         this.age = age;

 

    }

 

    // 重寫toString 方法

    @Override

    public String toString() {

         return "name" + name + "age" + age;

    }

 

    // 重寫hashCode方法

    @Override

    public int hashCode() {

         return name.hashCode() + age;

    }

 

    // 重寫equals 方法

    @Override

    public boolean equals(Object o) {

         // 判斷這個o是不是這個類的實例,不是肯定是false

         if (!(o instanceof Test))

             return false;

 

         // 判斷這個如果是這執行一個向下轉型強轉

         Test t = (Test) o;

         // 如果是判斷是否有傳參,數字沒有空的概念

         if (name == null)

             return false;

         // 判斷傳入的參數是不是一樣

         if (!name.equals(t.getName()))

             return false;

         if (age != t.getAge())

             return false;

         return true;

    }

}

 

java.lang.String

      String描述的是文本字符串序列,Java程序中看到的字符串常量值,都是String類型的對象,字符串在Java中不是基本數據類型,是引用數據類型(類類型)。

      String類是final類,該類不能有子類,並且實現了java.lang.CharSequence接口。

     

      String的字符串數據是存儲在內存的常量池的,在創建N個相同的字符串時,在內存是隻存儲一份的,提高了內存的空間使用率。

      注:new String(“abc”),這樣是創建了兩個對象,一個是abc這個字符常量,一個是new出來的這個String對象。

 

String類對equals()進行了重寫:

/**

     * Compares this string to the specified object.  The result is {@code

     * true} if and only if the argument is not {@code null} and is a {@code

     * String} object that represents the same sequence of characters as this

     * object.

     *

     * @param  anObject

     *         The object to compare this {@code String} against

     *

     * @return  {@code true} if the given object represents a {@code String}

     *          equivalent to this string, {@code false} otherwise

     *

     * @see  #compareTo(String)

     * @see  #equalsIgnoreCase(String)

     */

    public boolean equals(Object anObject) {

        if (this == anObject) {

            return true;

        }

        if (anObject instanceof String) {

            String anotherString = (String)anObject;

            int n = value.length;

            if (n == anotherString.value.length) {

                char v1[] = value;

                char v2[] = anotherString.value;

                int i = 0;

                while (n-- != 0) {

                    if (v1[i] != v2[i])

                        return false;

                    i++;

                }

                return true;

            }

        }

        return false;

    }

 

Serializable接口

      Seriablizable是一個序列化接口,實現這個接口後可以實現對象序列化,並且給類添加一個序列號後,可以保證在讀取時,不會出現丟失,還可以保證對象的狀態的一致性。

 

序列化

      序列化的主要作用是讓對象永久的存儲在硬盤中。

 

      序列化:把對象轉換成二進制(字節序列)存儲到文件中。

      反序列化:把文件中的二進制(字節序列)對象懷府到類中就是反序列化。

      注:序列化時,一般需要提供一個序列化編碼,確保在恢復時,仍然是指向同一個內存區域(Serializable接口作用)。

 

      可變序列化:對象在創建出來後,仍然可以改變位於對象中的內容,如StringBuffer。

      不可變序列化:在創建對象了之後,內存中的內容是不可變的,如String。

 

可變類與不可變類

      可變類:創建出來這個類的實例時,是可以改變這個類的實例的內容的。

      不可變類:創建出這個類的實例時,是不可以改變的。

 

值傳遞與引用傳遞

      值傳遞:當傳遞一個實際的值給這個形參時,是用來初始化這個形參的,而形參的值發生改變時,是不會改變傳遞過來的這個實際的值的,即沒有改變傳進來的這個值的內存的值。

      值傳遞的類型是基本數據類型,即方法形參時基本數據類型,這是一個值拷貝的過程。

     

      引用傳遞:當調用某個方法時,參數是對象或數組,而這個對象調用某個方法時,這個方法就是使用內存中的地址值空間實際參數進行操作的,而當這個方法結束時,這些操作修改了的結果都會保留下來,即改變了內存空間的值。

      引用傳遞是引用類型,在傳遞時,是傳遞地址值,改變內存的數據。

     

public class J05Pass {

   

    public static void main(String[] args) {

         J05Pass pass = new J05Pass();

        

         int value = 6;

         pass.valuePass(value);

         System.out.println(value);

        

         TestObject testObject = new TestObject();

         testObject.setName("abc");

         pass.referencePass(testObject);

         System.out.println(testObject.getName());

    }

   

    /**

     * 值傳遞

     * @author Levi

     */

    public void valuePass(int i) {

         i = 5;

         System.out.println("方法內修改後的值:" + i);

    }

   

    /**

     * 引用傳遞

     * @author Levi

     */

    public void referencePass(TestObject testObject) {

         testObject.setName("ccc");

         System.out.println("方法內修改後的值:" + testObject.getName());

    }

}

class TestObject {

    private String name;

 

    public String getName() {

         return name;

    }

 

    public void setName(String name) {

         this.name = name;

    }

   

}

 

可變字符序列與不可變字符序列

      可變字符序列:即可改變內存中的數據,是將字符的值存儲到數組中,當調用了toString()時,會把數組的值拷貝到對應的可變字符對象中(引用傳遞),從而實現一個追加的功能。

      代表的可變字符序列類有StringBuilder和StringBuffer,在使用時,就是使用append()方法,將值存儲到數組,實現這樣一個追加功能。

 

      不可變字符序列:代表類有String,String的值是存儲到內存的方法區中的常量池的,並且String這個類是使用final修飾的,而且這個類底層存儲是使用數組存儲的,其數組也是使用final修飾的,使得其在內存中是可以改變的。

      所以創建內存中沒有的,一個新的字符串時,是會在內存中創建出一個新的字符串。

     

      補充:

      String:存儲在常量池的。

      StringBuffer:線程安全的字符類,適用於對字符串進行修改、插入、刪除等操作,底層是使用數組進行存儲的,默認空參的構造器數組長度是16,當超出數組長度,則會以當前長度*2+2。

      StringBuilder:線程不安全的字符類,效率上優於StringBuffer,底層存儲是使用數組,默認空參構造器的數組長度是16,當超出數組長度,則會以當前長度*2+2。

 

爲什麼重寫Comparator方法,同時重寫equals?

      equals相同,hashCode必然是相同的,而重寫Comparator的compare時,建議重寫equals方法,可以避免2個值一樣則後一個無法進來,而hashCode是確定2個值的位置。

 

數組和鏈表的區別

      線性結構的典型數據關係是一對一的,是有序的數據集合,即從左往右或從右往左的行元素,而不是像二維或多維數組受行和列即更多因素的影響,這就是一對一的關係。

      線性結構是除了第一個和最後一個數據元素之外,其他數據元素都是首尾相接的。

      常用的線性結構有:棧、隊列、數組。

      特點:

      集合中必須存在唯一的一個第一個元素。

      集合中必須存在唯一的一個最後的元素。

      除了最後元素之外,其他數據元素均有唯一的後繼。

      除了第一元素之外,其他數據元素具有唯一前驅。

      ……

      即收尾相接,前後鏈接成線。

 

      非線性結構的一個明顯特徵就是一個結點元素可能對應多個直接前驅和多個後繼,各個數據元素不在保持在一個線性序列中,每個數據元素可能與零個或多個其他數據元素髮生聯繫,這就是所謂的一對多或多對一,總之就不是一對一。同時,也會根據關係不同,可分層、分羣結構。

      常見的有線性結構有:多維數組、二叉樹。

 

      數組是屬於線性結構,元素是可以直接索引的,鏈表也是線性結構的,對於元素用指針往後遍歷N遍即可。

類型

數組

鏈表

內存分配

靜態分配

系統自動申請空間

動態分配

手動申請空間

內存空間

連續

不連續

內存位置

棧區

堆區

示例

看電影,爲了保證10個人能坐在一起,就必須提前訂好10個連續的位置,這樣的好處在於保證了10個人可以坐在一起,但是這樣的缺點是,如果來的人不夠10個人,剩下的位置就浪費了。

如果臨死多來了個人,那麼10個就不夠用了,可能需要把第11個位置上的人挪走,或者是他們11個人重新去找一個11連坐的位置,這樣的話,效率都很低。

如果沒有找到符合要求的座位,就沒有辦法坐了。

在電影院中,每個人隨便坐。

第一個人知道第二個人座位號,第二個人知道第三個人座位號……

如果再來一個人了,要坐在第三個位置上,只需要找第二個人拿到原來第三個人的位置就行了,其他人不用動。

特點

數組需要預留空間,在使用前要先申請佔內存的大小,可能會浪費空間。

增加和刪除數據效率低,增加數據時,這個位置後面的數據在內存中都要往後移動,因爲需要連在一起。

數組是連續的,知道每一個數據的內存地址,可以直接找到對應地址的數據,因此隨機讀取效率很高。

但是數據定義空間不夠時,要重新定義數組,所以並不利於擴展。

不需要一開始指定內存大小,擴展方便,數據隨意增刪。

在內存中可以存在任何地方,不要求連續的。

每個數據都保存了下一個數據的內存地址。

查找數據時效率低,因爲不具備隨機訪問性,所以訪問數據時,是要知道類推的方式,找到第一個,再找到第二個……

優點

隨機訪問性強

查找速度快(時間複雜度O(1)

增刪速度快(時間複雜度O(1)

大小不固定,可以動態擴展

內存利用率高,不會浪費內存

缺點

增刪速度慢(時間複雜度O(n)

內存空間要求高,必須要有足夠大的連續內存存儲空間

數組大小固定,不能動態擴展

不能隨機查找,必須從第一個開始查找,查找效率低(時間複雜度O(n)

 

      注:

隨機讀取:就是直接存取,可以通過下角標直接訪問,也是數組的特性,因爲數組在存儲是上使用了一塊連續的存儲空間,元素逐個存儲,可以在O(1)的時間內存,即如果知道第一個元素的位置,就可以知道第X元素的位置。

      非隨機讀取:順序存取,不能通過下角標訪問,只能按照順序讀取,這也是鏈表的特性。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章