引言
字符串操作是 Java 中使用最頻繁的操作颈嚼,沒有之一毛秘。其中非常常見的操作之一就是對字符串的組織,由于常見所以就衍生了多種方案阻课。比如我們要實現(xiàn) x + y = ?叫挟,方案有如下幾種
使用 + 進行字符串拼接
String s = x + " + " + y + " = " + (x + y);
使用 StringBuilder
String s = new StringBuilder()
.append(x)
.append(" + ")
.append(y)
.append(" = ")
.append(x + y)
.toString()
String::format 和 String::formatted 將格式字符串從參數(shù)中分離出來
String s = String.format("%2$d + %1$d = %3$d", x, y, x + y);
or
String s = "%2$d + %1$d = %3$d".formatted(x, y, x + y);
java.text.MessageFormat
String s = MessageFormat.format("{0} + {1} = {2}", x,y, x + y);
這四種方案雖然都可以解決,但很遺憾的是他們或多或少都有點兒缺陷限煞,尤其是面對 Java 13 引入的文本塊(Java 13 新特性—文本塊)更是束手無措抹恳。
字符串模板
為了簡化字符串的構(gòu)造和格式化,Java 21 引入字符串模板功能晰骑,該特性主要目的是為了提高在處理包含多個變量和復雜格式化要求的字符串時的可讀性和編寫效率适秩。
它的設(shè)計目標是:
通過簡單的方式表達混合變量的字符串,簡化 Java 程序的編寫硕舆。
提高混合文本和表達式的可讀性秽荞,無論文本是在單行源代碼中(如字符串字面量)還是跨越多行源代碼(如文本塊)。
通過支持對模板及其嵌入式表達式的值進行驗證和轉(zhuǎn)換抚官,提高根據(jù)用戶提供的值組成字符串并將其傳遞給其他系統(tǒng)(如構(gòu)建數(shù)據(jù)庫查詢)的 Java 程序的安全性扬跋。
允許 Java 庫定義字符串模板中使用的格式化語法(java.util.Formatter ),從而保持靈活性凌节。
簡化接受以非 Java 語言編寫的字符串(如 SQL钦听、XML 和 JSON)的 API 的使用。
支持創(chuàng)建由字面文本和嵌入式表達式計算得出的非字符串值倍奢,而無需通過中間字符串表示朴上。
該特性處理字符串的新方法稱為:Template Expressions,即:模版表達式卒煞。它是 Java 中的一種新型表達式痪宰,不僅可以執(zhí)行字符串插值,還可以編程,從而幫助開發(fā)人員安全高效地組成字符串衣撬。此外乖订,模板表達式并不局限于組成字符串——它們可以根據(jù)特定領(lǐng)域的規(guī)則將結(jié)構(gòu)化文本轉(zhuǎn)化為任何類型的對象。
STR 模板處理器
STR 是 Java 平臺定義的一種模板處理器具练。它通過用表達式的值替換模板中的每個嵌入表達式來執(zhí)行字符串插值乍构。使用 STR 的模板表達式的求值結(jié)果是一個字符串。
STR 是一個公共靜態(tài) final 字段扛点,會自動導入到每個 Java 源文件中哥遮。
我們先看一個簡單的例子:
@Test
public void STRTest() {
String sk = "死磕 Java 新特性";
String str1 = STR."{sk},就是牛";
System.out.println(str1);
}
// 結(jié)果.....
死磕 Java 新特性占键,就是牛
上面的 STR."{sk}昔善,就是牛" 就是一個模板表達式,它主要包含了三個部分:
模版處理器:STR
包含內(nèi)嵌表達式({blog})的模版
通過.把前面兩部分組合起來畔乙,形式如同方法調(diào)用
當模版表達式運行的時候,模版處理器會將模版內(nèi)容與內(nèi)嵌表達式的值組合起來翩概,生成結(jié)果牲距。
這個例子只是 STR模版處理器一個很簡單的功能,它可以做的事情有很多钥庇。
數(shù)學運算
比如上面的 x + y = ?:
@Test
public void STRTest() {
int x = 1,y =2;
String str = STR."{x} + {y} = {x + y}";
System.out.println(str);
}
這種寫法是不是簡單明了了很多牍鞠?
調(diào)用方法
STR模版處理器還可以調(diào)用方法,比如:
ini 代碼解讀復制代碼String str = STR."今天是:{?LocalDate.now()} ";
當然也可以調(diào)用我們自定義的方法:
@Test
public void STRTest() {
String str = STR."{getSkStr()},就是牛";
System.out.println(str);
}
public String getSkStr() {
return "死磕 Java 新特性";
}
訪問成員變量
STR模版處理器還可以訪問成員變量评姨,比如:
public record User(String name,Integer age) {
}
@Test
public void STRTest() {
User user = new User("大明哥",18);
String str = STR."{user.name()}今年{user.age()}";
System.out.println(str);
}
需要注意的是难述,字符串模板表達式中的嵌入表達式數(shù)量沒有限制,它從左到右依次求值吐句,就像方法調(diào)用表達式中的參數(shù)一樣胁后。例如:
@Test
public void STRTest() {
int i = 0;
String str = STR."{i++},{i++},{i++},{i++},{i++}";
System.out.println(str);
}
// 結(jié)果......
0,1,2,3,4
同時,表達式中也可以嵌入表達式:
@Test
public void STRTest() {
String name = "大明哥";
String sk = "死磕 Java 新特性";
String str = STR."{name}的{STR."{sk},就是牛..."}";
System.out.println(str);
}
// 結(jié)果......
大明哥的死磕 Java 新特性,就是牛...
但是這種嵌套的方式會比較復雜嗦枢,容易搞混攀芯,一般不推薦。
多行模板表達式
為了解決多行字符串處理的復雜性文虏,Java 13 引入文本塊(Java 13 新特性—文本塊)侣诺,它是使用三個雙引號(""")來標記字符串的開始和結(jié)束,允許字符串跨越多行而無需顯式的換行符或字符串連接氧秘。如下:
String html = """
<html>
<body>
http://skjava.com
<ul>
<li>死磕 Java 新特性</li>
<li>死磕 Java 并發(fā)</li>
<li>死磕 Netty</li>
<li>死磕 Redis</li>
</ul>
</body>
</html>
""";
如果字符串模板表達式年鸳,我們就只能拼接這串字符串了,這顯得有點兒繁瑣和麻煩丸相。而字符串模版表達式也支持多行字符串處理搔确,我們可以利用它來方便的組織html、json、xml等字符串內(nèi)容妥箕,比如這樣:
@Test
public void STRTest() {
String title = "死磕 Java - 一站式 Java 學習平臺";
String sk1 = "死磕 Java 新特性";
String sk2 = "死磕 Java 并發(fā)";
String sk3 = "死磕 Netty";
String sk4 = "死磕 Redis";
String html = STR."""
<html>
<body>
<h2>{title}</h2>
<ul>
<li>{sk1}</li>
<li>{sk2}</li>
<li>{sk3}</li>
<li>{sk4}</li>
</ul>
</body>
</html>
""";
System.out.println(html);
}
如果決定定義四個 sk 變量麻煩滥酥,可以整理為一個集合,然后調(diào)用方法生成 <li> 標簽畦幢。
FMT 模板處理器
FMT 是 Java 定義的另一種模板處理器坎吻。它除了與STR模版處理器一樣提供插值能力之外,還提供了左側(cè)的格式化處理宇葱。
Java 版本更新類型JEP更新內(nèi)容Java 17第一次預覽JEP 406引入模式匹配的 Swith 表達式作為預覽特性瘦真。Java 18第二次預覽JEP 420對其做了改進和細微調(diào)整Java 19第三次預覽JEP 427進一步優(yōu)化模式匹配的 Swith 表達式Java 20第四次預覽JEP 433Java 21正式特性JEP 441成為正式特性
如果使用 STR 模板處理器,代碼如下:
@Test
public void STRTest() {
SwitchHistory[] switchHistories = new SwitchHistory[]{
new SwitchHistory("Java 17","第一次預覽","JEP 406","引入模式匹配的 Swith 表達式作為預覽特性黍瞧。"),
new SwitchHistory("Java 18","第二次預覽","JEP 420","對其做了改進和細微調(diào)整"),
new SwitchHistory("Java 19","第三次預覽","JEP 427","進一步優(yōu)化模式匹配的 Swith 表達式"),
new SwitchHistory("Java 20","第四次預覽","JEP 433",""),
new SwitchHistory("Java 21","正式特性","JEP 441","成為正式特性"),
};
String history = STR."""
Java 版本 更新類型 JEP 更新內(nèi)容
{switchHistories[0].javaVersion()} {switchHistories[0].updateType()} {switchHistories[0].jep()} {switchHistories[0].content()}
{switchHistories[1].javaVersion()} {switchHistories[1].updateType()} {switchHistories[1].jep()} {switchHistories[1].content()}
{switchHistories[2].javaVersion()} {switchHistories[2].updateType()} {switchHistories[2].jep()} {switchHistories[2].content()}
{switchHistories[3].javaVersion()} {switchHistories[3].updateType()} {switchHistories[3].jep()} {switchHistories[3].content()}
{switchHistories[4].javaVersion()} {switchHistories[4].updateType()} {switchHistories[4].jep()} {switchHistories[4].content()}
""";
System.out.println(history);
}
得到的效果是這樣的:
版本 更新類型 JEP 更新內(nèi)容
Java 17 第一次預覽 JEP 406 引入模式匹配的 Swith 表達式作為預覽特性诸尽。
Java 18 第二次預覽 JEP 420 對其做了改進和細微調(diào)整
Java 19 第三次預覽 JEP 427 進一步優(yōu)化模式匹配的 Swith 表達式
Java 20 第四次預覽 JEP 433
Java 21 正式特性 JEP 441 成為正式特性
是不是很丑?完全對不齊印颤,沒法看您机。為了解決這個問題,就可以采用FMT模版處理器年局,在每一列左側(cè)定義格式:
@Test
public void STRTest() {
SwitchHistory[] switchHistories = new SwitchHistory[]{
new SwitchHistory("Java 17","第一次預覽","JEP 406","引入模式匹配的 Swith 表達式作為預覽特性际看。"),
new SwitchHistory("Java 18","第二次預覽","JEP 420","對其做了改進和細微調(diào)整"),
new SwitchHistory("Java 19","第三次預覽","JEP 427","進一步優(yōu)化模式匹配的 Swith 表達式"),
new SwitchHistory("Java 20","第四次預覽","JEP 433",""),
new SwitchHistory("Java 21","正式特性","JEP 441","成為正式特性"),
};
String history = FMT."""
Java 版本 更新類型 JEP 更新內(nèi)容
%-10s{switchHistories[0].javaVersion()} %-9s{switchHistories[0].updateType()} %-10s{switchHistories[0].jep()} %-20s{switchHistories[0].content()}
%-10s{switchHistories[1].javaVersion()} %-9s{switchHistories[1].updateType()} %-10s{switchHistories[1].jep()} %-20s{switchHistories[1].content()}
%-10s{switchHistories[2].javaVersion()} %-9s{switchHistories[2].updateType()} %-10s{switchHistories[2].jep()} %-20s{switchHistories[2].content()}
%-10s{switchHistories[3].javaVersion()} %-9s{switchHistories[3].updateType()} %-10s{switchHistories[3].jep()} %-20s{switchHistories[3].content()}
%-10s{switchHistories[4].javaVersion()} %-9s{switchHistories[4].updateType()} %-10s{switchHistories[4].jep()} %-20s{switchHistories[4].content()}
""";
System.out.println(history);
}