Java基礎複習,整理知識點

Y_JunKang發表於2016-03-07

最近開始複習Java基礎,和Android,將知識點整理如下,另外開始嘗試使用MarkDown寫部落格,寫的有問題的地方還請各位包含。

Android部分的內容整理見我的另一篇部落格

1.Java關鍵字總結梳理

首先這裡總結一下在編寫類時常常會碰到的一些關鍵字:

private,public,protected,default

關鍵字 同一個包中的其他類 不同包中的其他類 子類 自身
private No No No Yes
protected Yes No Yes Yes
public Yes Yes Yes Yes
無修飾(default) Yes No No Yes


注意:以上幾個修飾詞是和包有關的

static

static關鍵字修飾內容的幾個特點:
1. static修飾的變數和類檔案一同被載入到記憶體中
2. 被修飾的方法可以直接通過類名加點來引用,也就是說static修飾部分的引用是不需要將物件例項化的。

有關static一些注意事項

  • static方法只能訪問static變數
  • static方法中不能使用this,super這樣的關鍵字,因為static優先於物件被載入到記憶體之中,static執行時物件可能還未被例項化。
  • 內部類包含static修飾的屬性或方法時,內部類必須也被static修飾,其實理解起來也很簡答,應為static會優先被載入,如果內部類不被static修飾,那麼內部變數是不會被提前載入的,這時static關鍵字修飾就不起作用了。

final

  • final是一個修飾詞,可修飾類,變數,函式
  • final修飾的類不可被繼承
  • final修飾的函式無法被複寫
  • final修飾的變數只能賦值一次

abstract

  • abstract同樣是一個修飾詞,能夠修飾方法和類
  • abstract修飾的類無法被例項化,只能夠通過子類的繼承並實現內部所有的抽象函式才能被例項化。
  • abstract修飾的函式只需要申明方法名,引數,不需要寫函式體。
  • 抽象類中同樣可以定義非抽象的方法,同時抽象類也有建構函式,這個建構函式提供給子類例項化時使用。
  • 抽象類中也可以沒有抽象的方法。
  • abstract不可以和static,private,final公用,簡單理解一下,static修飾說明優先載入,而abstract未被實現,所以無法被優先載入。final修飾表名為最終狀態無法修改,而abstract修飾的需要子類去實現,必須可以修改。private表示私有化,自由自身能夠訪問到,而abstract需要子類訪問並實現函式體。

instanceof

用於判斷類是否實現了指定介面或實現了指定的類,舉個簡單的例子:

public class Test {

    public static void main(String[] args) {

        NullPointerException e = new NullPointerException();
        System.out.println(e instanceof Exception);
    }
}

輸出結果為true

2.物件導向

物件導向最為主要的兩個內容

  • 過程,其實也就是函式
  • 物件,則是對一些函式和屬性進行了封裝

所以,其實在物件導向的程式設計過程中,編寫類,也就是完成對函式和成員變數的封裝(當然,在編寫前需要對類進行設計)。

介面和介面的實現

  • 介面有interface關鍵字定義,內部函式預設為抽象的,所以介面無法例項化.
  • 一個類在實現了介面中所有的方法時才能被例項化,否則這個類還是一個抽象類,無法被例項化,實現某個介面使用關鍵字implements
  • 一個類可以實現多個介面,而且介面之間也可以相互繼承,並且介面可以多繼承,一個簡單的例子:
public class Test {

    interface interface1 {
        void function1();
    }

    interface interface2 {
        void function2();
    }

    interface interface3 extends interface1, interface2 {

    }

    class MyClass implements interface3 {

        @Override
        public void function1() {
            // TODO Auto-generated method stub

        }

        @Override
        public void function2() {
            // TODO Auto-generated method stub

        }

    }
}

可以看到,當MyClass實現了interface3,並且interface3繼承了interface1和interface2時,MyClass需要實現interface1和interface2中的所有方法。

繼承

繼承當中子類與父類之間的一些關係:

  • 成員變數:當子類中出現與父類相同的成員變數時,在子類中呼叫優先呼叫子類的該變數,如果想要呼叫父類中的該成員,則需要使用super關鍵字。
  • 成員函式:當子類中出現與父類之中相同的方法時,子類的方法會將父類中的方法覆蓋。在外部呼叫時,會呼叫子類的方法。
  • 建構函式:子類的建構函式中會預設呼叫super()意味著在構造子類之前需要先對其父類進行構造。

多型

舉一個簡單的多型例子:
父類定義

class Father
{
    void sayHi()
    {
      System.out.println("father hello");
    }
}

子類定義:

class Son extends Father
{
    void sayHi()
    {
      System.out.println("son hello");
    }
}

測試程式碼:


    public static void main(String[] args) {

        Father f = new Son();
        f.sayHi();

    }

輸出結果為“son hello”
可以看出,可以通過父類訪問到子類的方法,這樣為我們操作大量物件提供了方便,當我們需要操作大量不同物件時,我們只需要通過訪問他們的父類來操作他們共性的一部分即可。
當然以上只是簡單的多型的例子,動態在Java中有很多體現,介面的實現,繼承,複寫都體現著多型。

3.那些我們常常遇到的類

Exception

異常是程式執行期間發生的不正常的行為,Java中將異常進行了封裝,並能夠使用try catch語句對異常捕獲和處理。所有異常的父類都是Exception,這些異常類的共有特點就是能夠被丟擲。
這裡對比一下throws和throw的區別:

throws丟擲的是異常的類名,出現在方法頭部,throws只是存在丟擲異常的可能並且自身無法處理,需要交給呼叫者來處理。

private void main() {
        try {
            function();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void function() throws IOException {
        Socket s = new Socket("", 11);
    }

可以看到,我們通過throws將異常丟擲,並且在function被其他函式呼叫時,呼叫者任然需要對異常進行捕獲和處理,當然呼叫者也可以繼續講異常丟擲,等待其他的呼叫者對異常進行處理。

throw丟擲的是異常的物件,出現在函式體內部,throw一定會丟擲異常。看例子

public static void main(String[] args) {

        String real_pass_word = "123456768";
        String input_pass_word = "123456";

        try {
            if (!real_pass_word.equals(input_pass_word)) {

                throw new IOException();

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

可以看到需要對當密碼不相等時,我們使用throw丟擲了異常IOException,並且在外層通過try catch對異常進行了捕獲。

Thread

首選明確一下執行緒和程式的區別:

程式是程式在執行當中分配資源和管理資源的最小單位。程式之間通常不會共享資源,每個程式獨有系統分配的資源,程式中可以包含多個執行緒。

執行緒是程式中的一個程式執行控制單元,同時也是一條執行路徑,屬於同一個程式的執行緒之間共享程式內的資料,並且可以相互之間進行通訊。

建立一個新的執行緒有兩種辦法:
- 通過Thread或繼承至Thread的類建立執行緒物件
- 通過實現Runable介面,並將實現Runable介面的物件作為引數傳入Thread()構造方法中(這裡解釋一下Runable介面出現的原因,由於,應為Java中類是單繼承的,所以當一個類既想作為一個執行緒也有其他必須要繼承的類時,就可以通過實現Runable介面來將需要作為執行緒執行的部分放入run方法之中)

這裡有一個常見的面試題

public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("哈哈哈");
            }
        }) {
            public void run() {
                System.out.println("呵呵呵");
            };
        }.start();
    }

上面的程式碼執行結果輸出的“呵呵呵”,簡單的解釋一下,上述程式碼首先建立了一個Thread物件,此時run中輸出“哈哈哈”,然後我們通過Thread實現了一個匿名的內部類,這個類的run方法輸出“呵呵呵”,最後執行輸出的結果就是“呵呵呵”了。

多執行緒的好處當然是能夠更好的利用cpu,但是多執行緒同樣也帶來了一些問題,由於同一個程式內的多個執行緒共享著程式內的資源,當多個執行緒同時執行時就容易出現共享資源的搶奪,一個常見的情況是多個執行緒在共同訪問一個資料時,A執行緒正在運算元據,這個時候B執行緒進入並修改資料,這樣會導致產生錯誤的結果,為了解決執行緒訪問共享資源的問題,Java提供了關鍵字synchronized,以下提供一個synchronized關鍵的使用例子:

public class Test {

    public static void main(String[] args) {
        Example example = new Example();

        Thread t1 = new Thread1(example);
        Thread t2 = new Thread1(example);

        t1.start();
        t2.start();
    }

}

class Example {
    public synchronized void execute() {
        for (int i = 0; i < 10; ++i) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Hello: " + i);
        }
    }

}

class Thread1 extends Thread {
    private Example example;

    public Thread1(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute();
    }

}

上面的程式碼對於excute方法使用了synchronized修飾,執行結果為先列印一次0-9再列印一次0-9,而去除synchronized關鍵字後,會同步列印,輸出結果為001122…..99。證明synchronized保證了函式被呼叫後執行完才能被其他呼叫者再次呼叫。

當然,處理synchronized方法以外,我們同樣可以使用synchronized塊來對小部分的程式碼進行同步,再來一個例子:

public class Test {

    private static Test instance;

    private Test() {
    }

    public static Test getInstance() {

        if (instance == null) {

            //鎖是類的位元組碼檔案,由於是靜態方法
            synchronized (Test.class) {

                if (instance == null) {
                    instance = new Test();
                }

            }
        }

        return instance;
    }

}

上面的程式碼是一個典型的使用延遲載入的單例模式類,這種時候當多執行緒同事呼叫getInstance方法時,可能出現建立多個實體的情況,這樣顯然不符合單例模式的思想,所以我們需要在建立實體時加上程式碼同步。為了避免由於判斷加鎖和解鎖的過程帶來的低效,可以再同步部分再次新增判斷,避免沒有必要的同步。從Java 5 開始,推出了新的同步解決辦法Lock
這部分的內容這裡有一個不錯的博文,推薦給大家,傳送門點這裡

String

String類,作為Java中使用最為頻發的幾個類之一,有必要好好熟悉一下,String類其實很簡單,就是對字串的封裝,以及提供了很多方法方便我們操作字串。這裡整理了一些很好用的,但是可能被大家忽略的方法:

方法 作用
charAt 獲取指定位置的字元
indexOf 順序獲取字元或字串的位置,沒有返回-1
lastIndexOf 倒序獲取字元或字串的位置,沒有返回-1
subString 獲取指定位置的子串
contain 判斷字串是否包含指定字串
startWith 判斷字串是否以指定字串開頭
endWith 判斷字串是否以指定字串結尾
equalsIgnoreCase 判斷字串是否相等,忽略大小寫
toLowerCase 所有字母轉換為小寫
toUpperCase 所有字母轉換為大寫
replace 替換
trim 去除字串首位空格


總結完String的一些方法,這裡再簡單的提一下StringBuilder和StringBuffer兩個類。String是賦值後無法修改的(我們可以看到所有的關於String的操作返回結果都是一個新的String物件而不是原物件),而StringBuilder和StringBuffer是可修改的,這兩個類作用類似都用於構建字串,不同在於:StringBuffer對於多執行緒是安全的,而StringBuilder對於多執行緒是不安全的。所以推薦的使用情況如下:
- 單執行緒操作時推薦使用StringBuilder,效率更高。
- 多執行緒時操作推薦使用StringBuffer,更加安全。

集合

Java中提供了功能強大的集合類,這裡我們對Java中的集合類進行整理:

  • Iterator (訪問集合的迭代器介面)
  • Collection(單列)
    • List(有序,可儲存重複元素,元素有索引)
    • Set(無序,不可儲存重複元素,元素都是唯一的)
  • Map(雙列)
    • Hashtable
    • TreeMap

List介面下我們經常用到主要為以下幾個類:

  • ArrayList: 底層陣列實現,查詢速度很快,執行緒不同步
  • LinkedList:底層連結串列實現,增刪熟讀很快,執行緒不同步
  • Vector:底層陣列實現,查詢增刪速度都很慢,執行緒同步

在通過Iterator對List介面物件進行迭代時,如果想要對集合物件進行操作,會出現ConcurrentModificationException的異常,錯誤原因是迭代過程中Iterator已經在操作集合物件了,這時我們再去操作集合物件會導致訪問衝突。解決辦法是使用iterator的子介面ListIterator,這個介面提供了對List集合物件的操作。

注意:由於在List集合中,判斷元素是否存在或是刪除元素都是通過元素的equals方法,所以在日常的開發過程中,通常需要自己重寫集合元素物件的equals方法,這樣能夠提高List集合的操作效率。

Set介面下我們經常用到主要為以下幾個類:
- HashSet:底層資料結構為Hash表,效率高,執行緒不同步
- LinkedHashSet:HashSet的子類,有序
- TreeSet:底層資料結構為二叉樹,可以對之中的資料進行指定順序的排序,執行緒不同步

簡單介紹一下Hash表是什麼:
1.對元素中特有資料進行Hash演算法,並得到元素的Hash值
2.Hash值就是這個元素在表中的位置
3.在儲存過程中,如果Hash值發生衝突,則需要進行衝突解決,最簡單的一個Hash衝突解決辦法為再次判斷元素是否相同,如果相同則不儲存,如果不同則儲存,在原元素的Hash值的基礎上加1。

(提供一個Hash衝突解決的博文連結

4.儲存Hash值的結構稱之為Hash表
5.為了提高效率,應該儘量保證元素關鍵字的唯一性,這樣能夠提高Hash表的效率。

TreeSet中的元素需要是可比較的,為了保證元素可比較,需要元素實現Comparable介面,TreeSet中為保證元素的唯一,是通過Comparable介面的toCompare方法來實現的,當toCompare方法返回0時,表示兩個元素相同。

Map
Map介面與Collection有很大的區別,Map一次儲存一個鍵值對,並且需要保證Map中所有的健是唯一的。
迭代Map的方法:

  • 通過Map.keySet()獲取健的Set,然後再遍歷時通過getKey方法迭代
  • 通過Map.entrySet()方法獲取到鍵值對set,直接遍歷。

這裡總結一下集合的使用規律:

當需要儲存的是單個資料時考慮使用Collection,當需要儲存的內容是鍵值對形式的資料使用Map

  • 需要保證內部元素唯一用Set,不要需要使用List
  • 看到Array說明底層資料結構是陣列,證明查詢速度快
  • 看到Linked說明底層資料結構是連結串列,增加刪除的速度開
  • 看到Hash說明底層資料結構是Hash表,需要儘量保證內部元素的Hash值唯一,並且需要複寫元素的hasCode方法和equals方法。
  • 看到Tree就說明底層的資料結構是二叉樹,需要相當排序,內部元素需要Comparable

泛型
泛型只是針對編譯時期,在執行時期並不存在泛型的概念,泛型只是為了將一些型別強制轉換的異常轉化為編譯錯誤。使用泛型時必須保證等式兩邊宣告的泛型是一樣的。
泛型的上限與下限:

  • ?extends E 泛型指 接受E和E的所有子類
  • ? super E 泛型指接受E和E的所有父類

IO Stream

  • 首先明確一下字元流和位元組流的區別(這個問題今天早上把我一個電話面阿里的同學給難住了,有必要好好記一下)

  • 位元組流:位元組流可以處理幾乎計算機當中的所有資料(凡是以InputStream和OutputStream結尾的都為位元組流)

  • 字元流:字元流的出現是應為,各個國家的語言不通,字元也不通,所以當將各總編碼表和流封裝在一起,為了方便字元的操作,所以設計到字元操作的時候優先考慮字元流(凡是以Reader和Writer結尾的都是字元流)

開發的時候如何明確該使用什麼樣的流呢:

  1. 如果需要讀入資料使用InputStream和Reader,寫入資料使用OutputStream和Writer
  2. 如果需要處理純文字物件 使用Reader和Writer,否者使用InputStream和OutputStream
  3. 明確使用那個具體的流,通過明確具體操作的資料裝置:(磁碟)File,(記憶體)CharArray。。。
  4. 是否需要利用快取提高效率,如果需要可以使用Buffer對流進再一次的封裝。

Java部分到這裡基本整理完畢了,4天的時間,也算是查漏補缺,感覺的確再看一遍書又有了一些提高。這裡整理的是我不太熟悉的一些知識點,並不一定全面,明天開始整理有關Android的部分。加油~!~~~

相關文章