1. Java基本語法
目錄主要來自於《尚矽谷Java教程》
變數和運算子
關鍵字和保留字
-
關鍵字(keyword)是被Java賦予了特殊含義、用作特殊用途的字串,全部為小寫
class
,boolean
和用於定義流程控制的switch
,while
等。 -
保留字(reserved word)在現有Java版本尚未使用,但以後版本可能會作為關鍵字使用,需要在命名識別符號時避免使用,包括:
goto
,const
。
識別符號
識別符號(identifier)是對變數、方法和類等命名時使用的字元序列。
-
由26個英文字母、0-9、
_
或$
組成。 -
嚴格區分大小寫。
-
不能以數字開頭、不能包含空格。
命名規範:
- 包名:多單詞的所有字母都小寫,
helloworldtest
。 - 類名、介面名:所有單詞首字母大寫,
HelloWorldTest
。 - 變數名、方法名:第一個單詞首字母小寫,其餘單詞首字母大寫,
helloWorldTest
- 常量名:所有字母大寫,多單詞時用下劃線連線,
HELLO_WORLD_TEST
。
變數
變數用於在記憶體中儲存資料,概念:
- 記憶體中的一個儲存區域。
- 該區域的資料可以在同一類型範圍內不斷變化,Java屬於強型別語言。
- 變數是程式中最基本的資料單元,包含變數型別、變數名和儲存的值。
需要注意:
- 變數的作用域:其定義所在的一對{}內。
資料型別
按照資料型別可以分為:
- 基本資料型別(primitive type):整數(
byte
,short
,int
,long
)、浮點(float
,double
)、字元(char
)、布林(boolean
)。 - 引用資料型別(reference type):類(
class
interface
)、陣列([]
)。
按照宣告位置的不同可以分為:
- 成員變數:在方法體外,類體內宣告的變數,分為例項變數與類變數(以
static
修飾)。 - 區域性變數:在方法體內部宣告的變數,包括形參(方法、構造器中的變數)、方法區域性變數(在方法內定義)、程式碼塊區域性變數(在程式碼塊定義)。
具體內容參考面向物件。
基礎資料型別
整數型別
型別 | 佔用空間 | 範圍 |
---|---|---|
byte |
1 byte = 8 bit | \(-128\) ~ \(127\) |
short |
2 bytes | \(-2^{15}\) ~ \(2^{15}-1\) |
int |
4 bytes | \(-2^{31}\) ~ \(2^{31}-1\) |
long |
8 bytes | \(-2^{63}\) ~ \(2^{63}-1\) |
主要注意,Java各個整數型別有固定的欄位長度和表達範圍,不受OS影響,以保證可移植性。
- Java的整數型別預設為
int
。 - 宣告
long
型別時,如果數字大小超過int
的範圍,數字後須加l
或者L
,這是因為Java預設把數字當成int
型別來處理。
浮點型別
型別 | 佔用空間 | 範圍 |
---|---|---|
單精度float |
4 bytes | \(-3.403\times 10^{38}\) ~ \(3.403\times 10^{38}\) |
雙精度double |
8 bytes | \(-1.798\times 10^{308}\) ~ \(1.798\times 10^{308}\) |
Java的浮點型別常量預設為double
,宣告float
型別,需要加f
或F
。浮點型常量有兩種表現形式:
- 十進位制:如5.12, 512.0f, .512。
- 科學計數法:5.12e2, 512E2, 100E-2。
字元型別
char
型別用來表示通常意義上的字元,佔用2 bytes。
- Java中的所有字元都使用Unicode編碼,故一個字元可以儲存一個字母或一個漢字。
- 可以直接用Unicode編碼來表示字元常量:
\u000a
表示\n
,其中000a
為十六進位制整數。 char
型別可以運算,因為它有對應的Unicode碼。
布林型別
boolean
只能取ture
和false
。
String資料型別
String
屬於引用資料型別,宣告時使用一對""
。
String
型別可以和8種基本資料型別變數做運算,且只能是連線運算+
,運算的結果仍然是String
。
不能通過強制型別轉換將String
轉換為其他基本資料型別。
String str1 = 123 + "";
// int num1 = (int) str1; // 編譯不通過
int num1 = Integer.parseInt(str1); // 123
字串連線:
char c = 'a';
int num = 10;
String str = "hello";
System.out.println(c + num + str); // output: 107hello
System.out.println(c + str + num); // output: ahello10
System.out.println(c + (num + str)); // output: a10hello
System.out.println(str + num + c); // output: hello10a
型別轉換
只討論7種基本資料型別變數間的運算,不包含boolean
。
自動型別提升
- 當容量小的資料型別變數轉換為容量大的資料型別的變數時,結果自動提升為容量大的資料型別。
- 順序表示為:
byte
/short
/char
<int
<long
<float
<double
,這裡的容量指表示資料範圍的大小而不是佔用空間的大小,例如:long
佔用8個位元組,float
佔用4個位元組,但是float
的表示範圍比long
更大。
short s1 = 123;
double b1 = s1;
System.out.println(d1); // output: 123.0
- 特別地,當
byte
/short
/char
這3種類型做運算時,結果為int
型,包括相同種類(如2個byte
變數相加)。
byte b1 = 1;
short s1 = 10;
// short s3 = b1 + s1; // 編譯不通過
int i1 = b1 + s1;
強制型別轉換
將容量大的資料型別變數強制賦值給容量小的資料變數,可能會導致精度損失。
// 沒有精度損失
long l1 = 123;
short s1 = (short) l1; // 123
// 精度損失,截斷
double d1 = 12.9;
int i1 = (int) d1; // 12
// 精度損失,溢位
int i2 = 128;
byte b = (byte) i2; // -128
進位制轉換
對於整數型別,有4種表達方式:
- 二進位制(binary):以
0b
或0B
開頭。 - 十進位制(decimal):0-9。
- 八進位制(octal):0-7,以
0
開頭。 - 十六進位制(hex):以
0x
或0X
開頭,A-F不區分大小寫,0x21af + 1 == 0x21b0
。
二進位制
Java整數常量預設是int
型別,當用二進位制定義整數時,第32位是符號位。正數的原碼、補碼、反碼相同,負數的補碼為其反碼+1。 計算機底層都以補碼的方式儲存資料。
- 原碼:直接將一個數值轉換為二進位制,最高位為符號位。
- 負數的反碼:符號位為1,其餘對原碼按位取反。
- 負數的補碼:其反碼+1。
運算子
算數運算子
取模運算
結果的符號與被模數相同。
int m1 = 12;
int n1 = 5;
System.out.println(m1 % n1); // 2
int m2 = -12;
int n2 = 5;
System.out.println(m2 % n2); // -2
int m3 = 12;
int n3 = -5;
System.out.println(m3 % n3); // 2
int m4 = -12;
int n4 = -5;
System.out.println(m4 % n4); // -2
自增/自減運算子
自增/自減運算子不會改變變數的資料型別。
short s1 = 100;
s1 -= 1;
s1--;
// s1 = s1 - 1; // 編譯失敗,需要強制型別轉換
System.out.println(s1); // 98
賦值運算子
與自增運算子類似,+=
, -=
, *=
, /=
等賦值運算子不會改變資料型別。
比較運算子
比較運算子的結果都是boolean
型別,包括==
, !=
, <
, >
, <=
, >=
, instanceof
,其中instanceof
用來檢查是否是類的物件,用法如下:
System.out.println("Hello" instanceof String); // ture
邏輯運算子
運算子 | 含義 |
---|---|
& |
邏輯與 |
&& |
短路與 |
| |
邏輯或 |
|| |
短路或 |
! |
邏輯非 |
^ |
邏輯異或 |
位運算子
注意,沒有<<<
運算子。
運算子 | 含義 | 示例 |
---|---|---|
<< |
左移 | 3 << 2 => 12 |
>> |
右移 | 3 >> 1 => 1 |
>>> |
無符號右移 | 3 >>> 1 => 1 |
& |
與運算 | 6 & 3 => 2 |
| |
或運算 | 6 | 3 => 7 |
^ |
異或運算 | 6 ^ 3 => 5 |
~ |
取反運算 | ~6 => -7 |
- 位運算子操作的都是整數型別的資料。
<<
:空位補0,被移除的高位丟棄,在一定範圍內,每向左移動1位,相當於* 2
。>>
:正數右移後空缺位補0,負數補1,在一定範圍內,每向右移動1位,相當於/ 2
。>>>
:最高位無論是1還是0,右移空缺位都補0。~
:按照補碼,各位取反。
2 << 3
和 8 << 1
的複雜度是\(O(1)\),比2 * 8
效率更高。
三元運算子
結構:(條件表示式) ? 表示式1 : 表示式2
。
- 三元表示式可以改寫為
if-else
語句,反之則不一定,參見下一條。 表示式1
和表示式2
要求是一致的,例如(m > n) ? 2 : "n is bigger"
編譯不通過。
程式流程控制
使用Scanner從鍵盤獲取資料
- 導包:
import java.util.Scanner;
- 例項化
Scanner
類。 - 呼叫
next()
等方法讀取資料。
import java.util.Scanner;
public class HelloWorld{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int num = scan.nextInt();
String str = scan.next();
System.out.println(num + str);
}
}
分支結構
除了if-else
結構外,有關switch-case
結構需要注意:
switch
中的表示式只能是這6種資料型別之一:byte
、short
、char
、int
、列舉(JDK5.0新增)、String
(JDK7.0新增)。case
之後只能宣告常量,不能聲明範圍。default
結構是可選的,而且位置靈活。
另外switch-case
結構比if-else
結構效率稍高。
迴圈結構
帶標籤的break和continue
label: for(int i = 1; i <= 4; i++){
for(int j = 1; j <= 10; j++){
System.out.println(j);
if (j % 4 == 0){
// continue; // 結束包裹此關鍵字最近的一層迴圈結構
continue label; // 結束指定標籤的一層迴圈結構
}
}
}
陣列
概述
陣列(array)是多個相同型別資料按一定順序排列的集合。
- 有序排列。
- 陣列屬於引用資料型別的變數。
- 建立陣列物件會在記憶體中開闢一整塊連續的空間。
- 陣列的長度一旦確定就不能修改。
- 分類:按照維度分為一維陣列和多維陣列;按照資料型別分為基本資料型別元素的陣列和引用資料型別元素的陣列。
一維陣列
初始化
陣列一旦初始化完成,其長度就確定了。
// 靜態初始化:陣列的初始化和陣列元素的賦值操作同時進行
int[] ids = new int[] {1001, 1002, 1003, 1004};
// 動態初始化:陣列的初始化和元素賦值分開進行
String[] names = new String[5];
names[0] = "Wang";
// 獲取陣列長度
System.out.println(ids.length + " " + names.length);
預設初始化值
- 整型:0
- 浮點型:0.0
char
型:0或\u0000
,而非'0'
boolean
型:false
- 引用資料型別:
null
記憶體結構的簡單說明
JVM中的記憶體結構簡單分為:
- 棧(stack):區域性變數,如上面程式碼中的變數
ids
和names
(僅存放陣列的地址)。 - 堆(heap):
new
出來的物件、陣列等,如上述程式碼中ids
和names
對應陣列開闢的連續空間。 - 方法區:常量池、靜態域等。
多維陣列
二維陣列
初始化方法如下:
// 靜態初始化:陣列的初始化和陣列元素的賦值操作同時進行
int[][] arr1 = new int[][] {{1, 2, 3}, {4, 5}, {6}};
// 動態初始化:陣列的初始化和元素賦值分開進行
String[][] arr2 = new String[5][3];
String[][] arr3 = new String[6][];
// 也是正確的寫法
int[][] arr4 = {{1, 2, 3}, {4, 5}, {6}}; // 型別推斷
int arr5[][] = new int[][] {{1, 2, 3}, {4, 5}, {6}};
預設初始化值
針對動態初始化方法一,例如int[][] arr = new int[3][4];
,則初始化值為
- 外層元素:地址值。
- 內層元素:與一維陣列相同。
int[][] arr = new int[3][4];
System.out.println(arr[0]); // 地址值:[I@28d93b30
System.out.println(arr[0][0]); // 0
針對動態初始化方法二,例如int[][] arr = new int[3][];
,則初始化值為
- 外層元素:
null
。 - 內層元素:不能呼叫,否則報錯。
常見演算法
隨機生成一個兩位數
(int) (Math.random() * (99 - 10 + 1) + 10);
/*
* Math.random() => [0.0, 1.0)
* Math.random() * 90 => [0.0, 90.0)
* (int) (Math.random() * 90) => [0, 89]
* (int) (Math.random() * 90 + 10) => [10, 99]
*/
陣列的反轉
在空間複雜度為常數級的情況下,反轉陣列。主要思想為交換頭尾元素。
// 隨機生成20個兩位數
int[] arr = new int[20];
for(int i = 0; i < arr.length; i++){
arr[i] = (int) (Math.random() * 90 + 10);
System.out.print(arr[i] + " ");
}
System.out.println();
// reverse 1
/*
for (int i = 0; i < arr.length / 2; i++){
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
*/
// reverse 2: 雙指標交換,實際操作上與方法一相同
for (int i = 0, j = arr.length - 1; i < j; i++, j--){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
二分查詢
// 二分查詢(必須是有序的)
int [] arr2 = new int[] {-98, -34, 2, 34, 54, 66, 79, 105, 210, 333};
int head = 0; // 初始前索引
int end = arr2.length - 1; // 初始後索引
boolean isFound = false; // 是否被找到
int target = 210; // 查詢的目標值
while (head < end){
int middle = (head + end) / 2;
if (target == arr2[middle]){
System.out.println("Find " + target + ", index = " + middle + ".");
isFound = true;
break;
}
else if (target < arr2[middle]){
end = middle - 1;
}
else {
head = middle + 1;
}
}
if (!isFound){
System.out.println("Can't find " + target + ".");
}
排序演算法
衡量排序演算法:
- 時間複雜度:關鍵字比較次數和記錄的移動次數。
- 空間複雜度:演算法需要多少輔助記憶體。
- 穩定性:若兩個記錄A和B的關鍵字值相等,排序後A、B的先後次序保持不變,則稱演算法是穩定的。
氣泡排序
for (int i = 0; i < arr.length - 1; i++){
for (int j = 0; j < arr.length - 1 - i; j++){
if (arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
快速排序
public class QuickSort {
// 交換陣列的兩個索引上的值
private static void swap(int[] data, int i, int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
// 子方法
private static void subSort(int[] data, int start, int end){
if (start < end){
int base = data[start];
int low = start;
int high = end + 1;
while (true){
// 通過兩個while迴圈,向右移動low,向左移動high
// 直到找到大於base的low和小於base的high
while (low < end && data[++low] <= base);
while (high > start && data[--high] >= base);
// low和high沒有碰面,需要交換,迴圈繼續
if (low < high){
swap(data, low, high);
}
else {
break;
}
}
// 將base移到中間,base左邊全部是比它小的數字,右邊全部是比它大的數字
swap(data, start, high);
// 遞迴地對base左半部分和右半部分排序
subSort(data, start, high - 1);
subSort(data, high + 1, end);
}
}
// 快速排序
public static void quickSort(int[] data){
subSort(data, 0, data.length - 1);
}
}
Arrays工具類的使用
java.util.Arrays
是運算元組的工具類,裡面定義了很多運算元組的方法。
// 1. 判斷兩個陣列是否相等
int[] arr1 = new int[] {1, 2, 3, 4, 5};
int[] arr2 = new int[] {1, 2, 3, 4, 5};
System.out.println(Arrays.equals(arr1, arr2));
// true
// 2. 輸出陣列
System.out.println(Arrays.toString(arr1));
// [1, 2, 3, 4, 5]
// 3. 將陣列填充為指定值
Arrays.fill(arr1, 10);
System.out.println(Arrays.toString(arr1));
// [10, 10, 10, 10, 10]
// 4. 對陣列排序
int [] arr3 = new int[] {98, 34, 2, 34, 54, -66, -79, 105, -210, 333};
Arrays.sort(arr3);
System.out.println(Arrays.toString(arr3));
// [-210, -79, -66, 2, 34, 34, 54, 98, 105, 333]
// 5. 二分查詢
System.out.println(Arrays.binarySearch(arr3, 105)); // 如果找到,輸出索引
System.out.println(Arrays.binarySearch(arr3, -2)); // 負數表示沒找到
// 8
// -4