Java學習筆記

2020-08-09 14:45:17

本文參考自廖雪峯老師的官方教學:https://www.liaoxuefeng.com/wiki/1252599548343744

Java語言概述

Java介於編譯型語言和直譯語言之間。編譯型語言如C、C++,程式碼是直接編譯成機器碼執行,但是不同的平臺(x86、ARM等)CPU的指令集不同,因此,需要編譯出每一種平臺的對應機器碼。直譯語言如Python、Ruby沒有這個問題,可以由直譯器直接載入原始碼然後執行,代價是執行效率太低。而Java是將程式碼編譯成一種「位元組碼」,它類似於抽象的CPU指令,然後,針對不同平臺編寫虛擬機器(JVM),不同平臺的虛擬機器負責載入位元組碼並執行,這樣就實現了「一次編寫,到處執行」的效果。

Java的三個版本:

┌───────────────────────────┐    Java EE是企業版,在Java SE的基礎上加上了大量的API
│Java EE                    │         和庫,以便方便開發Web應用、數據庫、訊息服務等
│    ┌────────────────────┐ │          
│    │Java SE             │ │    Java SE就是標準版,包含標準的JVM和標準庫
│    │    ┌─────────────┐ │ │
│    │    │   Java ME   │ │ │    Java ME是一個針對嵌入式裝置的「瘦身版」,但是不流行
│    │    └─────────────┘ │ │
│    └────────────────────┘ │
└───────────────────────────┘

這裏是Java SE(Standard Edition)的學習筆記,Java SE是整個Java平臺的核心,包含以下知識:

image-20200530232203007

區別 jdk、jre、jvm:

  ┌─    ┌──────────────────────────────────┐ JDK: Java Development Kit
  │     │     Compiler, debugger, etc.     │ Java開發工具包,包括JRE和其他如
  │     └──────────────────────────────────┘ 編譯工具javac.exe,打包工具jar.exe等
 JDK ┌─ ┌──────────────────────────────────┐ 
  │  │  │                                  │ JRE: Java Runtime Environment
  │ JRE │      JVM + Runtime Library       │ Java 執行環境包括虛擬機器JVM和核心類庫
  │  │  │                                  │ 如果只需要執行java程式,有JRE即可
  └─ └─ └──────────────────────────────────┘  
        ┌───────┐┌───────┐┌───────┐┌───────┐ JVM: Java Virtual Machine
        │Windows││ Linux ││ macOS ││others │ Java的跨平臺特性就得益於JVM
        └───────┘└───────┘└───────┘└───────┘

簡單而言,使用JDK的開發工具完成的java程式,交給JRE去執行。

Java環境設定

Oracle的官網下載 JDK安裝。

安裝完成後設定環境變數:

設定一個JAVA_HOME的環境變數,它指向JDK的安裝目錄。

通常Eclipse/IntelliJ Idea/Tomcat等軟體就是通過搜尋JAVA_HOME變數來找到並使用安裝好的jdk。

C:\Program Files\Java\jdk-14

然後,把JAVA_HOMEbin目錄附加到系統環境變數PATH上。

%JAVA_HOME%\bin;

JAVA_HOMEbin目錄新增到PATH中是爲了在任意資料夾下都可以執行java。開啓命令提示字元視窗,輸入命令java -version,即可看到版本資訊。

bin目錄下的java可執行程式其實就是JVM,執行Java程式,就是啓動JVM,然後讓JVM執行指定的編譯後的程式碼。

之後設定CLASSPATH環境變數:

JDK在預設情況下會到當前工作目錄下(變數值用「.」表示)以及JDK的lib目錄下尋找所需的class檔案,因此如果Java程式放在這兩個目錄中,即使不設定CLASSPATH變數執行環境也可以找得到。但是如果Java程式放在其他目錄下,執行時則需要設定CLASSPATH變數。

.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar

但是廖老師在講classpath時提到不用設定,參考https://www.liaoxuefeng.com/wiki/1252599548343744/1260466914339296。他說設定classpath是因爲JVM需要知道,如果要載入一個Hello的類,應該去哪搜尋對應的Hello.class檔案。

他推薦在啓動JVM時設定classpath,就是給java命令傳入-classpath-cp參數:

java -cp .;C:\work\project1\bin Hello

不傳的話預設就是當前目錄。

我們在自己編寫的class中,會參照Java核心庫的class,這些又該去哪裏找?

就是上面教學說的lib目錄下的tools.jarrt.jar。但廖老師說,根本不需要告訴JVM如何去Java核心庫查詢class,JVM怎麼可能笨到連自己的核心庫在哪都不知道?所以儘量不要設定classpath

第一個java程式

建立一個Hello.java檔案,輸入如下程式碼:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");  //列印hello world!
    }
}

java是完全物件導向的語言,一個程式的基本單位就是class,所以最外層是一個類,類名必須和檔名一致,一般類名必須以英文字母開頭,後接字母,數位和下劃線的組合,**建議以大寫字母開頭,不同單詞之間大寫字母分隔。**如:NoteBook,不建議Note_Book。

pubilc表示該class是公開的,不寫的話這個類就無法從命令列執行。

main方法是Java程式的固定入口方法,其必須是靜態方法,方法名必須爲main,括號內的參數必須是String陣列。方法名的命名建議首字母小寫,不同單詞之間以大寫字母分隔。如:playVR,不建議play_VR。

在這個方法裏面我們寫了一條語句,語句纔是真正的執行程式碼。其向控制檯列印一個hello,world!

執行java程式:

┌──────────────────┐
│    Hello.java    │<─── source code
└──────────────────┘
          │ compile                           控制檯輸入javac Hello.java
          ▼
┌──────────────────┐
│   Hello.class    │<─── byte code
└──────────────────┘
          │ execute							   控制檯輸入java Hello
          ▼
┌──────────────────┐
│    Run on JVM    │
└──────────────────┘

Java 11新增了一個功能,它可以直接執行一個單檔案原始碼。

$ java Hello.java 
Hello, world!

Java基礎

變數:

在Java中,變數分爲兩種:基本型別的變數和參照型別的變數。

變數必須先定義後使用:

int x = 1;

定義時不寫初始值,預設賦值0

基本數據型別:

  • 整數型別:byte,short,int,long

    • byte:-128 ~ 127,佔用1個位元組
    • short: -32768 ~ 32767,佔用2個位元組
    • int: -2147483648 ~ 2147483647,佔用4個位元組
    • long: -9223372036854775808 ~ 9223372036854775807,佔用8個位元組
  • 浮點數型別(小數):float,double

    參考:https://blog.csdn.net/a327369238/article/details/52354811

    • float: -3.4x103810^{38}~3.4x103810^{38} 即−21282^{128}~21282^{128},佔用4個位元組

    • double: 最大可表示1.79x1030810^{308},佔用8個位元組

  • 字元型別:char

    佔用2個位元組,因爲除了可表示標準的ASCII外,還可以表示一個Unicode字元

  • 布爾型別:boolean

    理論上儲存布爾型別只需要1 bit,但是通常JVM內部會把boolean表示爲4位元組整數。

參照型別:

除了上述基本型別的變數,剩下的都是參照型別。例如,參照型別最常用的就是String字串:

String s = "hello";

參照型別的變數類似於C語言的指針,它內部儲存一個「地址」,指向某個物件在記憶體的位置。

常數:

定義變數的時候,如果加上final修飾符,這個變數就變成了常數:常數名通常全部大寫

final double PI = 3.14; 

常數在定義時進行初始化後就不可再次賦值,再次賦值會導致編譯錯誤。

變數作用域:

在語句塊{}中定義的變數,它有一個作用域,就是從定義處開始,到語句塊結束。

var關鍵字:

var sb = new StringBuilder();

等價於:

StringBuilder sb = new StringBuilder();

使用var定義變數,僅僅是少寫了變數型別而已。

運算子:

在Java的計算表達式中,

數值計算運算優先順序從高到低依次是:

  • ()
  • ! ~ ++ --
  • * / %
  • + -
  • << >> >>> 移位運算(只對於整數)
  • & 位運算
  • |
  • += -= *= /=

關係運算符的優先順序從高到低依次是:

  • !
  • >>=<<=
  • ==!=
  • &&
  • ||

三元運算b ? x : y會首先計算b,如果btrue,則只計算x,否則,只計算y。此外,xy的型別必須相同,因爲返回值不是boolean,而是xy之一。

型別轉換:

兩個整數相除只能得到結果的整數部分。

int x = 1 / 10   //結果是0

在運算過程中,如果參與運算的數型別不一致,那麼計算結果爲較大型別

short s = 1234;
int i = 123456;
float x = s + i; // s自動轉型爲int   //結果自動轉爲float

強制型別轉換:

int i = 12345;
short s = (short) i; // 12345
int i2 = 12345678;
short s2 = (short) i2; // 24910  結果是錯的
int n3 = (int) (12.7); // 12   浮點數的小數部分會被丟掉
int n4 = (int) 1.2e20; // 2147483647  溢位時,返回整型的最大值

浮點數誤差:

浮點數常常無法精確表示。例如:浮點數0.1在計算機中就無法精確表示,因爲十進制的0.1換算成二進制是一個無限回圈小數,很顯然,無論使用float還是double,都只能儲存一個0.1的近似值。但是,0.5這個浮點數又可以精確地表示。

double x = 1.0 / 10;        //0.1 
double y = 1 - 9.0 / 10;    //0.09999999999999998 

由於浮點數存在運算誤差,所以比較兩個浮點數是否相等常常會出現錯誤的結果。正確的比較方法是判斷兩個浮點數之差的絕對值是否小於一個很小的數.

溢位:

整數運算在除數爲0時會報錯,而浮點數運算在除數爲0時,不會報錯,但會返回幾個特殊值:

  • NaN表示Not a Number
  • Infinity表示無窮大
  • -Infinity表示負無窮大

短路運算

布爾運算的一個重要特點是短路運算。如果一個布爾運算的表達式能提前確定結果,則後續的計算不再執行,直接返回結果。

字元與字串:

Java在記憶體中總是使用Unicode表示字元,所以,一個字元佔用兩個位元組。

在java中,用雙引號"..."表示字串。一個字串可以儲存0個到任意個字元。

常見的跳脫字元包括:

  • \" 表示字元"
  • \' 表示字元'
  • \\ 表示字元\
  • \n 表示換行符
  • \r 表示回車符
  • \t 表示Tab
  • \u#### 表示一個Unicode編碼的字元

Java的編譯器對字串做了特殊照顧,可以使用+連線任意字串和其他數據型別。其他型別會先轉換成字串再拼接。

從Java 13開始,字串可以用"""..."""表示多行字串(Text Blocks)。

字串不可變特性

字串賦值變的不是字串,而是變數的指向。

看一下這樣一段程式碼:

String s = "hello";
String t = s;
s = "world";
System.out.println(t); // t是"hello"還是"world"

執行String s = "hello";時,JVM虛擬機器先建立字串"hello"(其實就是char[]陣列),然後,把字串變數s指向它:

      s
      │
      ▼
┌───┬───────────┬───┐
│   │  "hello"  │   │
└───┴───────────┴───┘

執行String t = s;時,t也指向"hello"字串。

之後執行s = "world";時,JVM虛擬機器先建立字串"world",然後,把字串變數s指向它:

      s ──────────────┐
                      │
                      ▼
┌───┬───────────┬───┬───────────┬───┐
│   │  "hello"  │   │  "world"  │   │
└───┴───────────┴───┴───────────┴───┘

原來的字串"hello"還在,只是我們無法通過變數s存取它而已。因此,字串的不可變是指字串內容不可變

所以最後s指向"world"t指向"hello"

空值null

參照型別的變數可以指向一個空值null,它表示不存在,即該變數不指向任何物件。

String s1 = null; // s1是null
String s2; // 沒有賦初值值,s2也是null
String s3 = s1; // s3也是null
String s4 = ""; // s4指向空字串,不是null
陣列

建立:

int[] ns = new int[5];  //指定陣列大小建立
int[] ns = new int[] { 68, 79, 91, 85, 62 };   //指定內容建立陣列
int[] ns = { 68, 79, 91, 85, 62 };  //簡寫

Java的陣列有幾個特點:

  • 陣列所有元素初始化爲預設值,整型都是0,浮點型是0.0,布爾型是false
  • 陣列一旦建立後,大小就不可改變。

要存取陣列中的某一個元素,需要使用索引。陣列索引從0開始。

可以用陣列變數.length獲取陣列大小。

陣列是參照型別:

執行ns = new int[] { 68, 79, 91, 85, 62 };時,它指向一個5個元素的陣列:

     ns
      │
      ▼
┌───┬───┬───┬───┬───┬───┬───┐
│   │68 │79 │91 │85 │62 │   │
└───┴───┴───┴───┴───┴───┴───┘

之後執行ns = new int[] { 1, 2, 3 };時,它指向一個新的3個元素的陣列:

     ns ──────────────────────┐
                              │
                              ▼
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│   │68 │79 │91 │85 │62 │   │ 1 │ 2 │ 3 │   │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

和字串一樣,原有的5個元素的陣列並沒有改變,只是無法通過變數ns參照到它們而已。

直接列印陣列變數,得到的是陣列在JVM中的參照地址:

int[] ns = { 1, 1, 2, 3, 5, 8 };
System.out.println(ns); // 類似 [I@7852e922

字串陣列

定義一個字串陣列:

String[] names = {
    "ABC", "XYZ", "zoo"
};

對於String[]型別的陣列變數names,它實際上包含3個元素,但每個元素都指向某個字串物件:

          ┌─────────────────────────┐
    names │   ┌─────────────────────┼───────────┐
      │   │   │                     │           │
      ▼   │   │                     ▼           ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │░░░│░░░│░░░│   │ "ABC" │   │ "XYZ" │   │ "zoo" │   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                 ▲
      └─────────────────┘

names[1]進行賦值,例如names[1] = "cat";,效果如下:

          ┌─────────────────────────────────────────────────┐
    names │   ┌─────────────────────────────────┐           │
      │   │   │                                 │           │
      ▼   │   │                                 ▼           ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │░░░│░░░│░░░│   │ "ABC" │   │ "XYZ" │   │ "zoo" │   │ "cat" │   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                 ▲
      └─────────────────┘

流程控制

輸入與輸出:

一般使用System.out.println()來向螢幕輸出一些內容。

println是print line的縮寫,表示輸出並換行,如果輸出後不想換行,可以用print()

如果要把數據顯示成我們期望的格式,就需要使用格式化輸出的功能。格式化輸出使用System.out.printf(),通過使用佔位符%?printf()可以把後面的參數格式化成指定格式:

double d = 3.1415926;
System.out.printf("%.2f\n", d); // 顯示兩位小數3.14
System.out.printf("%.4f\n", d); // 顯示4位元小數3.1416

Java的格式化功能提供了多種佔位符:

佔位符 說明
%d 格式化輸出整數
%x 格式化輸出十六進制整數
%f 格式化輸出浮點數
%e 格式化輸出科學計數法表示的浮點數
%s 格式化字串

Java提供Scanner物件來方便輸入,讀取對應的型別可以使用:scanner.nextLine() / nextInt() / nextDouble() / …

下面 下麪是一個例子:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in); // 建立Scanner物件
        System.out.print("Input your name: "); // 列印提示
        String name = scanner.nextLine(); // 讀取一行輸入並獲取字串
        System.out.print("Input your age: "); // 列印提示
        int age = scanner.nextInt(); // 讀取一行輸入並獲取整數
        System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化輸出
    }
}
判斷:

if判斷:

if (條件) {
    // 條件滿足時執行
}else {
    //條件不滿足時執行
}

注意:在Java中,判斷值型別的變數是否相等,可以使用==運算子。但是,要判斷參照型別的變數內容是否相等,必須使用equals()方法。

String s1 = "hello";
String s2 = "HELLO".toLowerCase();
if (s1.equals(s2)) {
    System.out.println("s1 equals s2");
} else {
    System.out.println("s1 not equals s2");
}

注意:執行語句s1.equals(s2)時,如果變數s1null,會報NullPointerException,要避免這個錯誤我們可以用短路運算子:

String s1 = null;
if (s1 != null && s1.equals("hello")) {
	System.out.println("hello");
}

switch判斷:

switch (option) {
    case 1:
        System.out.println("Selected 1");
        break;
    case 2:
        System.out.println("Selected 2");
        break;
    case 3:
        System.out.println("Selected 3");
        break;
    default:
        System.out.println("Not selected");
        break;
}

switch的匹配項裏面可以匹配字串。

Java 12開始,switch語句升級爲更簡潔的表達式語法,使用類似模式匹配(Pattern Matching)的方法,保證只有一種路徑會被執行,並且不需要break語句:

switch (fruit) {
    case "apple" -> System.out.println("Selected apple");
    case "pear" -> System.out.println("Selected pear");
    case "mango" -> {
        System.out.println("Selected mango");
        System.out.println("Good choice!");
    }
    default -> System.out.println("No fruit selected");
}

新的switch語法還可以直接返回值,如果還需要複雜的語句處理後返回,可以用yield語句:

int opt = switch (fruit) {
    case "apple" -> 1;
    case "pear", "mango" -> 2;
    default -> {
        int code = fruit.hashCode();
        yield code; // switch語句返回值
    }
};  //注意分號結束
回圈:

while回圈:

while (條件表達式) {
    條件滿足時執行回圈語句
}

while回圈是先判斷回圈條件,再回圈,因此,有可能一次回圈都不做。

do while回圈則是先執行回圈,再判斷條件,條件滿足時繼續回圈,條件不滿足時退出。

do {
    執行回圈語句
} while (條件表達式);

for回圈:

for (初始條件; 回圈檢測條件; 回圈後更新計數器) {
    // 執行語句
}

for each回圈:

for回圈常用於遍歷陣列:

int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
    System.out.println(ns[i]);
}

for each回圈可以更簡單地遍歷陣列:

int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
    System.out.println(n);
}

除了陣列外,for each回圈能夠遍歷所有「可迭代」的數據型別,包括ListMap等。

break和continue:

break會跳出當前回圈,也就是整個回圈都不會執行了。而continue則是提前結束本次回圈,直接繼續執行下次回圈。

陣列操作

對於陣列的回圈列印,除了可以用回圈,Java標準庫還提供了Arrays.toString()方法:

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 1, 2, 3, 5, 8 };
        System.out.println(Arrays.toString(ns));
    }
}
陣列排序:

氣泡排序演算法:

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
        // 排序前:
        System.out.println(Arrays.toString(ns));
        for (int i = 0; i < ns.length - 1; i++) {
            for (int j = 0; j < ns.length - i - 1; j++) {
                if (ns[j] > ns[j+1]) {
                    // 交換ns[j]和ns[j+1]:
                    int tmp = ns[j];
                    ns[j] = ns[j+1];
                    ns[j+1] = tmp;
                }
            }
        }
        //Arrays.sort(ns);    //Java的標準庫已經內建的排序功能
        // 排序後:
        System.out.println(Arrays.toString(ns));
    }
}

注意:對陣列排序實際上修改了陣列本身。

在記憶體中,一個整型陣列表示如下:

      ┌───┬───┬───┬───┐
ns───>│ 9 │ 3 │ 6 │ 5 │
      └───┴───┴───┴───┘

當我們呼叫Arrays.sort(ns);後,這個整型陣列在記憶體中變爲:

      ┌───┬───┬───┬───┐
ns───>│ 3 │ 5 │ 6 │ 9 │
      └───┴───┴───┴───┘
二維陣列

定義:

int[][] ns = {
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    { 9, 10, 11, 12 }
};

呼叫System.out.println(ns.length);,輸出爲3。其記憶體結構如下:

                    ┌───┬───┬───┬───┐
         ┌───┐  ┌──>│ 1 │ 2 │ 3 │ 4 │
ns ─────>│░░░│──┘   └───┴───┴───┴───┘
         ├───┤      ┌───┬───┬───┬───┐
         │░░░│─────>│ 5 │ 6 │ 7 │ 8 │
         ├───┤      └───┴───┴───┴───┘
         │░░░│──┐   ┌───┬───┬───┬───┐
         └───┘  └──>│ 9 │10 │11 │12 │
                    └───┴───┴───┴───┘

所以,存取二維陣列的某個元素需要使用array[row][col]

列印二維陣列可以用兩層for回圈,也可以使用Java標準庫的Arrays.deepToString(ns)

命令列參數

main方法可以接受一個命令列參數,它是一個String[]陣列。這個命令列參數由JVM接收使用者輸入並傳給main方法。

比如在命令列執行程式Main,給他傳入一個參數-version:

$ java Main -version

以下程式就可以把-version返回給我們:

public class Main {
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

物件導向基礎

這裏的內容寫成了xmind思維導圖。有時間再整理。

Java核心類

String

在Java中,String是一個參照型別,它本身也是一個class

String s = "Hello!";

實際上字串在String內部是通過一個char[]陣列表示的:

String s = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

Java字串的一個重要特點就是字串不可變。這種不可變性是通過內部的private final char[]欄位,以及沒有任何修改char[]的方法實現的。

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

java字串是參照變數,所以不能用==進行比較。但是上面的程式==輸出爲true。原因是

Java編譯器在編譯期,會自動把所有相同的字串當作一個物件放入常數池,自然s1s2的參照就是相同的。

字串操作:

  • //大寫
    s.toUpperCase()  
    
  • //小寫
    s.toLowerCase()  
    
  • //比較
    s.equals("hello")  
    
  • //比較忽略大小寫
    s.equalsIgnoreCase("Hello") 
    
  • // 是否包含子串:
    "Hello".contains("ll"); // true
    
  • //搜尋子串
    "Hello".indexOf("l"); // 2
    "Hello".lastIndexOf("l"); // 3
    "Hello".startsWith("He"); // true
    "Hello".endsWith("lo"); // true
    
  • //提取子串,索引號是從0開始的
    "Hello".substring(2); // "llo"
    "Hello".substring(2, 4); "ll"
    
  • //trim()方法可以移除字串首尾空白字元。空白字元包括空格,\t,\r,\n
    "  \tHello\r\n ".trim(); // "Hello"
    
  • //isEmpty()和isBlank()判斷字串是否爲空和空白字串
    "".isEmpty(); // true,因爲字串長度爲0
    "  ".isEmpty(); // false,因爲字串長度不爲0
    "  \n".isBlank(); // true,因爲只包含空白字元
    " Hello ".isBlank(); // false,因爲包含非空白字元
    
  • //替換字串
    String s = "hello";
    s.replace('l', 'w'); // "hewwo",所有字元'l'被替換爲'w'
    s.replace("ll", "~~"); // "he~~o",所有子串"ll"被替換爲"~~"
    
  • //正則表達式替換字串
    String s = "A,,B;C ,D";
    s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
    
  • //split()方法分割字串
    String s = "A,B,C,D";
    String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
    
  • //拼接字串使用靜態方法join()
    String[] arr = {"A", "B", "C"};
    String s = String.join("***", arr); // "A***B***C"
    
  • 格式化字串

    //formatted()方法和format()靜態方法,傳入其他參數,替換佔位符,然後生成新的字串
    String s = "Hi %s, your score is %d!";
    System.out.println(s.formatted("Alice", 80));
    System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
    

    有幾個佔位符,後面就傳入幾個參數。參數型別要和佔位符一致。

    • %s:顯示字串;
    • %d:顯示整數;
    • %x:顯示十六進制整數;
    • %f:顯示浮點數。可以帶格式,如%.2f表示顯示兩位小數。
  • 型別轉換

    //靜態方法valueOf()將其他型別轉換成字串
    String.valueOf(123); // "123"
    String.valueOf(45.67); // "45.67"
    String.valueOf(true); // "true"
    

    要把字串轉換爲其他型別,就需要根據情況。

    int n1 = Integer.parseInt("123"); // 123
    boolean b1 = Boolean.parseBoolean("true"); // true
    

    Stringchar[]型別互相轉換:

    char[] cs = "Hello".toCharArray(); // String -> char[]
    String s = new String(cs); // char[] -> String
    

    new String(char[])建立新的String範例時,它並不會直接參照傳入的char[]陣列,而是會複製一份,所以,修改外部的char[]陣列不會影響String範例內部的char[]陣列。

字元編碼:

在Java中,char型別實際上就是兩個位元組的Unicode編碼。String也總是以Unicode編碼表示。

如果我們要手動把字串轉換成其他編碼,可以這樣做:

byte[] b = "Hello".getBytes("UTF-8"); // 按UTF-8編碼轉換
byte[] b = "Hello".getBytes("GBK"); // 按GBK編碼轉換

注意:轉換編碼後,就不再是char型別,而是byte型別表示的陣列。

如果要把已知編碼的byte[]轉換爲String,可以這樣做:

byte[] b = ...
String s1 = new String(b, "GBK"); // 按GBK轉換
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8轉換
StringBuilder
String s = "";
for (int i = 0; i < 1000; i++) {
    s = s + "," + i;
}

上述程式在拼接字串的時候,Java每次都會建立新的字串,扔掉舊的字串。這樣,絕大部分字串都是臨時物件,不但浪費記憶體,還會影響GC效率。

爲了能高效拼接字串,Java標準庫提供了StringBuilder,可以預分配快取區:

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',')
      .append(i);
}
sb.insert(0,"index");
String s = sb.toString();

檢視StringBuilder的原始碼,可以發現,進行鏈式操作的關鍵是,定義的append()方法會返回this,這樣,就可以不斷呼叫自身的其他方法。