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 方法包含以下要素:public 、static 、void 和 String[] args |
2 | 可以使用 new 關鍵字建立類的例項物件,可以直接通過類名呼叫類所提供的靜態成員 |
包
No | 說明 |
---|---|
1 | 包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。【規範】 |
2 | package 語句必須是源程式檔案中第一個非註釋、非空白語句 |
3 | 包的成員可以包含子包、類、介面。如果原始碼中沒有指定包,則使用預設包 |
4 | 一個包(子包)的成員不能重名,即使不同的型別 |
包的訪問方式:
- 完全限定名稱方式訪問:
java.net.InetAddress hostAdd
- 匯入包成員:
import java.net.InetAddress
- 匯入整個包:
import java.net.*
- 匯入類的靜態成員:
import static java.lang.Math.*
- 訪問包成員名稱衝突:需要使用完全限定名稱方式
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 層方法命名規約
- 獲取單個物件的方法用
get
做字首。 - 獲取多個物件的方法用
list
做字首。 - 獲取統計值的方法用
count
做字首。 - 插入的方法用
save
( 推薦 ) 或insert
做字首。 - 刪除的方法用
remove
( 推薦 ) 或delete
做字首。 - 修改的方法用
update
做字首。
4、程式流程
4.1 基本程式結構
- 順序結構
- 選擇結構:
if
語句和switch
語句 - 迴圈結構:
for
迴圈、while
迴圈、do...while
迴圈、for each
迴圈 - 跳轉語句:
break
語句、continue
語句、return
語句
兩個需要注意的地方
- 在 Java 中
for each
迴圈體語句序列中,陣列或集合的元素是隻讀的,其值不能改變,若要改變可用常規for
迴圈. - 應該強制在使用
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.lang.Exception
類繼承的子類; Exception
類是Throwable
類的子類。除了Exception
類外,Throwable
還有一個子類Error
,用來指示執行時環境發生的錯誤,我們最好不要在程式中定義Error
的子類;- Java 程式通常不捕獲錯誤。錯誤一般發生在嚴重故障時,它們在 Java 程式處理的範疇之外;
- 異常類有兩個主要的子類:
IOException
類和RuntimeException
類; - Java 提供了三種可丟擲結構:受檢異常、執行時異常和錯誤;
- 每個丟擲的異常都要有文件。
對於使用受檢的異常還是未受檢的異常的原則:對於可恢復的情況,使用受檢的異常;對於程式錯誤,使用執行時異常。
執行時異常(可以參考上圖)通常意味著程式不符合API規範,繼續執行下去也無濟於事。而受檢異常則意味著,對出現了異常進行正確的處理之後,程式仍然能夠繼續執行。
不過這只是一種規範,但實際上不論執行時異常還是受檢異常都是可以捕獲並處理的。
異常處理程式的結構
try {
int k = 1/0;
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("finally");
}
- 異常處理系統會按照“就近”原則匹配異常,如果滿足了匹配就不再繼續向下匹配。因此,在
catch
語句中捕獲異常的時候,要按照從子類到基類的順序捕獲(從具體錯誤到頂層錯誤)。 - 異常鏈返回:
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
執行完畢才會呼叫。
異常使用指南
建議使用異常的情況:
- 異常應該只用於異常的情況;它們永遠不應該用於控制正常的控制流。
- 如果在
try
語句塊中已經出現了異常,而我們在finally
語句塊中進行異常處理的時候又丟擲了異常,或者使用了return
語句,那麼這個時候我們的第一個丟擲的異常會丟失。
丟失的後果就是當程式出現了錯誤的時候,我們沒有辦法對錯誤發生的位置進行定位。 - 在恰當的級別處理問題(知道該如何處理的情況下捕獲異常);
- 解決問題並且重新呼叫產生異常的方法;
- 進行少許修補,然後繞過異常發生的地方繼續執行;
- 用別的資料進行計算,以代替方法預計會返回的值;
- 把當前執行環境下能做的事情儘量做完,然後把相同的異常拋到高層;
- 把當前執行環境下能做的事情儘量做完,然後把不同的異常拋到高層;
- 終止程式;
- 進行簡化;
- 讓類庫和程式更安全。
5、運算子
5.1 運算子(基本同C,特別說明幾個)
No | 運算子 | 說明 |
---|---|---|
1 | 自增(++ )/自減(-- )運算子 |
注意它本質上是分3步執行的(所以非原子的),運算子放在前面和後面會有區別 |
2 | 算術運算子(+、-、*、/ ) |
當參與 / 運算的兩個運算元都是表示整數除法,否則是浮點數除法 |
3 | 比較運輸符(>、<、>=、<=、!=、== ) |
參考總結1 |
4 | 字串運算子(+ ) |
運算子用於串聯兩個字串,對非字串型別的通過呼叫從Object類繼承的toString()方法將其轉換為字串形式 |
5 | 賦值運算子(+= -= *= /= %= <<= >>= &= |= ^= ) |
要求右邊的運算元可以隱式地轉換為左邊的運算元或型別相同 |
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:關於字串運算子
- 錯誤的例子
String s0 = true + false + “abc”;
// 錯誤(true+false)運算錯誤 - 對於字串,
+
運算是每次建立一個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
32>>32
相當於32>>0
,而32>>33
相當於32>>1
;- 左移 (
<<
) 能按照操作符左側指定的位數將操作符左邊的運算元向左移動(在低位補0); - “有符號”右移操作符(>>)按照操作符右側指定的位數將從操作符左邊的運算元向右移動;
- “有符號”右移操作符使用 “符號拓展”:若符號位為證,則在高位插入0,若符號位為負,則在高位插入1;
- “無符號”右移操作符(
>>>
),無論正負都在高位插入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、基本資料型別
- Java中的資料型別分成:引用型別和簡單型別 兩個大類;
- 引用型別包括:類、介面、陣列和 null 型別;
- 簡單型別包括:數值型別和布林型別。
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_VALUE
和MIN_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 裝箱
- Java中存在8種簡單型別:boolean, byte, short, int, long, float和double.
- 對應的有8種包裝類:Boolean, Byte, Character, Short, Integer, Long, Float和Double.
- Java 無論裝箱和拆箱都存在顯示和隱式轉換兩種方式.
- 雖然很多場合下基本資料型別和它的包裝類能達到相同的效果,但是使用包裝類對基本資料型別進行包裝會有額外的開銷。所以,能不使用包裝類時,儘量使用基本資料型別。
示例:
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 型別轉換
- 自動型別轉換(隱式轉換):隱式轉換隻允許發生在從小的值範圍型別到大的值範圍型別的轉換,轉換後數值大小不受影響. 但可能會導致精度降低.
- 強制型別轉換(顯式轉換)
關於強制型別轉換的總結:
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 | 對於引用型別,兩個變數可能引用同一物件,因此對一個變數的操作可能影響另一個變數所引用的物件 |