【計理01組04號】JDK基礎入門

yyyyfly發表於2022-02-26

java.lang包

java.lang包裝類

我們都知道 java 是一門物件導向的語言,類將方法和屬性封裝起來,這樣就可以建立和處理相同方法和屬性的物件了。但是 java 中的基本資料型別卻不是物件導向的,不能定義基本型別的物件。那如果我們想像處理物件的方式處理基本型別的資料,呼叫一些方法怎麼辦呢?

其實 java 為每個基本型別都提供了包裝類:

原始資料型別 包裝類
byte(位元組) Byte
char(字元) Character
int(整型) Integer
long (長整型) Long
float(浮點型) Float
double (雙精度) Double
boolean (布林) Boolean
short(短整型) Short

在這八個類名中,除了 Integer 和 Character 類以後,其它六個類的類名和基本資料型別一致,只是類名的第一個字母大寫。

 

Integer 類

java.lang 包中的 Integer 類、Long 類和 Short 類都是 Number 的子類,他們的區別在於不同子類裡面封裝著不同的資料型別,比如 Integer 類包裝了一個基本型別 int。其包含的方法基本相同。

我們以 Integer 類為例。 Integer 構造方法有兩種:

  1. Integer(int value),以 int 型變數作為引數建立 Integer 物件。例如Integer a = new Integer(10);
  2. Integer(String s),以 String 型變數作為引數建立 Integer 物件,例如Integer a = new Integer("10")

下面列舉一下 Integer 的常用方法:

方法 返回值 功能描述
byteValue() byte 以 byte 型別返回該 Integer 的值
compareTo(Integer anotherInteger) int 在數字上比較 Integer 物件。如果這兩個值相等,則返回 0;如果呼叫物件的數值小於 anotherInteger 的數值,則返回負值;如果呼叫物件的數值大於 anotherInteger 的數值,則返回正值
equals(Object IntegerObj) boolean 比較此物件與指定物件是否相等
intValue() int 以 int 型返回此 Integer 物件
shortValue() short 以 short 型返回此 Integer 物件
longValue() long 以 long 型返回此 Integer 物件
floatValue() float 以 float 型返回此 Integer 物件
doubleValue() double 以 double 型返回此 Integer 物件
toString() String 返回一個表示該 Integer 值的 String 物件
valueOf(String str) Integer 返回儲存指定的 String 值的 Integer 物件
parseInt(String str) int 將字串引數作為有符號的十進位制整數進行解析

 

Character 類

Character 類在物件中包裝一個基本型別 char 的值。Character 型別的物件包含型別為 char 的單個欄位。

Character 包裝類的常用方法:

方法 返回值 說明
isDigit(char ch) boolean 確定字元是否為數字
isLetter(char ch) boolean 確定字元是否為字母
isLowerCase(char ch) boolean 確定字元是否為小寫字母
isUpperCase(char ch) boolean 確定字元是否為大寫字母
isWhitespace(char ch) boolean 確定字元是否為空白字元
isUnicodeIdentifierStart(char ch) boolean 確定是否允許將指定字元作為 Unicode 識別符號中的首字元

 

Boolean 類

Boolean 類將基本型別為 boolean 的值包裝在一個物件中。一個 Boolean 型別的物件只包含一個型別為 boolean 的欄位。

Boolean 類的構造方法也有兩個:

  1. Boolean(boolean value),建立一個表示 value 引數的 Boolean 物件,如 Boolean b = new Boolean(true)
  2. Boolean(String s),如果 String 引數不為 null 且在忽略大小寫時等於 "true", 建立一個表示 true 值的 Boolean 物件,如 Boolean b = new Boolean("ok"),為 false。

Boolean 包裝類的常用方法:

方法 返回值 說明
booleanValue() boolean 將 Boolean 物件的值以對應的 boolean 值返回
equals(Object obj) boolean 判斷呼叫該方法的物件與 obj 是否相等。當且僅當引數不是 null,而且與呼叫該方法的物件一樣都表示同一個 boolean 值的 Boolean 物件時,才返回 true
parseBoolean(String s) boolean 將字串引數解析為 boolean 值
toString() String 返回表示該 boolean 值的 String 物件
valueOf(String s) Boolean 返回一個用指定得字串表示值的 boolean 值

java.lang類String

計算字串長度

length() 方法

字串比較

equals() 方法,該方法的作用是判斷兩個字串物件的內容是否相同。如果相同則返回 true,否則返回 false。

equals() 方法比較是從第一個字元開始,一個字元一個字元依次比較。

那如果我想忽略掉大小寫關係,比如:java 和 Java 是一樣的,我們怎麼辦呢?我們可以呼叫equalsIgnoreCase()方法,其用法與 equals 一致,不過它會忽視大小寫。

字串連線

字串連線有兩種方法:

  1. 使用+,比如 String s = "Hello " + "World!"
  2. 使用 String 類的 concat() 方法。

而且使用+進行連線,不僅可以連線字串,也可以連線其他型別。但是要求進行連線時至少有一個參與連線的內容是字串型別。

charAt() 方法

charAt() 方法的作用是按照索引值(規定字串中第一個字元的索引值是 0,第二個字元的索引值是 1,依次類推),獲得字串中的指定字元。

字串常用提取方法

方法 返回值 功能描述
indexOf(int ch) int 搜尋字元 ch 第一次出現的索引
indexOf(String value) int 搜尋字串 value 第一次出現的索引
lastIndexOf(int ch) int 搜尋字元 ch 最後一次出現的索引
lastIndexOf(String value) int 搜尋字串 value 最後一次出現的索引
substring(int index) String 提取從位置索引開始到結束的字串
substring(int beginindex, int endindex) String 提取 beginindex 和 endindex 之間的字串部分
trim() String 返回一個前後不含任何空格的呼叫字串的副本

說明:在字串中,第一個字元的索引為 0,子字串包含 beginindex 的字元,但不包含 endindex 的字元。

StringBuffer

String 的不變性的機制顯然會在 String 常量內有大量的冗餘。比如我建立一個迴圈,使字元'1'依次連線到'n',那麼系統就得建立 n+(n-1) 個 String 物件。那有沒有可變的 String 類呢?

StringBuffer 類是可變的。它是 String 的對等類,它可以增加和編寫字元的可變序列,並且能夠將字元插入到字串中間或附加到字串末尾,當然是不用建立其他物件的,這裡建議大家去看一看 String 類與 StringBuffer 類的區別,理解一下他們在記憶體中的儲存情況。

先上 StringBuffer 的構造方法:

構造方法 說明
StringBuffer() 構造一個其中不帶字元的字串緩衝區,其初始容量為 16 個字元
StringBuffer(CharSequence seq) 構造一個字串緩衝區,它包含與指定的 CharSequence 相同的字元
StringBuffer(int capacity) 構造一個不帶字元,但具有指定初始容量的字串緩衝區
StringBuffer(String str) 構造一個字串緩衝區,並將其內容初始化為指定的字串內容

StringBuffer 類的常用方法:

方法 返回值 功能描述
insert(int offsetm,Object s) StringBuffer 在 offsetm 的位置插入字串 s
append(Object s) StringBuffer 在字串末尾追加字串 s
length() int 確定 StringBuffer 物件的長度
setCharAt(int pos,char ch) void 使用 ch 指定的新值設定 pos 指定的位置上的字元
toString() String 轉換為字串形式
reverse() StringBuffer 反轉字串
delete(int start, int end) StringBuffer 刪除呼叫物件中從 start 位置開始直到 end 指定的索引(end-1)位置的字元序列
replace(int start, int end, String s) StringBuffer 使用一組字元替換另一組字元。將用替換字串從 start 指定的位置開始替換,直到 end 指定的位置結束

java.lang類Math

Math 類的簡單使用

我們在程式設計的過程中,經常對一些數字進行數學操作,比如我們想要求絕對值或餘弦什麼的。那這些方法是否需要我們自己實現嗎?其實是 java.lang 裡的 Math 類。Math 類包含用於執行基本數學運算的方法,如初等指數、對數、平方根和三角函式。我們就來學習一下吧。

先認識一些方法吧:

方法 返回值 功能描述
sin(double numvalue) double 計算角 numvalue 的正弦值
cos(double numvalue) double 計算角 numvalue 的餘弦值
acos(double numvalue) double 計算 numvalue 的反餘弦
asin(double numvalue) double 計算 numvalue 的反正弦
atan(double numvalue) double 計算 numvalue 的反正切
pow(double a, double b) double 計算 a 的 b 次方
sqrt(double numvalue) double 計算給定值的正平方根
abs(int numvalue) int 計算 int 型別值 numvalue 的絕對值,也接收 long、float 和 double 型別的引數
ceil(double numvalue) double 返回大於等於 numvalue 的最小整數值
floor(double numvalue) double 返回小於等於 numvalue 的最大整數值
max(int a, int b) int 返回 int 型 a 和 b 中的較大值,也接收 long、float 和 double 型別的引數
min(int a, int b) int 返回 a 和 b 中的較小值,也可接受 long、float 和 double 型別的引數
rint(double numvalue) double 返回最接近 numvalue 的整數值
round(T arg) arg 為 double 時返回 long,為 float 時返回 int 返回最接近 arg 的整數值
random() double 返回帶正號的 double 值,該值大於等於 0.0 且小於 1.0

上面都是一些常用的方法,如果同學們以後還會用到極座標、對數等,就去查一查手冊吧。

java.lang類Class

Class 類的例項表示正在執行的 Java 應用程式中的類或介面。在 Java 中,每個 Class 都有一個相應的 Class 物件,即每一個類,在生成的 .class 檔案中,就會產生一個 Class 物件,用於表示這個類的型別資訊。我們獲取 Class 例項有三種方法:

  1. 利用物件呼叫 getClass() 方法獲取該物件的 Class 例項

  2. 使用 Class 類的靜態方法 forName(String className),用類的名字獲取一個 Class 例項

  3. 運用.class的方式來獲取 Class 例項,對於基本資料型別的封裝類,還可以採用.TYPE來獲取相對應的基本資料型別的 Class 例項

java.lang類Object

Object 類是所有類的父類,所有物件(包括陣列)都實現這個類的方法。所以在預設的情況下,我們定義的類擴充套件自 Object 類,那我們當然可以呼叫和重寫 Object 類裡的所有方法了。

我們看一下 Object 類裡都定義了哪些方法。

方法 返回值 功能描述
equals(Objectobj) boolean 將當前物件例項與給定的物件進行比較,檢查它們是否相等
finalize() throws Throwable void 當垃圾回收器確定不存在物件的更多引用時,由物件的垃圾回收器呼叫此方法。通常被子類重寫
getClass() Class 返回當前物件的 Class 物件
toString() String 返回此物件的字串表示
wait() throws InterruptedException void 在其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法前,使當前執行緒進入等待狀態

日期和隨機數

Date類

Date 類表示日期和時間,裡面封裝了操作日期和時間的方法。Date 類經常用來獲取系統當前時間。

我們來看看類 Date 中定義的未過時的構造方法:

構造方法 說明
Date() 構造一個 Date 物件並對其進行初始化以反映當前時間
Date(long date) 構造一個 Date 物件,並根據相對於 GMT 1970 年 1 月 1 日 00:00:00 的毫秒數對其進行初始化

Calendar類

在早期的 JDK 版本中,Date 類附有兩大功能:

  1. 允許用年、月、日、時、分、秒來解釋日期
  2. 允許對錶示日期的字串進行格式化和句法分析

在 JDK1.1 中提供了類 Calendar 來完成第一種功能,類 DateFormat 來完成第二項功能。DateFormat 是 java.text 包中的一個類。與 Date 類有所不同的是,DateFormat 類可以接受用各種語言和不同習慣表示的日期字串。

但是 Calendar 類是一個抽象類,它完成 Date 類與普通日期表示法之間的轉換,而我們更多的是使用 Calendar 類的子類 GregorianCalendar 類。它實現了世界上普遍使用的公曆系統。當然我們也可以繼承 Calendar 類,然後自己定義實現日曆方法。

先來看一看 GregorianCalendar 類的建構函式:

構造方法 說明
GregorianCalendar() 建立的物件中的相關值被設定成指定時區,預設地點的當前時間,即程式執行時所處的時區、地點的當前時間
GregorianCalendar(TimeZone zone) 建立的物件中的相關值被設定成指定時區 zone,預設地點的當前時間
GregorianCalendar(Locale aLocale) 建立的物件中的相關值被設定成預設時區,指定地點 aLocale 的當前時間
GregorianCalendar(TimeZone zone,Locale aLocale) year - 建立的物件中的相關值被設定成指定時區,指定地點的當前時間

TimeZone 是 java.util 包中的一個類,其中封裝了有關時區的資訊。每一個時區對應一組 ID。類 TimeZone 提供了一些方法完成時區與對應 ID 兩者之間的轉換。

java.time包

因為 java8 之前的日期和時間 api 飽受詬病,比如執行緒安全問題,比如 Date 的月份是從 0 開始的!而 java.time 包中將月份封裝成為了列舉型別。接下來來看看如何使用這個新的時間報。

首先了解一下 LocalTime 類,LocalTime 類是一個不可變類(也就是用 final 修飾的類),和 String 類一樣,所以它是執行緒安全的。除了 LocalTime 還有 LocalDate(日期)、LocalDateTime(日期和時間)等,他們的使用方式都差不多。

Random類

Java 實用工具類庫中的類 java.util.Random 提供了產生各種型別隨機數的方法。它可以產生 int、long、float、double 以及 Gaussian 等型別的隨機數。這也是它與 java.lang.Math 中的方法 random() 最大的不同之處,後者只產生 double 型的隨機數。

構造方法 說明
Random() 產生一個隨機數需要基值,這裡將系統時間作為 seed
Random(long seed) 使用單個 long 種子建立一個新的隨機數生成器

集合框架

Collection介面

因為集合框架中的很多類功能是相似的,所以我們用介面來規範類。Collection 介面是 java 集合框架裡的一個根介面。它也是 List、Set 和 Queue 介面的父介面。Collection 介面中定義了可用於操作 List、Set 和 Queue 的方法——增刪改查。

方法 返回值 說明
add(E e) boolean 向 collection 的尾部追加指定的元素(可選操作)
addAll(Collection<? extend E> c) boolean 將指定 collection 中的所有元素都新增到此 collection 中(可選操作)
clear() void 移除此 collection 中的所有元素(可選操作)
contains(Object o) boolean 如果此 collection 包含指定的元素,則返回 true
containsAll(Collection<?> c) boolean 如果此 collection 包含指定 collection 的所有元素,則返回 true
equals(Object o) boolean 比較此 collection 與指定物件是否相等
hashCode() int 返回此 collection 的雜湊碼值
isEmpty() boolean 如果此 collection 不包含元素,則返回 true
iterator() Iterator 返回在此 collection 的元素上進行迭代的迭代器
remove(Object o) boolean 移除此 collection 中出現的首個指定元素(可選操作)
removeAll(Collection<?> c) boolean 移除此 collection 中那些也包含在指定 collection 中的所有元素(可選操作)
retainAll(Collection<?> c) boolean 僅保留此 collection 中那些也包含在指定 collection 的元素(可選操作)
size() int 返回此 collection 中的元素數
toArray() Object[] 返回包含此 collection 中所有元素的陣列
toArray(T[] a) T[] 返回包含此 collection 中所有元素的陣列;返回陣列的執行時型別與指定陣列的執行時型別相同

Map介面

Map 介面也是一個非常重要的集合介面,用於儲存鍵/值對。Map 中的元素都是成對出現的,鍵值對就像陣列的索引與陣列的內容的關係一樣,將一個鍵對映到一個值的物件。一個對映不能包含重複的鍵;每個鍵最多隻能對映到一個值。我們可以通過鍵去找到相應的值。

value 可以儲存任意型別的物件,我們可以根據 key 鍵快速查詢 value。Map 中的鍵/值對以 Entry 型別的物件例項形式存在。

看一看 Map 中的方法吧

方法 返回值 說明
clear() void 從此對映中移除所用對映關係(可選操作)
containsKey(Object key) boolean 如果此對映包含指定鍵的對映關係,則返回 true
containsValue(Object value) boolean 如果此對映將一個或多個鍵對映到指定值,則返回 true
entrySet() Set<Map.Entry<K,V>> 返回此對映中包含的對映關係的 Set 檢視
equals(Object o) boolean 比較指定的物件與此對映是否相等
get(Object key) V 返回指定鍵所對映的值;如果此對映不包含該鍵的對映關係,則返回 null
hashCode() int 返回此對映的雜湊碼值
isEmpty() boolean 如果此對映未包含鍵-值對映關係,則返回 true
keySet() Set 返回此對映中包含的鍵的 Set 檢視
put(K key, V value) V 將指定的值與此對映中的指定鍵關聯(可選操作)
putAll(Map<? extends K, ? extends V> m) void 從指定對映中將所有對映關係複製到此對映中(可選操作)
remove(Object key) V 如果存在一個鍵的對映關係,則將其從此對映中移除(可選操作)
size int 返回此對映中的鍵-值對映關係數
values() Collection 返回此對映中包含的值的 Collection 檢視

List 介面與 ArrayList 類

List 是一個介面,不能例項化,需要一個具體類來實現例項化。List 集合中的物件按照一定的順序排放,裡面的內容可以重複。List 介面實現的類有:ArrayList(實現動態陣列),Vector(實現動態陣列),LinkedList(實現連結串列),Stack(實現堆疊)。

List 在 Collection 基礎上增加的方法:

方法 返回值 說明
add(int index, E element) void 在列表的指定位置插入指定元素(可選操作)
addAll(int index, Collection<? extends E> c) boolean 將指定 collection 中的所有元素都插入到列表中的指定位置(可選操作)
get(int index) E 返回列表中指定位置的元素
indexOf(Object o) int 返回此列表中第一次出現的指定元素的索引;如果此列表不包含該元素,則返回 -1
lastIndexOf(Object o) int 返回此列表中最後出現的指定元素的索引;如果列表不包含此元素,則返回 -1
listIterator() ListIterator 返回此列表元素的列表迭代器(按適當順序)
listIterator(int index) ListIterator 返回此列表元素的列表迭代器(按適當順序),從列表的指定位置開始
remove(int index) E 移除列表中指定位置的元素(可選操作)
set(int index, E element) E 用指定元素替換列表中指定位置的元素(可選操作)
subList(int fromIndex, int toIndex) List 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之間的部分檢視

今天我們主要來學習 java.util.ArrayList,ArrayList 類實現一個可增長的動態陣列,它可以儲存不同型別的物件,而陣列則只能存放特定資料型別的值。

Set和HashSet

Set 介面也是 Collection 介面的子介面,它有一個很重要也是很常用的實現類——HashSet,Set 是元素無序並且不包含重複元素的 collection(List 可以重複),被稱為集。

HashSet 由雜湊表(實際上是一個 HashMap 例項)支援。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變。

HashMap

HashMap 是基於雜湊表的 Map 介面的一個重要實現類。HashMap 中的 Entry 物件是無序排列的,Key 值和 value 值都可以為 null,但是一個 HashMap 只能有一個 key 值為 null 的對映(key 值不可重複)。

位元組流

I/O 流

大部分程式都需要進行輸入/輸出處理,比如從鍵盤讀取資料、從螢幕中輸出資料、從檔案中寫資料等等。在 Java 中,把這些不同型別的輸入、輸出源抽象為流(Stream),而其中輸入或輸出的資料則稱為資料流(Data Stream),用統一的介面表示,從而使程式設計簡單明瞭。

【計理01組04號】JDK基礎入門

流是一組有順序的,有起點和終點的位元組集合,是對資料傳輸的總稱或抽象。即資料在兩裝置間的傳輸稱為流,流的本質是資料傳輸,根據資料傳輸特性將流抽象為各種類,方便更直觀的進行資料操作。

流一般分為輸入流(Input Stream)和輸出流(Output Stream)兩類,但這種劃分並不是絕對的。比如一個檔案,當向其中寫資料時,它就是一個輸出流;當從其中讀取資料時,它就是一個輸入流。當然,鍵盤只是一個輸入流,而螢幕則只是一個輸出流。(其實我們可以通過一個非常簡單的方法來判斷,只要是向記憶體中寫入就是輸入流,從記憶體中寫出就是輸出流)。

【計理01組04號】JDK基礎入門

位元組流

基類:InputStream 和 OutputStream

位元組流主要操作 byte 型別資料,以 byte 陣列為準,java 中每一種位元組流的基本功能依賴於基本類 InputStream 和 Outputstream,他們是抽象類,不能直接使用。位元組流能處理所有型別的資料(如圖片、avi 等)。

InputStream

InputStream 是所有表示位元組輸入流類的基類,繼承它的子類要重新定義其中所定義的抽象方法。InputStream 是從裝置來源地讀取資料的抽象表示,例如 System 中的標準輸入流 in 物件就是一個 InputStream 型別的例項。

我們先來看看 InputStream 類的方法:

方法

說明

read()throws IOException

從輸入流中讀取資料的下一個位元組(抽象方法)

skip(long n) throws IOException

跳過和丟棄此輸入流中資料的 n 個位元組

available()throws IOException

返回流中可用位元組數

mark(int readlimit)throws IOException

在此輸入流中標記當前的位置

reset()throws IOException

將此流重新定位到最後一次對此輸入流呼叫 mark 方法時的位置

markSupport()throws IOException

測試此輸入流是否支援 mark 和 reset 方法

close()throws IOException

關閉流

在 InputStream 類中,方法 read() 提供了三種從流中讀資料的方法:

  1. int read():從輸入流中讀一個位元組,形成一個 0~255 之間的整數返回(是一個抽象方法)。

  2. int read(byte b[]):從輸入流中讀取一定數量的位元組,並將其儲存在緩衝區陣列 b 中。

  3. int read(byte b[],int off,int len):從輸入流中讀取長度為 len 的資料,寫入陣列 b 中從索引 off 開始的位置,並返回讀取得位元組數。

對於這三個方法,若返回 -1,表明流結束,否則,返回實際讀取的字元數。

OutputStream

OutputStream 是所有表示位元組輸出流類的基類。子類要重新定義其中所定義的抽象方法,OutputStream 是用於將資料寫入目的地的抽象表示。例如 System 中的標準輸出流物件 out 其型別是 java.io.PrintStream,這個類是 OutputStream 的子類。

OutputStream 類方法:

方法

說明

write(int b)throws IOException

將指定的位元組寫入此輸出流(抽象方法)

write(byte b[])throws IOException

將位元組陣列中的資料輸出到流中

write(byte b[], int off, int len)throws IOException

將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此輸出流

flush()throws IOException

重新整理此輸出流並強制寫出所有緩衝的輸出位元組

close()throws IOException

關閉流

檔案流

在 I/O 處理中,最常見的就是對檔案的操作。java.io 包中所提供的檔案操作類包括:

  1. 用於讀寫本地檔案系統中的檔案:FileInputStream 和 FileOutputStream

  2. 描述本地檔案系統中的檔案或目錄:File、FileDescriptor 和 FilenameFilter

  3. 提供對本地檔案系統中檔案的隨機訪問支援:RandomAccessFile

今天我們來學習檔案流的 FileInputStream 和 FileOutputStream。

FileInputStream 類用於開啟一個輸入檔案,若要開啟的檔案不存在,則會產生異常 FileNotFoundException,這是一個非執行時異常,必須捕獲或宣告拋棄。 FileOutputStream 類用來開啟一個輸出檔案,若要開啟的檔案不存在,則會建立一個新的檔案,否則原檔案的內容會被新寫入的內容所覆蓋。

在進行檔案的讀/寫操作時,會產生非執行時異常 IOException,必須捕獲或宣告拋棄(其他的輸入/輸出流處理時也同樣需要進行輸入/輸出異常處理)。

緩衝流

類 BufferedInputStream 和 BufferedOutputStream 實現了帶緩衝的過濾流,它提供了緩衝機制,把任意的 I/O 流“捆綁”到緩衝流上,可以提高 I/O 流的讀取效率。

在初始化時,除了要指定所連線的 I/O 流之外,還可以指定緩衝區的大小。預設時是用 32 位元組大小的緩衝區;最優的緩衝區大小常依賴於主機作業系統、可使用的記憶體空間以及機器的配置等;一般緩衝區的大小為記憶體頁或磁碟塊等的整數倍。

BufferedInputStream 的資料成員 buf 是一個位陣列,預設為 2048 位元組。當讀取資料來源時例如檔案,BufferedInputStream 會盡量將 buf 填滿。當使用 read () 方法時,實際上是先讀取 buf 中的資料,而不是直接對資料來源作讀取。當 buf 中的資料不足時,BufferedInputStream 才會再實現給定的 InputStream 物件的 read() 方法,從指定的裝置中提取資料。

BufferedOutputStream 的資料成員 buf 是一個位陣列,預設為 512 位元組。當使用 write() 方法寫入資料時,實際上會先將資料寫至 buf 中,當 buf 已滿時才會實現給定的 OutputStream 物件的 write() 方法,將 buf 資料寫至目的地,而不是每次都對目的地作寫入的動作。

【計理01組04號】JDK基礎入門

資料流

介面 DataInput 和 DataOutput,設計了一種較為高階的資料輸入輸出方式:除了可處理位元組和位元組陣列外,還可以處理 int、float、boolean 等基本資料型別,這些資料在檔案中的表示方式和它們在記憶體中的一樣,無須轉換,如 read()readInt()readByte()...; write()writeChar()writeBoolean()... 此外,還可以用 readLine() 方法讀取一行資訊。

方法:

方法

返回值

說明

readBoolean()

boolean

 

readByte()

byte

 

readShort()

short

 

readChar()

char

 

readInt()

int

 

readLong()

long

 

readDouble()

double

 

readFloat()

float

 

readUnsignedByte()

int

 

readUnsignedShort()

int

 

readFully(byte[] b)

void

從輸入流中讀取一些位元組,並將它們儲存在緩衝區陣列 b 中

readFully(byte[] b, int off,int len)

void

從輸入流中讀取 len 個位元組

skipBytes(int n)

int

與 InputStream.skip 等價

readUTF()

String

按照 UTF-8 形式從輸入中讀取字串

readLine()

String

按回車 (\r) 換行 (\n) 為分割符讀取一行字串,不完全支援 UNICODE

writeBoolean(boolean v)

void

 

writeByte(int v)

void

 

writeShort(int v)

void

 

writeChar(int v)

void

 

writeInt(int v)

void

 

writeLong(long v)

void

 

writeFloat(float v)

void

 

writeDouble(double v)

void

 

write(byte[] b)

void

與 OutputStream.write 同義

write(byte[] b, int off, int len)

void

與 OutputStream.write 同義

write(int b)

void

與 OutputStream.write 同義

writeBytes(String s)

void

只輸出每個字元的低 8 位;不完全支援 UNICODE

writeChars(String s)

void

每個字元在輸出中都佔兩個位元組

資料流類 DataInputStream 和 DataOutputStream 的處理物件除了是位元組或位元組陣列外,還可以實現對檔案的不同資料型別的讀寫:

  1. 分別實現了 DataInput 和 DataOutput 介面。

  2. 在提供位元組流的讀寫手段同時,以統一的形式向輸入流中寫入 boolean,int,long,double 等基本資料型別,並可以再次把基本資料型別的值讀取回來。

  3. 提供了字串讀寫的手段。

標準流、記憶體讀寫流、順序輸入流

語言包 java.lang 中的 System 類管理標準輸入/輸出流和錯誤流。

System.in 從 InputStream 中繼承而來,用於從標準輸入裝置中獲取輸入資料(通常是鍵盤) System.out 從 PrintStream 中繼承而來,把輸入送到預設的顯示裝置(通常是顯示器) System.err 也是從 PrintStream 中繼承而來,把錯誤資訊送到預設的顯示裝置(通常是顯示器)

每當 main 方法被執行時,就會自動生產上述三個物件。這裡就不再寫程式碼驗證了。

記憶體讀寫流

為了支援在記憶體上的 I/O,java.io 中提供了類:ByteArrayInputStream、ByteArrayOutputStream 和 StringBufferInputStream

  1. ByteArrayInputStream 可以從指定的位元組陣列中讀取資料。
  2. ByteArrayOutputStream 中提供了緩衝區可以存放資料(緩衝區大小可以在構造方法中設定,預設為 32),可以用 write() 方法向其中寫入資料,然後用 toByteArray() 方法將緩衝區中的有效位元組寫到位元組陣列中去。size() 方法可以知道寫入的位元組數,reset() 可以丟棄所有內容。
  3. StringBufferInputStream 與 ByteArrayInputStream 相類似,不同點在於它是從字元緩衝區 StringBuffer 中讀取 16 位的 Unicode 資料,而不是 8 位的位元組資料(已被 StringReader 取代)。

這裡只做簡要的介紹,有興趣的同學可以檢視一下這些類裡具體的方法。

順序輸入流

java.io 中提供了類 SequenceInputStream,使應用程式可以將幾個輸入流順序連線起來。順序輸入流提供了將多個不同的輸入流統一為一個輸入流的功能,這使得程式可能變得更加簡潔。

字元流

字元流基類

java.io 包中專門用於字元流處理的類,是以 Reader 和 Writer 為基礎派生的一系列類。

字元流以字元為單位,根據碼錶對映字元,一次可能讀多個位元組,只能處理字元型別的資料。

同類 InputStream 和 OutputStream 一樣,Reader 和 Writer 也是抽象類,只提供了一系列用於字元流處理的介面。它們的方法與類 InputStream 和 OutputStream 類似,只不過其中的引數換成字元或字元陣列。

Reader 是所有的輸入字元流的父類,它是一個抽象類。

我們先來看一看基類 Reader 的方法,其用法與作用都與 InputStream 和 OutputStream 類似,就不做過多的說明了。

方法 返回值
close() void
mark (int readAheadLimit) void
markSupported() boolean
read() int
read(char[] cbuf, int off,int len) int
ready() boolean
reset() void
skip(long n) long

Writer 是所有的輸出字元流的父類,它是一個抽象類。 Writer 的方法:

方法 返回值
close() void
flush() void
write(char[] cbuf) void
write(char[] cbuf, int off,int len) void
write(int c) void
write(String str) void
write(String str, int off, int len) void

InputStreamReader 和 OutputStreamWriter 是 java.io 包中用於處理字元流的最基本的類,用來在位元組流和字元流之間作為中介:從位元組輸入流讀入位元組,並按編碼規範轉換為字元;往位元組輸出流寫字元時先將字元按編碼規範轉換為位元組。使用這兩者進行字元處理時,在構造方法中應指定一定的平臺規範,以便把以位元組方式表示的流轉換為特定平臺上的字元表示。

快取流

同樣的,為了提高字元流處理的效率,java.io 中也提供了緩衝流 BufferedReader 和 BufferedWriter。其構造方法與 BufferedInputStream 和 BufferedOutPutStream 相類似。另外,除了 read() 和 write() 方法外,它還提供了整行字元處理方法:

  1. public String readLine():BufferedReader 的方法,從輸入流中讀取一行字元,行結束標誌\n\r或者兩者一起(這是根據系統而定的)。
  2. public void newLine():BufferedWriter 的方法,向輸出流中寫入一個行結束標誌,它不是簡單地換行符\n\r,而是系統定義的行隔離標誌(line separator)。
其它字元流類

在這裡我們就列舉一下有哪些類,具體的就不再講解了。

  1. 對字元陣列進行處理: CharArrayReader、CharArrayWrite
  2. 對文字檔案進行處理:FileReader、FileWriter
  3. 對字串進行處理:StringReader、StringWriter
  4. 過濾字元流:FilterReader、FilterWriter
  5. 管道字元流:PipedReader、PipedWriter
  6. 行處理字元流:LineNumberReader
  7. 列印字元流:PrintWriter

類有千萬,方法更是不計其數,所以沒有必要去掌握所有的方法和類,只需要知道常見常用的就行了,而大多數的類和方法,希望大家有一個印象,當我們在實際開發的時候,能夠想到,並且藉助其他工具去查詢我們需要的方法的應用方式就可以了。

檔案操作

檔案操作

java.io 定義的大多數類都是流式操作,但 File 類不是。它直接處理檔案和檔案系統。File 類沒有指定資訊怎樣從檔案讀取或向檔案儲存;它描述了檔案本身的屬性。File 物件用來獲取或處理與磁碟檔案相關的資訊,例如許可權,時間,日期和目錄路徑。此外,File 還瀏覽子目錄層次結構。Java 中的目錄當成 File 對待,它具有附加的屬性——一個可以被 list() 方法檢測的檔名列表。

先看一看 File 的構造方法:

//根據 parent 抽象路徑名和 child 路徑名字串建立一個新 File 例項。
File(File parent, String child)

//通過將給定路徑名字串轉換為抽象路徑名來建立一個新 File 例項
File(String pathname)

// 根據 parent 路徑名字串和 child 路徑名字串建立一個新 File 例項
File(String parent, String child)

//通過將給定的 file: URI 轉換為一個抽象路徑名來建立一個新的 File 例項
File(URI uri)

例如:

//一個目錄路徑引數
File f1 = new File("/Users/mumutongxue/");

//物件有兩個引數——路徑和檔名
File f2 = new File("/Users/mumutongxue/","a.bat");

//指向 f1 檔案的路徑及檔名
File f3 = new File(f1,"a.bat");

再來看看 File 的一些方法

方法 說明
boolean canExecute() 測試應用程式是否可以執行此抽象路徑名錶示的檔案
boolean canRead() 測試應用程式是否可以讀取此抽象路徑名錶示的檔案
boolean canWrite() 測試應用程式是否可以修改此抽象路徑名錶示的檔案
int compareTo(File pathname) 按字母順序比較兩個抽象路徑名
boolean createNewFile() 當且僅當不存在具有此抽象路徑名指定名稱的檔案時,不可分地建立一個新的空檔案
static File createTempFile(String prefix, String suffix) 在預設臨時檔案目錄中建立一個空檔案,使用給定字首和字尾生成其名稱
static File createTempFile(String prefix, String suffix, File directory) 在指定目錄中建立一個新的空檔案,使用給定的字首和字尾字串生成其名稱
boolean delete() 刪除此抽象路徑名錶示的檔案或目錄
void deleteOnExit() 在虛擬機器終止時,請求刪除此抽象路徑名錶示的檔案或目錄
boolean equals(Object obj) 測試此抽象路徑名與給定物件是否相等
boolean exists() 測試此抽象路徑名錶示的檔案或目錄是否存在
File getAbsoluteFile() 返回此抽象路徑名的絕對路徑名形式
String getAbsolutePath() 返回此抽象路徑名的絕對路徑名字串
File getCanonicalFile() 返回此抽象路徑名的規範形式
String getCanonicalPath() 返回此抽象路徑名的規範路徑名字串
long getFreeSpace() 返回此抽象路徑名指定的分割槽中未分配的位元組數
String getName() 返回由此抽象路徑名錶示的檔案或目錄的名稱
String getParent() 返回此抽象路徑名父目錄的路徑名字串;如果此路徑名沒有指定父目錄,則返回 null
File getParentFile() 返回此抽象路徑名父目錄的抽象路徑名;如果此路徑名沒有指定父目錄,則返回 null
String getPath() 將此抽象路徑名轉換為一個路徑名字串
long getTotalSpace() 返回此抽象路徑名指定的分割槽大小
long getUsableSpace() 返回此抽象路徑名指定的分割槽上可用於此虛擬機器的位元組數
int hashCode() 計算此抽象路徑名的雜湊碼
boolean isAbsolute() 測試此抽象路徑名是否為絕對路徑名
boolean isDirectory() 測試此抽象路徑名錶示的檔案是否是一個目錄
boolean isFile() 測試此抽象路徑名錶示的檔案是否是一個標準檔案
boolean isHidden() 測試此抽象路徑名指定的檔案是否是一個隱藏檔案
long lastModified() 返回此抽象路徑名錶示的檔案最後一次被修改的時間
long length() 返回由此抽象路徑名錶示的檔案的長度
String[] list() 返回一個字串陣列,這些字串指定此抽象路徑名錶示的目錄中的檔案和目錄
String[] list(FilenameFilter filter) 返回一個字串陣列,這些字串指定此抽象路徑名錶示的目錄中滿足指定過濾器的檔案和目錄
File[] listFiles() 返回一個抽象路徑名陣列,這些路徑名錶示此抽象路徑名錶示的目錄中的檔案
File[] listFiles(FileFilter filter) 返回抽象路徑名陣列,這些路徑名錶示此抽象路徑名錶示的目錄中滿足指定過濾器的檔案和目錄
File[] listFiles(FilenameFilter filter) 返回抽象路徑名陣列,這些路徑名錶示此抽象路徑名錶示的目錄中滿足指定過濾器的檔案和目錄
static File[] listRoots() 列出可用的檔案系統根
boolean mkdir() 建立此抽象路徑名指定的目錄
boolean mkdirs() 建立此抽象路徑名指定的目錄,包括所有必需但不存在的父目錄
boolean renameTo(File dest) 重新命名此抽象路徑名錶示的檔案
boolean setExecutable(boolean executable) 設定此抽象路徑名所有者執行許可權的一個便捷方法
boolean setExecutable(boolean executable, boolean ownerOnly) 設定此抽象路徑名的所有者或所有使用者的執行許可權
boolean setLastModified(long time) 設定此抽象路徑名指定的檔案或目錄的最後一次修改時間
boolean setReadable(boolean readable) 設定此抽象路徑名所有者讀許可權的一個便捷方法
boolean setReadable(boolean readable, boolean ownerOnly) 設定此抽象路徑名的所有者或所有使用者的讀許可權
boolean setReadOnly() 標記此抽象路徑名指定的檔案或目錄,從而只能對其進行讀操作
boolean setWritable(boolean writable) 設定此抽象路徑名所有者寫許可權的一個便捷方法
boolean setWritable(boolean writable, boolean ownerOnly) 設定此抽象路徑名的所有者或所有使用者的寫許可權
String toString() 返回此抽象路徑名的路徑名字串
URI toURI() 構造一個表示此抽象路徑名的 file: URI

隨機訪問檔案

對於 FileInputStream/FileOutputStream、FileReader/FileWriter 來說,它們的例項都是順序訪問流,即只能進行順序讀/寫。而類 RandomAccessFile 則允許檔案內容同時完成讀和寫操作,它直接繼承 object,並且同時實現了介面 DataInput 和 DataOutput。

隨機訪問檔案的行為類似儲存在檔案系統中的一個大型 byte 陣列。存在指向該隱含陣列的游標或索引,稱為檔案指標。輸入操作從檔案指標開始讀取位元組,並隨著對位元組的讀取而前移此檔案指標。如果隨機訪問檔案以讀取/寫入模式建立,則輸出操作也可用。輸出操作從檔案指標開始寫入位元組,並隨著對位元組的寫入而前移此檔案指標。

RandomAccessFile 提供了支援隨機檔案操作的方法:

  1. readXXX() 或者 writeXXX(): 如 ReadInt(),ReadLine(),WriteChar(),WriteDouble() 等
  2. int skipBytes(int n): 將指標向下移動若干位元組
  3. length(): 返回檔案長度
  4. long getFilePointer(): 返回指標當前位置
  5. void seek(long pos): 將指標呼叫所需位置

在生成一個隨機檔案物件時,除了要指明檔案物件和檔名之外,還需要指明訪問檔案的模式。

我們來看看 RandomAccessFile 的構造方法:

RandomAccessFile(File file,String mode)
RandomAccessFile(String name,String mode)

mode 的取值:

  • r: 只讀,任何寫操作都將丟擲 IOException。
  • rw: 讀寫,檔案不存在時會建立該檔案,檔案存在時,原檔案內容不變,通過寫操作改變檔案內容。
  • rws: 開啟以便讀取和寫入,對於 "rw",還要求對檔案的內容或後設資料的每個更新都同步寫入到底層儲存裝置。
  • rwd: 開啟以便讀取和寫入,對於 "rw",還要求對檔案內容的每個更新都同步寫入到底層儲存裝置。

Swing入門

注意:WEB IDE 下不支援顯示圖形介面,所以本章節的內容請同學們在本地使用 IDE 練習,如 IDEA,Eclipse 等

先聊聊 GUI 和 MVC

圖形使用者介面(Graphical User Interface,簡稱 GUI,又稱圖形使用者介面)是指採用圖形方式顯示的計算機操作使用者介面。 ——來自 百度百科 · GUI

你在系統中按下的每一個按鈕、瀏覽器中輸入網址的位址列、以及無數個被開啟和關閉的視窗,它們都是 GUI 的組成部分。這與我們在前面章節提到的模組化思想不謀而合。Swing 便是 Java 中的一個 GUI,它是基於MVC(模型-檢視-控制器)設計模式來進行設計的,通過事件對使用者的輸入進行反應。即使是最簡單的一個按鈕,也是包含了它的外觀(什麼顏色,有多大)、內容(按鈕上面顯示什麼文字等)以及行為(對於使用者按下時的反應)這三個要素。你可以 進一步瞭解設計模式MVC 框架 和 事件 這三個方面的知識來作為學習 Swing 的準備。

比如,你在實驗樓進行實驗的時候,擺在你面前、在你的電腦螢幕上顯示的內容,就是檢視;你在實驗環境中每一次的滑鼠點選、輸入的內容,都有專門的模組來負責處理你的這些輸入,可以理解為控制器;而你寫的程式碼、實驗時的環境,這些內容,都稱之為模型

下圖表示了 MVC 元件型別的關係和功能。

【計理01組04號】JDK基礎入門

Swing 是在 抽象視窗工具箱(AWT) 的架構上發展而來的一個使用者介面庫,整個可視元件庫的基礎構造塊是 JComponent。它是所有元件的父類,為所有元件提供了繪製的基礎架構。換言之,所有的 Swing 元件都是由它派生而來。

基於 Swing 製作的 Java 程式就是由一個一個的元件構成的,開發的過程有點像組裝樂高積木。下面我們就通過實驗來熟悉一些基本的元件。

請在 Eclipse 中新建專案 HelloSwing,建立包 com.shiyanlou.course,新建一個包含主方法的類 MySwingWindow。在建立類時,你可以使用 Eclipse 來幫你生成此類的主方法,就像下圖那樣。

【計理01組04號】JDK基礎入門

這樣,在建立好指定的類之後,你可以在程式碼中看到自動生成的 main() 方法,然後就能增添更多功能模組了。

【計理01組04號】JDK基礎入門

盛放控制元件的盤子——JFrame

JFrame 類就是一個容器,允許你把其他元件新增到它裡面,把它們組織起來,並把它們呈現給使用者。JFrame 在大多數作業系統中是以視窗的形式註冊的,這意味著你可以對其進行最小化和最大化,甚至移動這個視窗。

如果要打個比方的話,你的臉就是一個容器,眼睛、耳朵、鼻子和嘴巴這些“控制元件”需要放在臉上,別人看到你這個“介面”實際上就是你的臉,以及上面的“控制元件”。

不同的教材對於 JFrame 的稱呼是有差別的。為了幫助你理解,在本實驗中,我們稱之為“窗體”。

下面是它包含的一些方法,你最好在 Java SE 官方 API 中去查閱它們的用法和詳細說明:

方法 說明
get/setTitle() 獲取/設定窗體的標題
get/setState() 獲取/設定窗體的最小化、最大化等狀態
is/setVisible() 獲取/設定窗體的可視狀態,換言之,是否在螢幕上顯示
get/setLocation() 獲取/設定窗體在螢幕上在什麼位置出現
get/setSize() 獲取/設定窗體的大小
add() 將元件新增到窗體中,這個過程把各個控制元件形成了一個整體

對於 Swing 應用程式,我們如果要將元件放在 JFrame 上,則需要繼承 JFrame 類。我們來嘗試建立一個窗體吧。

主要的程式碼如下:

package com.shiyanlou.course;

import javax.swing.JFrame;

public class MySwingWindow extends JFrame {
    //此處通過繼承 JFrame 類來使我們自己的 MySwingWindow 具有窗體的一些屬性和方法

    public MySwingWindow(){
        //在窗體的構造方法中設定窗體的各項屬性

        super();
        //使用 super() 來引用父類的成分,用 this 來引用當前物件

        this.setSize(400, 300);
        //設定窗體的大小

        this.getContentPane().setLayout(null);
        //返回此窗體的 contentPane 物件,設定其佈局
        //這一句不太懂的話也不用擔心,先寫著

        this.setTitle("My First Swing Window");
        //設定窗體的標題
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MySwingWindow window = new MySwingWindow();
        //宣告一個窗體物件 window

        window.setVisible(true);
        //設定這個窗體是可見的
    }
}

你不必一開始就寫 import 相關程式碼,通常在需要引入相應的包時,使用自動提示給出的方案即可,就像下面這樣:

【計理01組04號】JDK基礎入門

和所有的 Java 程式一樣,都需要 main() 方法才能讓程式“跑”起來,所以我們只需要在其中建立好一個窗體物件並設定其可見就行了。

不妨編譯並執行一下,你應該可以看到一個最原始的窗體程式。

【計理01組04號】JDK基礎入門

個性化 Swing 應用程式

有了最基礎的容器,我們就可以在上面新增各式各樣的控制元件。Swing 中的控制元件數量是巨大的,但它們的使用方法都是相通的,你可以在 API 文件中查閱每種控制元件的屬性及其設定方法、獲取屬性和資料的方法等等。

我們在本實驗中先為大家介紹一些常用的控制元件,在上一步的基礎上繼續個性化我們的 MySwingWindow

首先新增 Swing 庫中最基礎的元件——標籤 JLabel。JLabel 可以用作文字描述和圖片描述,常用的方法如下:

方法 說明
get/setText() 獲取/設定標籤的文字
get/setIcon() 獲取/設定標籤的圖片

你需要先宣告這個控制元件物件。

private JLabel myLabel;

然後編寫一個getJLabel()方法,用於返回一個 JLabel 例項。

private JLabel getJLabel() {
    //此處的返回值型別為 JLabel

    if(myLabel == null) {

        myLabel = new JLabel();
        //例項化 myLabel 物件

        myLabel.setBounds(5, 10, 250, 30);
        //使用 setBounds() 方法設定尺寸
        //四個引數的分別是 x,y,width,height
        //代表了橫向、縱向的位置,以及標籤自身的寬和高

        myLabel.setText("Hello! Welcome to shiyanlou.com");
        //使用 setText() 方法設定要顯示的文字
    }

    return myLabel;
    //返回建立的例項
}

同樣,我們來新增一個文字框 JTextFiled,它包含的方法與 JLabel 類似,你可以在 API 文件中查閱更多的方法。

private JTextField myTextField;

private JTextField getJTextField() {
    //此處的返回值型別為 JTextField

    if(myTextField == null) {
    //加上這個判斷是為了防止出錯

        myTextField = new JTextField();
        //例項化 myTextField 物件

        myTextField.setBounds(5, 45, 200, 30);
        //設定它的位置和尺寸

        myTextField.setText("Shi Yan Lou");
        //設定它要顯示的字串

    }

    return myTextField;
    //返回這個例項
}

再來做一個按鈕 JButton。與上述控制元件不同的是,我們在這裡為它新增一個事件響應,當你按下按鈕的時候它就能做一些事情了。

private JButton myButton;

private JButton getJButton() {
    //此處的返回值型別為 JButton

    if(myButton == null) {

        myButton = new JButton();
        //例項化 myTextField 物件
        myButton.setBounds(5, 80, 100, 40);
        //設定它的位置和尺寸
        myButton.setText("Click me!");
        //設定它要顯示的字串
        myButton.addActionListener(new ActionListener() {
            //為其新增一個事件監聽,從而使這個按鈕可以響應使用者的點選操作
            //ActionListener 是用於接收操作事件的偵聽器介面。
            //對處理操作事件感興趣的類可以實現此介面,而使用該類建立的對
            //可使用元件的 addActionListener 方法向該元件註冊。
            //在發生操作事件時,呼叫該物件的 actionPerformed 方法。

            public void actionPerformed(ActionEvent e) {
                //該方法會在發生操作時被呼叫,我們要做的事情就可以寫在這裡面
                //比如我們下面要做的事情就是改變之前兩個控制元件裡面的文字顏色和背景色

                myLabel.setForeground(Color.RED);
                //設定此元件的前景色。

                myTextField.setBackground(Color.BLUE);
                //設定此元件的背景色。
            }
        });
    }

    return myButton;
    //返回這個例項
}

程式碼 myButton.addActionListener(new ActionListener(){ ... }); 中的 new ActionListener(){ ... } 是一種名為 匿名類 的用法。

最後,我們在這個窗體的構造方法 public MySwingWindow() 中,將這三個控制元件的獲取方法新增進去。

this.add(getJLabel(),null);
this.add(getJTextField(), null);
this.add(getJButton(),null);
//在自定義的 JFrame 構造方法中使用 add() 方法來新增控制元件
//add() 方法可以將指定元件新增到此容器的給定位置上
//第一個引數為待新增的元件,這裡的元件來自我們的返回值
//第二個引數為描述元件的佈局限制的物件,我們不加限制,所以填 null

如果你不是很清楚整個程式碼的結構,可以參考下面的完整程式碼:

package com.shiyanlou.course;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class MySwingWindow extends JFrame {
    //此處通過繼承 JFrame 類來使我們自己的 MySwingWindow 具有窗體的一些屬性和方法

    /**
     *
     */
    private static final long serialVersionUID = 8978037719568897634L;

    //首先需要宣告各個控制元件
    private JLabel myLabel;
    private JTextField myTextField;
    private JButton myButton;

    public MySwingWindow(){
        //在窗體的構造方法中設定窗體的各項屬性

        super();
        //使用 super() 來引用父類的成分,用 this 來引用當前物件

        this.setSize(400, 300);
        //設定窗體的大小

        this.getContentPane().setLayout(null);
        //返回此窗體的 contentPane 物件,設定其佈局
        //這一句不太懂的話也不用擔心,先寫著

        this.setTitle("My First Swing Window");
        //設定窗體的標題

        this.add(getJLabel(),null);
        this.add(getJTextField(), null);
        this.add(getJButton(),null);
        //在自定義的 JFrame 構造方法中使用 add() 方法來新增控制元件
        //add() 方法可以將指定元件新增到此容器的給定位置上
        //第一個引數為待新增的元件,這裡的元件來自我們的返回值
        //第二個引數為描述元件的佈局限制的物件,我們不加限制,所以填 null
    }

    private JLabel getJLabel() {
        //此處的返回值型別為 JLabel

        if(myLabel == null) {

            myLabel = new JLabel();
            //例項化 myLabel 物件

            myLabel.setBounds(5, 10, 250, 30);
            //使用 setBounds 方法設定其位置和尺寸
            //四個引數的分別是 x,y,width,height
            //代表了橫向、縱向的位置,以及標籤自身的寬和高

            myLabel.setText("Hello! Welcome to shiyanlou.com");
            //使用 setText 方法設定要顯示的文字
        }

        return myLabel;
        //返回建立的例項
    }

    private JTextField getJTextField() {
        //此處的返回值型別為 JTextField

        if(myTextField == null) {
        //加上這個判斷是為了防止出錯

            myTextField = new JTextField();
            //例項化 myTextField 物件

            myTextField.setBounds(5, 45, 200, 30);
            //設定它的位置和尺寸

            myTextField.setText("Shi Yan Lou");
            //設定它要顯示的字串

        }

        return myTextField;
        //返回這個例項
    }

    private JButton getJButton() {
        //此處的返回值型別為 JButton

        if(myButton == null) {

            myButton = new JButton();
            //例項化 myTextField 物件
            myButton.setBounds(5, 80, 100, 40);
            //設定它的位置和尺寸
            myButton.setText("Click me!");
            //設定它要顯示的字串
            myButton.addActionListener(new ActionListener() {
                //為其新增一個事件監聽,從而使這個按鈕可以響應使用者的點選操作
                //ActionListener 是用於接收操作事件的偵聽器介面。
                //對處理操作事件感興趣的類可以實現此介面,而使用該類建立的對
                //可使用元件的 addActionListener 方法向該元件註冊。
                //在發生操作事件時,呼叫該物件的 actionPerformed 方法。

                public void actionPerformed(ActionEvent e) {
                    //該方法會在發生操作時被呼叫,我們要做的事情就可以寫在這裡面
                    //比如我們下面要做的事情就是改變之前兩個控制元件裡面的文字顏色和背景色

                    myLabel.setForeground(Color.RED);
                    //設定此元件的前景色。

                    myTextField.setBackground(Color.BLUE);
                    //設定此元件的背景色。
                }
            });
        }

        return myButton;
        //返回這個例項
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MySwingWindow window = new MySwingWindow();
        //宣告一個窗體物件 window

        window.setVisible(true);
        //設定這個窗體是可見的
    }
}

檢查一下程式碼,確認無誤後點選編譯並執行,自己動手做的第一個窗體程式就出來了。

【計理01組04號】JDK基礎入門

多執行緒程式設計

從執行緒到多執行緒

首先你應該知道什麼是執行緒:

執行緒:程式執行流的最小單元。它是程式內一個相對獨立的、可排程的執行單元,是系統獨立排程和分派 CPU 的基本單位。

如同大自然中的萬物,執行緒也有“生老病死”的過程,下圖表示了一個執行緒從建立到消亡的過程,以及過程中的狀態。

【計理01組04號】JDK基礎入門

結合執行緒的生命週期,我們再來看看多執行緒的定義:

多執行緒:從軟體或者硬體上實現多個執行緒併發執行的技術。在單個程式中同時執行多個執行緒完成不同的工作。

在 Java 中,垃圾回收機制 就是通過一個執行緒在後臺實現的,這樣做的好處在於:開發者通常不需要為記憶體管理投入太多的精力。反映到我們現實生活中,在瀏覽網頁時,瀏覽器能夠同時下載多張圖片;實驗樓的伺服器能夠容納多個使用者同時進行線上實驗,這些都是多執行緒帶來的好處。

從專業的角度來看,多執行緒程式設計是為了最大限度地利用 CPU 資源——當處理某個執行緒不需要佔用 CPU 而只需要利用 IO 資源時,允許其他的那些需要 CPU 資源的執行緒有機會利用 CPU。這或許就是多執行緒程式設計的最終目的。當然,你也可以進一步瞭解 為什麼使用多執行緒

對於多執行緒和執行緒之間的關係,你可以這樣理解:一個使用了多執行緒技術的程式,包含了兩條或兩條以上併發執行的執行緒(Thread)。

Java 中的 Thread 類就是專門用來建立執行緒和操作執行緒的類,我們來具體學習一下。

使用 Thread 類

建立執行緒

根據我們前面所學,我們可以自定義一個類,然後繼承 Thread 類來使其成為一個執行緒類。

那麼我們要把執行緒要做的事情放在哪裡呢?在 Java 中,run() 方法為執行緒指明瞭它要完成的任務,你可以通過下面兩種方式來為執行緒提供 run 方法:

  1. 繼承 Thread 類並重寫它的 run() 方法,然後用這個子類來建立物件並呼叫 start() 方法。

  2. 通過定義一個類,實現 Runnable 介面,實現 run() 方法。

概括一下,啟動執行緒的唯一的方法便是 start(),而你需要把待完成的工作(功能程式碼)放入到 run() 方法中。

我們來建立兩個執行緒試試。新建一個帶有主方法的類 CreateThread

程式碼片段如下,我們在註釋中繼續講解:

public class CreateThread {

    public static void main(String[] args)
    {
        Thread1 thread1 = new Thread1();
        //宣告一個 Thread1 物件,這個 Thread1 類繼承自 Thread 類的

        Thread thread2 = new Thread(new Thread2());
        //傳遞一個匿名物件作為引數

        thread1.start();
        thread2.start();
        //啟動執行緒
    }
}

class Thread1 extends Thread
{
    public void run()
    {
        //在 run() 方法中放入執行緒要完成的工作

        //這裡我們把兩個執行緒各自的工作設定為列印 100 次資訊
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Hello! This is " + i);
        }

        //在這個迴圈結束後,執行緒便會自動結束
    }
}

class Thread2 implements Runnable {
    //與 Thread1 不同,如果當一個執行緒已經繼承了另一個類時,就建議你通過實現 Runnable 介面來構造

    public void run()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Thanks. There is " + i);
        }
    }
}

編譯並執行此程式

javac CreateThread.java
java CreateThread

你在控制檯就可以看到下面這樣的輸出資訊。兩個執行緒近似交替地在輸出資訊。受到系統排程的影響,兩個執行緒輸出資訊的先後順序可能不同。

檢視執行緒執行狀態

執行緒的狀態共有 6 種,分別是:新建 New、執行(可執行)Runnable、阻塞 Blocked、計時等待 Timed Waiting、等待 Waiting 和終止 Terminate

當你宣告一個執行緒物件時,執行緒處於新建狀態,系統不會為它分配資源,它只是一個空的執行緒物件。 呼叫 start() 方法時,執行緒就成為了可執行狀態,至於是否是執行狀態,則要看系統的排程了。 呼叫了 sleep() 方法、呼叫 wait() 方法和 IO 阻塞時,執行緒處於等待、計時等待或阻塞狀態。 當 run() 方法執行結束後,執行緒也就終止了。

我們通過一個例子來加深對於這些狀態的理解。新建 ThreadState 類,用於自定義執行緒的狀態。

主要的程式碼如下:

public class ThreadState implements Runnable {

    public synchronized void waitForAMoment() throws InterruptedException {

        wait(500);
        //使用 wait() 方法使當前執行緒等待 500 毫秒
        //或者等待其他執行緒呼叫 notify() 或 notifyAll() 方法來喚醒
    }

    public synchronized void waitForever() throws InterruptedException {

        wait();
        //不填入時間就意味著使當前執行緒永久等待,
        //只能等到其他執行緒呼叫 notify() 或 notifyAll() 方法才能喚醒
    }

    public synchronized void notifyNow() throws InterruptedException {

        notify();
        //使用 notify() 方法來喚醒那些因為呼叫了 wait() 方法而進入等待狀態的執行緒
    }

    public void run() {

        //這裡用異常處理是為了防止可能的中斷異常
        //如果任何執行緒中斷了當前執行緒,則丟擲該異常

        try {
            waitForAMoment();
            // 在新執行緒中執行 waitMoment() 方法

            waitForever();
            // 在新執行緒中執行 waitForever() 方法

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

然後再新建一個測試類 ThreadTest,用於輸出這些狀態。

接下來會用到 sleep() 方法,下面給出了這個方法的使用方法。

sleep(),在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。填入的引數為休眠的時間(單位:毫秒)。

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadState state = new ThreadState();
        //宣告並例項化一個 ThreadState 物件

        Thread thread = new Thread(state);
        //利用這個名為 state 的 ThreadState 物件來建立 Thread 物件

        System.out.println("Create new thread: " + thread.getState());
        //使用 getState() 方法來獲得執行緒的狀態,並進行輸出

        thread.start();
        //使用 thread 物件的 start() 方法來啟動新的執行緒

        System.out.println("Start the thread: " + thread.getState());
        //輸出執行緒的狀態

        Thread.sleep(100);
        //通過呼叫 sleep() 方法使當前這個執行緒休眠 100 毫秒,從而使新的執行緒執行 waitForAMoment() 方法

        System.out.println("Waiting for a moment (time): " + thread.getState());
        //輸出執行緒的狀態

        Thread.sleep(1000);
        //使當前這個執行緒休眠 1000 毫秒,從而使新的執行緒執行 waitForever() 方法

        System.out.println("Waiting for a moment: " + thread.getState());
        //輸出執行緒的狀態

        state.notifyNow();
        // 呼叫 state 的 notifyNow() 方法

        System.out.println("Wake up the thread: " + thread.getState());
        //輸出執行緒的狀態

        Thread.sleep(1000);
        //使當前執行緒休眠 1000 毫秒,使新執行緒結束

        System.out.println("Terminate the thread: " + thread.getState());
        //輸出執行緒的狀態
    }
}