之前一篇文章講述了Java中字節流的分類及簡單用法,最後我們可以發現,Java中的字節流既可以用來處理二進制文件,也可以用來處理文本文件。但是字節流對於文本文件的處理是不太方便的,比如字節流的媒介是字節,但是文本文件的內容都是可顯式地字符,很明顯通過字符作爲媒介更合理(需要手動進行字節和字符通過某種特定編碼格式的轉化)。另外,在之前那篇文章Java編程拾遺『搞定編碼』中,我們瞭解到,字符可以有很多種編碼方式,但是在使用字節流處理文本文件,編碼這一塊就不是很方便處理,因爲字節流是沒有編碼地概念的。但是文本或者講字符串,是Java中比較常見地一種形式,能方便地進行處理還是很重要的,所以字符流就出現了。可以講,字符流就是用來方便處理可顯示字符的。字符流的繼承體系如下:
本文重點介紹如下幾種字符流:
- Reader/Writer:抽象類,也是字符流的基類
- InputStreamReader/OutputStreamWriter:適配器類,將字節流轉換爲字符流
- FileReader/FileWriter:輸入源和輸出目標是文件的字符流
- CharArrayReader/CharArrayWriter: 輸入源和輸出目標是char數組的字符流
- StringReader/StringWriter:輸入源和輸出目標是String的字符流
- BufferedReader/BufferedWriter:裝飾類,對輸入輸出流提供緩衝,以及按行讀寫功能
- PrintWriter:裝飾類,可將基本類型和對象轉換爲其字符串形式輸出的類
1. Reader/Writer
Reader和Writer是抽象類,是所有字符流的基類。
1.1 Reader
Reader中的方法跟InputStream中的方法類似,區別在於InputStream讀取的單位是字節,Reader讀取的單位char。
S.N. | 方法 | 說明 |
1 | public int read() throws IOException | 從字符輸入流Reader中讀取一個字符,返回值爲讀取字符對應的int值,返回-1表示字符流中的內容已經讀取結束 |
2 | public int read(char cbuf[]) throws IOException | 從字符輸入流Reader中讀取多個字節,放入字符數組cbuf中,返回值爲實際讀入的字符個數 |
3 | abstract public int read(char cbuf[], int off, int len) throws IOException | 從字符輸入流Reader中讀取len個字節放入字符數組cbuf index off開始的位置, 返回值爲實際讀入的字符個數 |
4 | public boolean ready() throws IOException | 返回字符輸入流Reader下一次read()操作是否需要阻塞,如果需要阻塞返回true,否則返回false。跟InputStream中的available方法類似。 |
5 | public long skip(long n) throws IOException | 跳過字符輸入流Reader n個字符,返回值爲實際跳過的字符個數 |
6 | public boolean markSupported() | 判斷當前字符輸入流是否支持mark/reset操作 |
7 | public void mark(int readAheadLimit) throws IOException | 標記能夠從字符輸入流中往後讀取的字符個數readAheadLimit |
8 | public void reset() throws IOException | 重新從標記位置讀取字符輸入流 |
9 | abstract public void close() throws IOException | 關閉字符輸入流,釋放資源 |
方法大體上跟InputStream很類似,不同的是,InputStream讀取的單位是字節,Reader讀取的單位是char。
1.1 Writer
S.N. | 方法 | 說明 |
1 | public void write(int c) throws IOException | 向字符輸出流中寫入一個字符 |
2 | public void write(char cbuf[]) throws IOException | 將字符數組cbuf中所有的字符寫入字符輸出流 |
3 | abstract public void write(char cbuf[], int off, int len) throws IOException | 將字符數組cbuf中從index off開始,長度爲len的字符寫入字符輸出流 |
4 | public void write(String str) throws IOException | 將字符串str中所有的字符寫入到字符輸出流 |
5 | public void write(String str, int off, int len) throws IOException | 將字符串str中index 從off開始長度爲len的所有字符寫入到字符輸出流 |
6 | public Writer append(char c) throws IOException | 操作等同於write(int c),向字符輸出流中寫入一個字符 |
7 | public Writer append(CharSequence csq) throws IOException | 操作等同於write(String str),將CharSequence中所有的字符寫入到字符輸出流 |
8 | public Writer append(CharSequence csq, int start, int end) throws IOException | 將CharSequence中index再[start, end)區間內所有的字符寫入到字符輸出流 |
9 | abstract public void flush() throws IOException | 將緩衝而未實際寫的數據進行實際寫入 |
10 | abstract public void close() throws IOException | 關字符輸出流,釋放資源 |
2. InputStreamReader/OutputStreamWriter
InputStreamReader和OutputStreamWriter是適配器類,負責InputStream/OutputStream和Reader/Writer之間的轉換。
2.1 OutputStreamWriter
OutputStreamWriter可以將字節輸出流OutputStream轉換writer。舉個例子,如果要將字符串寫入一個文本文件中,直接通過FileOutputStream是很不方便的(需要先將字符串通過某種編碼轉化爲字節數組,然後將字節數組通過FileOutputStream最終寫入到文件中),但是通過OutputStreamWriter將FileOutputStream轉化爲Writer後,就可以很方便地直接將字符串寫入文件。OutputStreamWriter可以通過如下方式構建:
public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out, Charset cs)
public OutputStreamWriter(OutputStream out, String charsetName)
參數out爲待適配的字節輸出流OutputStream對象,第二個參數cs/charSetName爲用來將字符編碼的字符集,在將字符轉化爲字節數組時使用。如果沒有傳,則使用系統默認編碼。
2.2 InputStreamReader
InputStreamReader可以將字節輸入流InputStream轉化爲Reader,可以方便地通過字符/字符串讀取字節輸入流InputStream中的內容。可以通過如下方式構建:
public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, Charset cs)
public InputStreamReader(InputStream in, String charsetName)
跟OutPutStreamWriter構造函數類似,參數in爲待適配的InputStream對象,第二個參數cs/charSetName爲用來將字符編碼的字符集,在將字節轉化爲字符時使用。 如果沒有傳,則使用系統默認編碼。
2.3 示例
@SneakyThrows
public static void main(String[] args) {
try (OutputStream fileOutputStream = new FileOutputStream("d:/hello.txt");
Writer outPutStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8)) {
String text = "hello 卓立";
outPutStreamWriter.write(text);
}
try (InputStream fileInputStream = new FileInputStream("d:/hello.txt");
Reader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8)) {
char[] cbuf = new char[1024];
int charsRead = inputStreamReader.read(cbuf);
System.out.println(new String(cbuf, 0, charsRead));
}
}
運行結果:
hello 卓立
可以看到,通過OutputStreamWriter,將FileOutputStream對象轉化爲Writer實例,然後直接調用Writer的write方法,直接將字符串通過UTF-8編碼寫入到文件中。然後通過InputStreamReader,將FileInputStream對象轉化爲Reader實例,然後直接調用Reader的read方法,直接將文件中的二進制內容通過UTF-8編碼,讀取到字符數組cbuf中,並轉化爲String打印輸出。
3. FileReader/FileWriter
FileReader/FileWriter是輸入源和輸出目標是文件的字符流,FileReader繼承了InputStreamReader,FileWriter繼承了OutputStreamWriter。
2.1 FileWriter
FileWriter可以方便地將字符/字符串寫入文件,從這個角度上講,FileWriter和OutputStreamWriter非常類似。但是不同的是,FileWriter不能指定編碼類型,只能使用系統默認編碼,而OutputStreamWriter可以指定編碼。可以通過如下方式構造FileWriter:
public FileWriter(File file) throws IOException
public FileWriter(File file, boolean append) throws IOException
public FileWriter(String fileName) throws IOException
public FileWriter(String fileName, boolean append) throws IOException
參數file/fileName,表示要輸出的文件。第二個參數append,表示是追加文件還是覆蓋文件。除了構造函數外,其他方法都試繼承自OutputStreamWriter類。
2.2 FileReader
FileReader可以方便地將文件內容讀取爲字符/字符串,跟InputSreamReader的功能非常類似。同時,FileReader也不能指定編碼類型,只能使用系統默認編碼,而InputStreamReader可以指定編碼。可以通過如下方式構造FileReader:
public FileReader(File file) throws FileNotFoundException
public FileReader(String fileName) throws FileNotFoundException
跟FileWriter類似,FileReader類中也只定義了構造函數,其他方法都繼承自InputStreamReader。
2.3 示例
@SneakyThrows
public static void main(String[] args) {
try (Writer fileWriter = new FileWriter("d:/hello.txt")) {
String text = "hello 卓立";
fileWriter.write(text);
}
try (Reader fileReader = new FileReader("d:/hello.txt")) {
char[] cbuf = new char[1024];
int charsRead = fileReader.read(cbuf);
System.out.println(new String(cbuf, 0, charsRead));
}
}
運行結果:
hello 卓立
4. CharArrayReader/CharArrayWriter
CharArrayReader/CharArrayWriter是源和目的地是char數組的字符流,跟字節流中的ByteArrayInputStream/ByteArrayOutputStream的功能類似。
4.1 CharArrayWriter
任何一個Reader的實現類都可以實現將輸入流中的內容讀取爲字符數組,但是有這樣一個問題Reader是無法解決的,那就是用來存儲輸入流中內容的字符數組大小需要預先定義,如果不夠大,也不方便擴展。這時候就可以藉助CharArrayWriter類,先通過Reader將輸入流中的內容讀取到一個固定大小的字符數組中,如果超過固定大小的字符數組,可分多次讀取,然後將每次讀取的內容依次寫入到CharArrayWriter對象中,最後調用CharArrayWriter的toCharArray方法獲取一個完整的數組。CharArrayWriter可以通過如下方式構建:
public CharArrayWriter()
public CharArrayWriter(int initialSize)
initialSize表示CharArrayWriter內部用來存儲寫入內容的char數組的大小,第一個構造函數初始化的char數組默認大小爲32,如果寫入時char數組容量不夠,會進行動態擴容,每次擴容爲之前的兩倍。除了構造方法之外,CharArrayWriter對Writer接口的方法進行了重寫。其餘方法參考上面Writer類中的方法說明,這裏不再講了。
4.2 CharArrayReader
CharArrayReader與上篇文章講的的ByteArrayInputStream類似,它可以將char數組包裝爲Reader,是一種適配器模式,可以通過如下方式構建:
public CharArrayReader(char buf[])
public CharArrayReader(char buf[], int offset, int length)
第一個構造函數是將char數組buf中的全部內容包裝到CharArrayReader(效果是buf數組中的全部char都體現在CharArrayReader中)。第二個構造函數是將char數組buf中index從offset開始長度爲length的所有char包裝到CharArrayReader(效果是buf數組中offset到offset + len -1的char體現在CharArrayReader中,read操作也只能讀取到offset及之後的char)。CharArrayReader的所有數據都在內存,支持mark/reset重複讀取。
4.3 示例
@SneakyThrows
public static void main(String[] args) {
try (Reader inputStreamReader = new InputStreamReader(new FileInputStream("d:/hello.txt"), StandardCharsets.UTF_8)) {
CharArrayWriter charArrayWriter = new CharArrayWriter();
char[] cbuf = new char[1024];
int charsRead;
//分多次將Reader地內容讀出,並寫到writer中
while ((charsRead = inputStreamReader.read(cbuf)) != -1) {
charArrayWriter.write(cbuf, 0, charsRead);
}
System.out.println(charArrayWriter.toString());
}
char[] chars = "卓立 test".toCharArray();
//調用CharArrayReader()構造函數初始化CharArrayReader
try (CharArrayReader charArrayReader = new CharArrayReader(chars);
CharArrayWriter charArrayWriter = new CharArrayWriter()) {
writeChars(charArrayReader, charArrayWriter);
}
//調用CharArrayReader(char buf[], int offset, int length)構造函數初始化CharArrayReader
try (CharArrayReader charArrayReader = new CharArrayReader(chars, 1, 3);
CharArrayWriter charArrayWriter = new CharArrayWriter()) {
writeChars(charArrayReader, charArrayWriter);
}
}
@SneakyThrows
private static void writeChars(CharArrayReader charArrayReader, CharArrayWriter charArrayWriter) {
char[] buf = new char[16];
int charsRead;
while ((charsRead = charArrayReader.read(buf)) != -1) {
charArrayWriter.write(buf, 0, charsRead);
}
String data = charArrayWriter.toString();
System.out.println(data);
}
運行結果:
hello 卓立
卓立 test
立 t
5. StringReader/StringWriter
StringReader/StringWriter是輸入源和輸出目標是String的字符流,這樣描述其實也不完全正確,因爲StringWriter類中實際是使用StringBuffer存儲write方法寫入的內容的。
5.1 StringWriter
將數據通過StringWriter的write操作寫入,其實就是將數據寫入到StringWriter內部的StringBuffer中了。實現了數據的“寫出”,只不過跟CharArrayWriter類似,寫出的數據還在內存中。StringWriter可以通過如下方式構造:
public StringWriter()
public StringWriter(int initialSize)
第二個構造函數的參數initialSize,表示StringWriter中用來存儲寫入內容的StringBuffer初始化時的容量。第一個構造函數會調用StringBuffer的無參構造函數初始化一個StringBuffer實例對象。通過之前講的StringBuffer的內容可知,StringBuffer的無參構造函數,其其實內部char數組聲明的大小爲16,而如果指定大小,其內部char數組的大小即爲指定的大小。StringWriter內部使用了StringBuffer存儲寫入的內容,最終其實也等價於使用char數組,並且也可以動態擴容,本質上跟CharArrayWriter沒什麼區別。
5.2 StringReader
StringReader跟CharArrayReader一樣,也是一種適配器模式,可以將字符串包裝爲字符流。可以通過如下方式構建:
public StringReader(String s)
5.3 示例
@SneakyThrows
public static void main(String[] args) {
try (Reader inputStreamReader = new InputStreamReader(new FileInputStream("d:/hello.txt"), StandardCharsets.UTF_8)) {
StringWriter stringWriter = new StringWriter();
char[] cbuf = new char[1024];
int charsRead;
//分多次將Reader地內容讀出,並寫到writer中
while ((charsRead = inputStreamReader.read(cbuf)) != -1) {
stringWriter.write(cbuf, 0, charsRead);
}
System.out.println(stringWriter.toString());
}
String str = "卓立 test";
try (StringReader stringReader = new StringReader(str);
StringWriter stringWriter = new StringWriter()) {
writeString(stringReader, stringWriter);
}
}
@SneakyThrows
private static void writeString(StringReader stringReader, StringWriter stringWriter) {
char[] buf = new char[16];
int charsRead;
while ((charsRead = stringReader.read(buf)) != -1) {
stringWriter.write(buf, 0, charsRead);
}
String data = stringWriter.toString();
System.out.println(data);
}
運行結果:
hello 卓立
卓立 test
6. BufferedReader/BufferedWriter
BufferedReader/BufferedWriter直接繼承自Reader/Writer,是裝飾類,可爲普通普通字符流提供緩衝及按行讀寫功能
6.1 BufferedWriter
BufferedWriter提供基礎字符輸出流緩衝寫和按行寫的能力,是一種裝飾器模式。可以通過如下方式構建:
public BufferedWriter(Writer out)
public BufferedWriter(Writer out, int sz)
out表示待包裝的基礎字符輸出流,sz表示緩衝大小,第一個構造函數中,沒有指定緩衝大小,默認爲8192。BufferedWriter繼承自Writer,對於繼承自Writer類的方法不再單獨講述了,參考上面的Writer方法列表,這裏列一下BufferedWriter中新增的按行寫的方法:
public void newLine() throws IOException
該方法可以輸出當前環境下特定的換行符
6.2 BufferedReader
BufferedReader提供基礎字符輸入流緩衝讀和按行讀的能力,是一種裝飾器模式。可以通過如下方式構建:
public BufferedReader(Reader in)
public BufferedReader(Reader in, int sz)
in表示待包裝的字符輸入流,sz表示緩衝的大小,第一個構造函數中,沒有指定緩衝大小,默認爲8192。BufferedReader繼承自Reader,對於繼承自Reader類的方法不再單獨講述了,參考上面的Reader方法列表,這裏列一下BufferedReader中新增的按行讀取的方法:
public String readLine() throws IOException
該方法返回一行內容,當讀到流結尾時,返回null
6.3 示例
FileReader/FileWriter讀寫時是沒有緩衝的,也不能按行讀寫,這裏通過裝飾類BufferedReader/BufferedWriter提供緩衝和按行讀寫功能:
@SneakyThrows
public static void main(String[] args) {
try (FileWriter fileWriter = new FileWriter("d:/hello.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
bufferedWriter.write("first line");
bufferedWriter.newLine();
bufferedWriter.write("second line");
bufferedWriter.newLine();
bufferedWriter.write("third line");
}
try (FileReader fileReader = new FileReader("d:/hello.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader)) {
String line;
while ((line = bufferedReader.readLine()) != null ) {
System.out.println(line);
}
}
}
運行結果:
first line
second line
third line
7. PrintWriter
PrinterWriter直接繼承了Writer類,它有很多構造函數,可以接受文件路徑名、文件對象、OutputStream、Writer等。同時提供了很多自定義方法,可以方便的將各種基本類型數據、對象轉化爲字符串輸出,也可以實現按行輸出,格式化輸出。在輸出到文件時,可以優先選擇該類。PrinterWriter可以通過如下方式構建:
S.N. | 方法 | 說明 |
1 | public PrintWriter (Writer out) | 裝飾模式,將普通字符輸出流包裝爲PrintWriter |
2 | public PrintWriter(Writer out, boolean autoFlush) | 裝飾模式,將普通字符輸出流包裝爲PrintWriter,autoFlush表示同步緩衝區的時機,如果爲true,則在調用print、printf、format方法時會同步緩衝區,否則需要根據情況調用flush方法 |
3 | public PrintWriter(OutputStream out) | 適配器模式,將字節輸出流轉化爲PrintWriter |
4 | public PrintWriter(OutputStream out, boolean autoFlush) | 適配器模式,將字節輸出流轉化爲PrintWriter,autoFlush參數含義同上 |
5 | public PrintWriter(String fileName) throws FileNotFoundException | 構造輸出目標時文件的PrintWriter,參數fileName表示文件路徑 |
6 | public PrintWriter(String fileName, String csn) | 構造輸出目標時文件的PrintWriter,參數fileName表示文件路徑,參數csn表示字符集,用於指定輸出到文件時的編碼,上面沒指定字符集時,表示使用系統默認字符集 |
7 | public PrintWriter(File file) throws FileNotFoundException | 構造輸出目標時文件的PrintWriter,參數file爲文件對象 |
8 | public PrintWriter(File file, String csn) | 構造輸出目標時文件的PrintWriter,參數file表示文件對象,參數csn表示字符集,用於指定輸出到文件時的編碼,上面沒指定字符集時,表示使用系統默認字符集 |
public PrintWriter (Writer out) {
this(out, false);
}
public PrintWriter(Writer out, boolean autoFlush) {
super(out);
this.out = out;
this.autoFlush = autoFlush;
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
public PrintWriter(OutputStream out) {
this(out, false);
}
public PrintWriter(OutputStream out, boolean autoFlush) {
this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);
// save print stream for error propagation
if (out instanceof java.io.PrintStream) {
psOut = (PrintStream) out;
}
}
public PrintWriter(String fileName) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
false);
}
public PrintWriter(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException {
this(toCharset(csn), new File(fileName));
}
public PrintWriter(File file) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))), false);
}
public PrintWriter(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException {
this(toCharset(csn), file);
}
private PrintWriter(Charset charset, File file) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)), false);
}
- 對於參數類型時文件路徑、文件對象和OutputStream的構造函數,PrintWriter內部會構造一個BufferedWriter用於緩衝。但是對於Writer爲參數的構造函數,PrinterWriter內部就不包裝BufferedWriter了。
- 參數csn表示字符集,可以控制寫入文件時的編碼格式,是通過構造OutputStreamWriter時生效的,具體可以參見上述最後一個私有的構造函數。
- 構造方法中的參數autoFlush表示同步緩衝區的時機,如果爲true,則在調用println、printf或format方法的時候,同步緩衝區,如果沒有傳,則不會自動同步,需要根據情況調用flush方法。
下面來看一下PrintWriter類中除了繼承字Writer類之外的其它方法:
S.N. | 方法 | 說明 |
1 | public PrintWriter append(char c) | 向writer中追加一個char |
2 | public PrintWriter append(CharSequence csq) | 向writer中追加一個CharSequence |
3 | public PrintWriter append(CharSequence csq, int start, int end) | 將CharSequence c從index start到index end的子串追加到writer中 |
4 | public PrintWriter format(Locale l, String format, Object … args) | 使用指定的格式字符串和參數將格式化的字符串寫入此writer。 如果啓用了自動刷新,則調用此方法將刷新輸出緩衝區。l表示格式化過程中使用的語言環境 |
5 | public PrintWriter format(String format, Object … args) | 同上format方法,只是沒指定語言環境,而使用默認語言環境 |
6 | public void print(boolean b) | 向writer中寫入一個boolean基本類型變量,如果爲true,寫入字符串”true”,否則寫入”false” |
7 | public void print(char c) | 向writer中寫入一個char基本類型變量 |
8 | public void print(char s[]) | 向writer中寫入一個char數組 |
9 | public void print(double d) | 向writer中寫入一個double基本類型變量,寫入的實際是調用String.valueOf(d)返回的字符串 |
10 | public void print(float f) | 向writer中寫入一個float基本類型變量,寫入的實際是調用String.valueOf(f)返回的字符串 |
11 | public void print(int i) | 向writer中寫入一個int基本類型變量,寫入的實際是調用String.valueOf(i)返回的字符串 |
12 | public void print(long l) | 向writer中寫入一個long基本類型變量,寫入的實際是調用String.valueOf(l)返回的字符串 |
13 | public void print(Object obj) | 向writer中寫入一個Object對象,寫入的實際是調用String.valueOf(obj)返回的字符串 |
14 | public void print(String s) | 向writer中寫入一個String對象,如果s爲null,寫入”null” |
15 | public PrintWriter printf(Locale l, String format, Object … args) | 使用指定的格式字符串和參數將格式化的字符串寫入此writer,同4 |
16 | public PrintWriter printf(String format, Object … args) | 使用指定的格式字符串和參數將格式化的字符串寫入此writer,同5 |
17 | public void println() | 通過向Writer寫入行分隔符字符串來實現換行 |
18 | public void println(boolean x) | 向writer中寫入一個boolean基本類型變量並換行 |
19 | public void println(char x) | 向writer中寫入一個char基本類型變量並換行 |
20 | public void println(char x[]) | 向writer中寫入一個char數組並換行 |
21 | public void println(double x) | 向writer中寫入一個double基本類型變量並換行 |
22 | public void println(float x) | 向writer中寫入一個float基本類型變量並換行 |
23 | public void println(int x) | 向writer中寫入一個int基本類型變量並換行 |
24 | public void println(long x) | 向writer中寫入一個long基本類型變量並換行 |
25 | public void println(Object x) | 向writer中寫入一個Object對象並換行 |
26 | public void println(String x) | 向writer中寫入一個String對象並換行 |
示例
@SneakyThrows
public static void main(String[] args) {
List<Student> students = Lists.newArrayList();
students.add(new Student("zhuoli", 18, 99.0f));
students.add(new Student("Michael", 19, 90.0f));
students.add(new Student("Jane", 20, 60.0f));
try (PrintWriter printWriter = new PrintWriter("d:/hello.txt")) {
for (Student student : students) {
printWriter.println(student);
}
}
try (FileReader fileReader = new FileReader("d:/hello.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader)) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
}
}
運行結果:
Student(name=zhuoli, age=18, grade=99.0)
Student(name=Michael, age=19, grade=90.0)
Student(name=Jane, age=20, grade=60.0)
參考鏈接:
1. Java API
2. 《Java編程的邏輯》