1. 程式人生 > >Java 基礎回顧:基本知識總結

Java 基礎回顧:基本知識總結

本篇文章對 Java 中的語言相關的基礎知識進行了總結,其中部分內容參考了阿里的 Java 開發規範

1、術語

No 結論
1 JDK 是編寫Java程式的程式設計師使用的軟體
2 JRE 是執行Java程式的使用者使用的軟體
3 Java的執行模式是編譯和解釋型,Java程式首先由編譯器轉換為標準位元組程式碼,然後由JVM來解釋執行,JVM將位元組程式碼程式同作業系統和硬體分開,使Java程式獨立於平臺執行
4 垃圾回收機制是一個系統執行緒,對記憶體使用進行跟蹤
5 Class 類物件由 Java 編譯器自動生成,隱藏在 .class 檔案中
6 JVM 執行 Java 程式碼的步驟: 裝載-連結-初始化(執行時不需要載入程式碼)
7 動態繫結: 在執行時能夠自動地選擇呼叫哪個方法的現象
8 Jar 是一種壓縮的檔案,使用了 ZIP 格式,其中包含一個清單檔案 MANIFEST.MF 位於 META-INF 目錄
9 JavaScript 是網頁中使用的指令碼語言,語法類似於Java,除此之外無任何聯絡
10 Java 大小寫敏感

2、Java程式的基本結構

程式結構

package me.shouheng;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String ...args) {
        System.out.println("Hello world!");
        List<String> s = new ArrayList<String>();
    }
}

簡單說明:

No 說明
1 main 方法用於控制程式的開始和結束,main 方法包含以下要素:publicstaticvoidString[] args
2 可以使用 new 關鍵字建立類的例項物件,可以直接通過類名呼叫類所提供的靜態成員

No 說明
1 包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。【規範】
2 package 語句必須是源程式檔案中第一個非註釋、非空白語句
3 包的成員可以包含子包、類、介面。如果原始碼中沒有指定包,則使用預設包
4 一個包(子包)的成員不能重名,即使不同的型別

包的訪問方式:

  1. 完全限定名稱方式訪問:java.net.InetAddress hostAdd
  2. 匯入包成員:import java.net.InetAddress
  3. 匯入整個包:import java.net.*
  4. 匯入的靜態成員:import static java.lang.Math.*
  5. 訪問包成員名稱衝突:需要使用完全限定名稱方式

3、識別符號

字元、數字、下劃線、美元符號($)組成,且開頭不能為數字,分大小寫,沒有長度限制,不能與關鍵字相同. 約定如下

No 元素 約定
1 常用名詞,全部小寫
2 類、介面 常用名詞,每個單詞首字母大寫;(PascalCase規則)
3 方法 常用動詞,首字母小寫;(camelCase規則)
4 常量 常用名詞,全部大寫,單詞之間用下劃線(_)分隔
5 變數 成員名稱,首字母小寫. (camelCase規則)

規範:

No 規範
1 【強制】程式碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束
2 【強制】程式碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式
3 【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外:DO/BO/DTO/VO/AO
4 【強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長
5 【強制】抽象類命名使用Abstract或Base開頭;異常類命名使用Exception結尾;測試類命名以它要測試的類的名稱開始,以Test結尾
6 【強制】POJO 類中布林型別的變數,都不要加is ,否則部分框架解析會引起序列化錯誤
7 【推薦】如果使用到了設計模式,建議在類名中體現出具體模式
8 【推薦】 如果是形容能力的介面名稱,取對應的形容詞做介面名( 通常是–able的形式)
9 【強制】對於 Service 和 DAO 類,基於 SOA 的理念,暴露出來的服務一定是介面,內部的實現類用 Impl 的字尾與介面區別

此外,Service / DAO 層方法命名規約

  1. 獲取單個物件的方法用 get 做字首。
  2. 獲取多個物件的方法用 list 做字首。
  3. 獲取統計值的方法用 count 做字首。
  4. 插入的方法用 save( 推薦 ) 或 insert 做字首。
  5. 刪除的方法用 remove( 推薦 ) 或 delete 做字首。
  6. 修改的方法用 update 做字首。

4、程式流程

4.1 基本程式結構

  1. 順序結構
  2. 選擇結構:if 語句和 switch 語句
  3. 迴圈結構:for 迴圈、while 迴圈、do...while 迴圈、for each 迴圈
  4. 跳轉語句:break 語句、continue 語句、return 語句

兩個需要注意的地方

  1. 在 Java 中 for each 迴圈體語句序列中,陣列或集合的元素是隻讀的,其值不能改變,若要改變可用常規 for 迴圈.
  2. 應該強制在使用 for 迴圈的時候加上 {}。下面的程式示例:

假如有一段程式:

for (int i=0;i<10;i++) int k = 5;
for (int i=0;i<10;i++) {int k = 5;}

上面的兩種寫法:第一種是錯誤的,for 迴圈可以不加 {},但是那僅限於執行語句,不能包含變數宣告語句。第一段程式碼定義了 k,它的作用域是包含 for 迴圈的整個方法,因此會造成重複定義的編譯錯誤。

foreach

foreach 迴圈使用了迭代器設計模式,它的基本使用方法:如果想要自己的資料集合能夠使用 foreach 的方式遍歷集合的元素,那麼我們需要自己的容器實現 Iterable 介面。下面是在為揹包變成可迭代的列子:

public class Bag<E> implements Iterable<E> {
    private Node<E> first;

    private static class Node<E> {
        E element;
        Node<E> next;

        Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    public void add(E element) {
        first = new Node<E>(element, first);
    }

    public Iterator<E> iterator() {
        return new ListIterator();
    }

    private class ListIterator implements Iterator<E> {
        private Node<E> current = first;

        @Override
        public boolean hasNext() {
            return current != null;
        }

        @Override
        public E next() {
            E element = current.element;
            current = current.next;
            return element;
        }

        @Override
        public void remove() {}
    }
}

4.2 異常控制結構

異常為我們提供了一種解決問題的機制,有了它我們就可以實現將業務邏輯和異常處理相分離,從而使我們的程式碼更加易於維護和理解。

Java 異常的結構層次

Java異常的結構層次

  1. 所有的異常類是從 java.lang.Exception 類繼承的子類;
  2. Exception 類是 Throwable 類的子類。除了 Exception 類外,Throwable 還有一個子類 Error,用來指示執行時環境發生的錯誤,我們最好不要在程式中定義 Error 的子類;
  3. Java 程式通常不捕獲錯誤。錯誤一般發生在嚴重故障時,它們在 Java 程式處理的範疇之外;
  4. 異常類有兩個主要的子類:IOException 類和 RuntimeException 類;
  5. Java 提供了三種可丟擲結構:受檢異常、執行時異常和錯誤;
  6. 每個丟擲的異常都要有文件。

對於使用受檢的異常還是未受檢的異常的原則:對於可恢復的情況,使用受檢的異常;對於程式錯誤,使用執行時異常。

執行時異常(可以參考上圖)通常意味著程式不符合API規範,繼續執行下去也無濟於事。而受檢異常則意味著,對出現了異常進行正確的處理之後,程式仍然能夠繼續執行。
不過這只是一種規範,但實際上不論執行時異常還是受檢異常都是可以捕獲並處理的。

異常處理程式的結構

try {
    int k = 1/0;
} catch (ArithmeticException e) {
    e.printStackTrace();
} finally {
    System.out.println("finally");
}
  1. 異常處理系統會按照“就近”原則匹配異常,如果滿足了匹配就不再繼續向下匹配。因此,在 catch 語句中捕獲異常的時候,要按照從子類到基類的順序捕獲(從具體錯誤到頂層錯誤)。
  2. 異常鏈返回finally 語句總是會執行,所以在一個方法中可以從多個點返回,卻仍然能夠保證清理工作的進行。

一個異常終止的示例程式:

public static void main(String ...args) {
    for (int i=1; i>=0; i--) {
        System.out.println(f(i));;
    }
}

private static int f(int i) {
    try {
        int k = 1 / i;
        return 0;
    } catch (ArithmeticException e) {
        System.out.println("catch");
        return 1;
    } finally {
        try {
            Thread.sleep(1000);
            System.out.println("finally");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

以上程式的輸出結果是:

finally
0
catch
finally
1

可見,finally 總是會被呼叫,而且 return 語句要等待 finally 執行完畢才會呼叫。

異常使用指南

建議使用異常的情況:

  1. 異常應該只用於異常的情況;它們永遠不應該用於控制正常的控制流。
  2. 如果在 try 語句塊中已經出現了異常,而我們在 finally 語句塊中進行異常處理的時候又丟擲了異常,或者使用了 return 語句,那麼這個時候我們的第一個丟擲的異常會丟失。
    丟失的後果就是當程式出現了錯誤的時候,我們沒有辦法對錯誤發生的位置進行定位。
  3. 在恰當的級別處理問題(知道該如何處理的情況下捕獲異常);
  4. 解決問題並且重新呼叫產生異常的方法;
  5. 進行少許修補,然後繞過異常發生的地方繼續執行;
  6. 用別的資料進行計算,以代替方法預計會返回的值;
  7. 把當前執行環境下能做的事情儘量做完,然後把相同的異常拋到高層;
  8. 把當前執行環境下能做的事情儘量做完,然後把不同的異常拋到高層;
  9. 終止程式;
  10. 進行簡化;
  11. 讓類庫和程式更安全。

5、運算子

5.1 運算子(基本同C,特別說明幾個)

No 運算子 說明
1 自增(++)/自減(--)運算子 注意它本質上是分3步執行的(所以非原子的),運算子放在前面和後面會有區別
2 算術運算子(+、-、*、/) 當參與 / 運算的兩個運算元都是表示整數除法,否則是浮點數除法
3 比較運輸符(>、<、>=、<=、!=、==) 參考總結1
4 字串運算子(+) 運算子用於串聯兩個字串,對非字串型別的通過呼叫從Object類繼承的toString()方法將其轉換為字串形式
5 賦值運算子(+= -= *= /= %= <<= >>= &= &#124;= ^=) 要求右邊的運算元可以隱式地轉換為左邊的運算元或型別相同

5.2 總結1:關於比較運算子

No 總結
1 形如 a<b<c 的複合比較式要寫成 (a<b)&&(b<c) 而不能是 a<b<c
2 簡單資料型別比較的是它的值
3 引用型別比較的是兩個引用是否指向同一物件例項,即是否指向同一記憶體位置
4 引用型別的比較中,參與比較的兩個引用型別必須能夠轉換為同一引用型別
5 null 常量表示非空物件,可以與任何引用型別變數比較
6 如果參與比較的兩個運算元,一個為常量,則採用值比較方式,而非引用地址
7 引用型別的比較可以使用對應類的 equals(Object obj) 方法實現
8 比較運算子具有截斷性,如果前面的能夠得出表示式的結果就不再繼續比較了,比如 if(obj != null && ebj.equals(obj2)),這裡如果 ebj 是 null 的話是不會進行 equals 比較的
9 equals() 方法預設比較引用,要想實現自己的比較邏輯必須覆寫該方法。同時要注意 equals()hashCode() 方法需要同時覆寫
10 對浮點數的比較是非常嚴格的,即使一個數僅在小數部分與另一個數存在極微小的差異,仍然認為它們是不相等的。即使一個數比零大一點點,它仍然是非零的值

5.3 總結2:關於字串運算子

  1. 錯誤的例子 String s0 = true + false + “abc”; // 錯誤(true+false)運算錯誤
  2. 對於字串,+ 運算是每次建立一個 StringBuilder 物件,並呼叫它的 append 方法新增字串,所以使用StringBuilder 比使用 + 的效率更高

5.4 總結3:關於移位

>>>>> 的區別:

System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));         // 輸出結果:1111111111111111111111111111111
System.out.println(Integer.toBinaryString(Integer.MAX_VALUE >> 10));   // 輸出結果:111111111111111111111
System.out.println(Integer.toBinaryString(Integer.MAX_VALUE >>> 10));  // 輸出結果:111111111111111111111
System.out.println(Integer.toBinaryString(-1));                        // 輸出結果:11111111111111111111111111111111
System.out.println(Integer.toBinaryString(-1 >> 10));                  // 輸出結果:11111111111111111111111111111111
System.out.println(Integer.toBinaryString(-1 >>> 10));                 // 輸出結果:1111111111111111111111
System.out.println(-1 >> 1);                                           // 輸出結果:-1
System.out.println(-1 >>> 1);                                          // 輸出結果:4194303
  1. 32>>32 相當於 32>>0,而 32>>33 相當於32>>1
  2. 左移 (<<) 能按照操作符左側指定的位數將操作符左邊的運算元向左移動(在低位補0);
  3. “有符號”右移操作符(>>)按照操作符右側指定的位數將從操作符左邊的運算元向右移動;
  4. “有符號”右移操作符使用 “符號拓展”:若符號位為證,則在高位插入0,若符號位為負,則在高位插入1;
  5. “無符號”右移操作符(>>>),無論正負都在高位插入0.

5.5 問題

5.5.1 問題1:以下程式的輸出結果是?

int j = 0;
for (int i = 0; i < 100; i++) {
    j = j++;
}
System.out.println(j);

輸出結果是 0,這是因為 j=j++ 操作等價於 int temp=j; j=j+1; j=temp;

5.5.2 問題2: 以下兩個表示式,哪個正確?

short s = 1; s = s + 1;
short s = 1; s += 1;

第二個,因為 s+1 為 int 型,不能賦值給 short 型別。第二個表示式中會被強制轉型為 short 型別。

5.5.3 問題3:以下程式的輸出結果是

char x = 'x';
int i = 10;
System.out.println(false ? i : x);
System.out.println(false ? 10 : x);

120 和 x,這是因為第一個輸出中,i 為 int 型別,第一個輸出被提升為 int 型;第二個輸出中,10 是常量,常量 10 可以被 char 表示,故輸出 char 型。

6、基本資料型別

  1. Java中的資料型別分成:引用型別和簡單型別 兩個大類;
  2. 引用型別包括:類、介面、陣列和 null 型別
  3. 簡單型別包括:數值型別和布林型別

6.1 整數

6.1.1 取值範圍

Java 中整型的範圍與執行 Java 的機器無關。它只有有符號的整數,沒有無符號的。下面是它們的範圍:

資料型別 位元組 範圍
byte 1 位元組 -128 ~ 127
short 2 位元組 -32768 ~ 32767 (正負3萬)
int 4 位元組 -2 147 483 648 ~ 2 147 483 647 (正負20億)
long 8 位元組 -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807

其他:

No 總結
1 若要表示 long 型別需要在數字後加 L 或 l,同樣的還有 float 和 double
2 二進位制前加 0b,八進位制前加 0,十六進位制前加 0x 或 0X
3 Java 中沒有 unsigned 型別
4 1_000_000 即一百萬,可以加下劃線,使數字更易讀,沒有實際意義

6.1.2 整數的取值範圍與二進位制表示

Java 中用補碼錶示二進位制數,補碼的最高位是符號位,最高位為 “0” 表示正數,最高位為 “1” 表示負數。

正數補碼為其本身;負數補碼為其絕對值各位取反加 1. 例如:

+21,其二進位制表示形式是 00010101,則其補碼同樣為 00010101
-21,按照概念其絕對值為 00010101,各位取反為 11101010,再加 1 為 11101011,即 -21 的二進位制表示形式為 11101011

計算個整數型別的取值範圍的步驟:

  • Step 1:byte 為一位元組 8 位,最高位是符號位,即最大值是 01111111,因正數的補碼是其本身,即此正數為 01111111,十進位制表示形式為 127
  • Step 2:最大正數是 01111111,那麼最小負是 10000000 (最大的負數是 11111111,即 -1)
  • Step 3:10000000 是最小負數的補碼錶示形式,我們把補碼計算步驟倒過來就即可。10000000 減 1 得01111111 然後取反 10000000
  • Step 4:因為負數的補碼是其絕對值取反,即 10000000 為最小負數的絕對值,而 10000000 的十進位制表示是 128,所以最小負數是 -128
  • Step 4:由此可以得出byte的取值範圍是 -128 到 +127
  • 另外,整數型別包裝物件提供了 MAX_VALUEMIN_VALUE 兩個欄位,表示指定型別的最大值和最小值,還有從字串中獲取整數的方法 valueOf(String)

6.2 浮點數

6.2.1 float 和 double

float

  • float 資料型別是單精度、32位、符合 IEEE 754 標準的浮點數
  • 浮點數不能用來表示精確的值,如貨幣
  • 例子:float f1 = 234.5f,末尾要加 f

double

  • double 資料型別是雙精度、64 位、符合 IEEE 754 標準的浮點數
  • 浮點數的預設型別為 double 型別
  • double 型別同樣不能表示精確的值,如貨幣
  • 例子:double d1 = 123.4

6.2.2 浮點數的儲存

浮點數的儲存

也就是

一個float型別 = 1bit(符號位)+ 8bits(指數位)+ 23bits(尾數位)
一個double包括 = 1bit(符號位)+ 11bits(指數位)+ 52bits(尾數位)  

於是,float 的指數範圍為 -128~+127,而 double 的指數範圍為 -1024~+1023,並且指數位是按補碼的形式來劃分的(和上面的 byte 一樣)。因此,float 的範圍為 -2^128 ~ +2^127,也即 -3.40E+38 ~ +3.40E+38;double 的範圍為 -2^1024 ~ +2^1023,也即 -1.79E+308 ~ +1.79E+308。

6.2.3 精度問題

float 和 double 的精度是由尾數的位數來決定的。浮點數在記憶體中是按科學計數法來儲存的,其整數部分始終是一個隱含著的“1”,由於它是不變的,故不能對精度造成影響。

float:2^23 = 8388608,一共七位,由於最左為 1 的一位省略了,這意味著最多能表示 8 位數: 2*8388608 = 16777216 。有 8 位有效數字,但絕對能保證的為 7 位,也即 float 的精度為 7~8 位有效數字;double:2^52 = 4503599627370496,一共 16 位,同理,double 的精度為 16~17 位。

在使用浮點型別的時候很容易出現意想不到的結果(瞭解更多),所以在精度要求比較高的時候建議使用 BigDecimal.

6.3 布林型別

No 總結
1 boolean 資料型別表示一位的資訊
2 這種型別只作為一種標誌來記錄 true/false 情況
3 只有兩個取值:true 和 false
4 預設值是 false
5 Java 中 0 不能代表 false,1 也不能代表 true

6.4 字元型別

No 總結
1 char 型別是一個單一的 16 位 Unicode 字元(雙位元組)
2 最小值是 \u0000(即為 0)
3 最大值是 \uffff(即為 65,535)
4 char 資料型別可以儲存任何字元

6.5 裝箱

  1. Java中存在8種簡單型別:boolean, byte, short, int, long, float和double.
  2. 對應的有8種包裝類:Boolean, Byte, Character, Short, Integer, Long, Float和Double.
  3. Java 無論裝箱和拆箱都存在顯示和隱式轉換兩種方式.
  4. 雖然很多場合下基本資料型別和它的包裝類能達到相同的效果,但是使用包裝類對基本資料型別進行包裝會有額外的開銷。所以,能不使用包裝類時,儘量使用基本資料型別。

示例:

public static void main(String[] args) {
	int var1=10;
	Integer obj1=var1;  			// 隱式裝箱
	Integer obj2=(Integer)var1;  	// 顯示裝箱
	int var2=obj1;  				// 隱式拆箱
	int var3=(int)obj2;  			// 顯式拆箱
}

6.6 型別轉換

型別轉換

  1. 自動型別轉換(隱式轉換):隱式轉換隻允許發生在從小的值範圍型別到大的值範圍型別的轉換,轉換後數值大小不受影響. 但可能會導致精度降低.
  2. 強制型別轉換(顯式轉換)

關於強制型別轉換的總結:

No 總結
1 強制型別轉換有時候會發生截斷,比如 300 轉換為 byte 型別會得到 44
2 不存在到 char 型別的隱式轉換
3 布林型別不允許進行任何的型別轉換
4 整數+浮點數時,浮點數為 double 則另一個是double,浮點數為 float 則另一個是 float,整數是long 則另一個是 long,否則兩個都是 int
5 上面的轉換圖實線箭頭表示無資訊丟失的轉換,虛線箭頭表示可能有精度損失的轉換。可見,從位元組小的到位元組大的沒有損失,從位元組大的到位元組小的會有精度損失
6 將 float 和 double 轉換成整數時,對該數字進行截尾,即捨棄小數部分,0.7 將會被轉換成 0
7 表示式中最大的資料型別決定了表示式的結果,如果將 float 與 double 相乘,結果就是 double 如果將 int 和 long 相乘,結果就是 long

6.7 值型別和引用型別

值型別和引用型別的區別的示例程式碼:

public static void main(String ...args) {
    ValueHolder holderPositive = new ValueHolder(1);
    ValueHolder holderNegative = new ValueHolder(-1);
	
    swap(holderNegative, holderPositive); // 交換引用,無法達到交換效果
    System.out.println("holderPositive.value=" + holderPositive.value + ", " + "holderNegative.value=" + holderNegative.value);
    
    swapValue(holderNegative, holderPositive); // 交換引用的欄位,可以達到交換效果
    System.out.println("holderPositive.value=" + holderPositive.value + ", " + "holderNegative.value=" + holderNegative.value);
    
    int positive = 1, negative = -1;
    swap(positive, negative); // 交換值型別的值,無法達到交換效果
    System.out.println("positive=" + positive + ", " + "negative=" + negative);
    
    Integer objPos = 1, objNeg = -1;
    swap(objPos, objNeg); // 使用整數型別的包裝型別來交換,無法達到交換效果
    System.out.println("objPos=" + objPos + ", " + "objNeg=" + objNeg);

    System.out.println("------------------------");
    positive = negative;
    positive = 2; // 修改一個值型別不會影響另一個
    System.out.println("positive=" + positive + ", " + "negative=" + negative);
	
    holderPositive = holderNegative;
    holderPositive.value = 2; // 修改一個引用會影響到另一個
    System.out.println("holderPositive.value=" + holderPositive.value + ", " + "holderNegative.value=" + holderNegative.value);
}

/**
 * 交換兩個引用型別的欄位:對各個引用的欄位進行了修改(相當於修改了記憶體塊上的記錄) 
 */
private static void swapValue(ValueHolder holder1, ValueHolder holder2) {
    int value = holder1.value;
    holder1.value = holder2.value;
    holder2.value = value;
}

/**
 * 交換兩個引用型別的引用:僅僅交換了傳入的應用的副本(副本指向了其他地方) 
 */
private static void swap(ValueHolder holder1, ValueHolder holder2) {
    ValueHolder value = new ValueHolder(holder1.value);
    holder1 = holder2;
    holder2 = value;
}

/**
 * 使用整數型別的包裝型別來進行交換:這裡相當於重新裝箱,不會修改原始物件的欄位(副本指向了其他地方) 
 */
private static void swap(Integer integer1, Integer integer2) {
    int value = integer1.intValue();
    integer1 = integer2.intValue();
    integer2 = value;
}

/**
 * 交換兩個值型別:交換的只是副本的值,原來的值不會變化
  */
private static void swap(int positive, int negative) {
    int value = positive;
    positive = negative;
    negative = value;
}

public class ValueHolder {
    public int value;

    public ValueHolder(int value) {
        this.value = value;
    }
}

輸出結果:

holderPositive.value=1, holderNegative.value=-1
holderPositive.value=-1, holderNegative.value=1
positive=1, negative=-1
objPos=1, objNeg=-1
------------------------
positive=2, negative=-1
holderPositive.value=2, holderNegative.value=2

結論

No 結論
1 在 Java 中對基本資料型別,Java 傳遞值的副本;對一切引用型別,Java 都傳遞引用的副本
2 對於引用型別,兩個變數可能引用同一物件,因此對一個變數的操作可能影響另一個變數所引用的物件