創:良許 源:良許Linux
相信大家在使用各種各樣強大的 IDE 寫代碼時都會注意到,代碼中各種類型的關鍵字會用獨特的顏色標記出來,然后形成一套語法高亮規則。這樣不僅美觀,而且方便代碼的閱讀。
而在上古神器 Vim 中,我們通常看到的是黑底白字,沒有任何其它顏色。難道我們的 Vim 就這么枯燥乏味嗎?當然不是,Vim 隱藏了非常多非常實用的功能與技巧,不是老司機根本就不知道。下面來詳細介紹。
在開始之前,我們要先檢查一下系統中是否安裝了 Vim 編輯器。一般來說 Ubuntu 系統是默認自帶的,沒有的小伙伴請先自行安裝。
$ sudo apt-get install vim
然后我們運行以下命令來查看安裝好的 Vim 編輯器的版本。
$ vim -version
接下來,我們就可以為 Vim 編輯器添加配色方案。為了能看出效果,我們準備了一個名為 login.sh 的 bash 文件,它的內容如下:
login.sh
!/bin/bash echo "Type your username" read username echo "Type your password" read password if [[ ( $username=="admin" && $password=="secret" ) ]]; then echo "Authorized user" else echo "Unauthorized user" fi
運行以下命令使用 Vim 編輯器打開該文件:
$ vim login.sh
啟用/禁用語法高亮顯示
有些發行版的 Vim 編輯器已經幫你默認啟用語法高亮,而有些發行版卻沒有。如果沒有默認打開語法高亮,那么我們就需要自行打開。
打開的方法其實也很簡單。在 Vim 編輯器中打開 login.sh 文件后,按 ESC 鍵并鍵入 :syntax on ,語法高亮顯示就打開了,非常方便。
同樣,關閉語法高亮也很簡單,只需按 ESC 鍵并鍵入 :syntax off即可。
永久啟用或禁用語法高亮顯示
剛剛那種打開語法高亮的方法只是暫時性的,一旦當前文件關閉,下次打開文件就需要重新打開語法高亮。
如果想要永久啟用或禁用語法高亮顯示,就需要修改 .vimrc 文件。首先,我們使用 Vim 打開 .vimrc 文件。
$ sudo vim ~ / .vimrc
然后,在打開的文件中添加一句 syntax on 命令,代表永久啟用語法高亮顯示。最后再輸入 :wq 保存并關閉文件。
如果想要永久禁用語法高亮,則添加 syntax off 命令即可。
改變配色方案
在默認情況下,打開語法高亮后 Vim 為你應用了一套默認的配色方案,而實際上 Vim 還有很多套配色方案可供我們使用,我們可以自行去修改。
安裝 Vim 后,顏色方案文件位于 /usr/share/vim/vim*/colors/中。我們可以運行以下命令來查看 Vim 配色方案文件的列表。
$ ls -l /usr/share/vim/vim*/colors/
可以看出 Vim 為我們提供了非常多的配色方案,我們可以根據自己的需要自由選擇。假設,我們有一個 hello.html 的 html 文件,我們想要將它的的配色方案改為 morning 類型。
hello.html
<html> <head> <title> Welcome </title> </head> <body> <p> Hello Everybody, Welcome to our site </p> </body> </html>
我們首先運行以下命令在 Vim 中打開這個 html 文件。
$ vim hello.html
按 ESC 并鍵入 :colorscheme morning ,這樣我們就更改了文件當前的顏色方案。
應用新顏色方案后,效果如下圖顯示。但這種更改依然是暫時的,關閉編輯器后配色方案將被重置。
如果想要永久設置特定的配色方案及背景,那么我們需要再次打開 .vimrc 文件并添加以下命令文本。下面配置的是夜晚配色方案,會將背景顏色設置為深色。
color evening set background=dark
之后再重新打開剛才的文件,效果就會變成如下圖所示。
根據語言選擇配色方案
Vim 編輯器支持多種語言的語法,例如 PHP,Perl,Python,awk 等。根據不同語言,它會應用一套相應語言的配色方案。
比如現在有一個名為 average.py 的 Python 文件,我們用 Vim 編輯器打開它。通過 Vim 編輯器的默認配色方案,文件的顯示效果如下圖所示。
這是一個 Python 文件,但如果我們想以 Perl 的語法風格來顯示,要怎么操作?我們只需輸入 :set syntax=perl ,按 Enter 鍵就可以了,效果如下圖所示。
:set syntax=perl
自定義配色方案
前面所介紹的那些配色方案,其實都是系統自帶的,我們只是選擇一下而已。如果我們想要個性化一點,自定義一套配色方案,要怎么操作?強大的 Vim 也是支持你這個需求的!
一般而言,語法需要高亮的無非就是 變量、注釋、常量 之類的,Vim 編輯器支持的語法高亮組有如下 9 個:
Group Name
Description
Identifier
變量
Statement
關鍵字,例如 if,else,do,while等
Comment
注釋
Type
數據類型,如 int、double、string等
PreProc
預處理器語句,比如 #include
Constant
常量,如數字、帶引號的字符串、真/假等
Special
特殊符號,如“”、“”等
Underlined
帶下劃線的文本
Error
錯誤
下面舉個例子。在 Vim 編輯器中打開名為 leap.py 的 Python 腳本文件。默認情況下,該文件的語法高亮顯示如下圖所示。
如果我們想把關鍵字變為紅色,該怎么操作?只需輸入 :hi Statement ctermfg=red 即可。這時,if 和 else 的顏色將變為紅色。
:hi Statement ctermfg=red
當然,對于注釋、常量、數據類型,我們也可以使用同樣的方法來改變顏色。這樣一來,你就會形成一套自己的配色方案,感覺是不是很酸爽?
最后,我自己是一名從事了多年開發的Python老程序員,辭職目前在做自己的Python私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的Python學習干貨,可以送給每一位喜歡Python的小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:01,即可免費獲取。
abbr 全稱是 abbreviations,意思是縮寫。應用場景也很簡單,為一些文章中的縮寫增加注釋。
以前在文章中對于縮寫的解釋經常會這么做:
DAU(Daily Active User),日活躍用戶數 ......
那我們用 abbr 標簽呢?
<abbr title="Daily Active User">
DAU
</abbr>
<span>,日活躍用戶數 ......</span>
展示的效果如下:
這個標簽就可以把全稱隱藏掉,弱化信息量,讓真正不知道該縮寫的用戶主動去獲取縮寫的具體意思,這個在 markdown 里經常會出現。
<mark/> 在 markdown 中也是很常用的,用于將包裹的文本高亮展示。
<mark>高亮文本</mark>
效果如下:
如果全文統一高亮樣式,可以專門對 mark 標簽進行樣式重置,這樣就不用對你用的每個 div 加一個 highlight 的類名了,又不語義化,又徒增文檔大小。
<sup/>和<sub/>分別表示上標和下標,在 markdown 中出現得也很頻繁,比如數學公式和引用。
<div>3<sup>[2]</sup></div>
<div>4<sub>2</sub></div>
效果如下:
上標和下標的樣式原理也比較簡單,主要就是利用了 vertical-align 的 top 和 sub 屬性值,然后將字號縮小,不過有現成的標簽,干嘛不用呢?
figure 是用于包裹其它標簽的內容的,然后再利用另一個標簽 figcaption ,可以對包裹的內容進行一個文本描述,例如:
<figure>
<img src="/media/cc0-images/elephant-660-480.jpg"
alt="大象">
<figcaption>這是一張大象的照片</figcaption>
</figure>
效果如下:
那要是圖片掛了呢?
再友好點處理,我們把 img 標簽的 alt 屬性去掉。
漂亮,終于把我一直厭煩的圖裂 icon 給干掉了,樣式還巨好看。
當然不止能包裹 img 標簽,其它任何都是可以的。
嘿嘿,給大家在本文來個實戰,下面這個可以點擊,樣式也是利用了 figure 這個標簽。
我是figure標簽產生的
說到 <progress/> 這個標簽就很有意思了,去年有段時間我做的業務里涉及到了進度條,當時是前同事做的,然后有一些性能問題,我就在研究如何優化,減少進度條改變帶來的性能問題。
雖然最后問題是解決了,但是也有幸收到了張鑫旭大佬的評論,他告訴我 progress 這個標簽就足夠了,既有語義化,又有進度條的功能,性能還好,兼容性也很不錯。后來經過一番嘗試,還真是,當時是我孤陋寡聞了,也安利給大家。
<!-- 進度條最大值為100,當前進度為60,即60% -->
<progress max="100" value="60"/>
瀏覽器自帶的樣式就已經很好看了,效果如下:
業務中我們也就可以通過控制 value 屬性,來改變進度條的進度了。
area 這個標簽也非常有意思,它的作用是為圖片提供點擊熱區,可以自己規定一張圖的哪些區域可點擊,且點擊后跳轉的鏈接,也可以設置成點擊下載文件,我們來舉個例子:
<img src="example.png" width="100" height="100" alt="" usemap="#map">
<map name="map">
<area shape="rect" coords="0,0,100,50" alt="baidu" href="https://www.baidu.com">
<area shape="rect" coords="0,50,100,100" alt="sougou" href="https://www.sogou.com/">
</map>
area 一般要搭配 map 標簽一起使用,每個 area 標簽表示一個熱區,例如上面代碼中,我們定義了兩個熱區,熱區形狀都為rect(矩形),他們的熱區分別是:
我們都知道,默認的坐標軸是這樣的:
因此,我們劃分的兩個熱區就是:
最后再來看一下我們的實際效果:
i
details 字面意思是 "詳情",在 markdown 里也經常用,用該標簽包裹了的內容默認會被隱藏,只留下一個簡述的文字,我們點擊以后才會展示詳細的內容。
<details>
<p>我是一段被隱藏的內容</p>
</details>
效果如下:
這還沒有加任何一行的 js 代碼,我們點擊后,details 標簽上會多一個 open 的屬性,被隱藏的內容就展示出來了。
默認情況下,簡要文字為 "詳情",想要修改這個文字,要搭配 summary 標簽來使用。
<details>
<summary>點擊查看更多</summary>
<p>我是一段被隱藏的內容</p>
</details>
就搞定了!
瀏覽器自帶彈窗方法 alert、confirm、prompt,樣式固定且每個瀏覽器不同,同時還會阻塞頁面運行,除了這個還提供了一個 dialog 標簽,它的使用方式有點類似于現在各大組件庫的 Modal 組件了,瀏覽器還為該標簽提供了原生的 dom 方法:showModal、close,可以直接控制彈窗的展示和隱藏。
<dialog id="dialog">
<input type="text">
<button id="close">ok</button>
</dialog>
<button id="openBtn">打開彈框</button>
<script>
const dialog=document.getElementById('dialog')
const openBtn=document.getElementById('openBtn')
const closeBtn=document.getElementById('close')
openBtn.addEventListener('click', ()=> {
// 打開彈框
dialog.showModal()
})
closeBtn.addEventListener('click', ()=> {
// 隱藏彈框
dialog.close()
})
</script>
效果如下:
細心的你有沒有發現,這原生的彈框還自帶背景蒙層,點擊是關閉不掉的,但起碼它不會阻塞頁面。
然后我們在彈窗展示時,也可以通過 esc 鍵來關閉彈窗。
datalist 是用于給輸入框提供可選值的一個列表標簽,類似咱們常用的 Select 組件。
我可以用其實現一個 "輸入聯想" 的功能。
<label> 輸入C開頭的英文單詞:</label>
<input list="c_words"/>
<datalist id="c_words">
<option value="China">
<option value="Click">
<option value="Close">
<option value="Const">
<option value="Count">
</datalist>
來試一試:
剛點擊時會把所有推薦的選項都列出來,然后根據后面輸入的內容,會過濾掉不匹配的選項,比如我輸入 cl,會過濾掉不是 cl 開頭的單詞,最后只剩下 Click 和 Close 了。
最后我發現,他這個下拉框有點好看啊?為啥這原生的 input 框默認樣式那么丑,啥時候改改。
fieldset 標簽是用于分組管理 form 表單內的元素的,若 fieldset 設置了 disabled 屬性,則被其包裹的所有表單元素都會被禁用置灰,且不會隨著表單一起提交上去,是的就成了擺設。
什么意思呢?看個例子:
<form action="/example">
<fieldset disabled>
<legend>被禁用區域</legend>
<label>ID:</label>
<input type="text" name="id" value="1">
<label>郵箱:</label>
<input type="text" name="email" value="1234567@163.com">
</fieldset>
<label>名字:</label>
<input type="text" name="name">
<button type="submit">提交</button>
</form>
這里我們把 ID 和 郵箱 的表單包裹了起來,且設置了 disabled,只開放了一個 name 的輸入控件,此時界面如下:
可以看到除了 name 輸入框,其它的兩個輸入框都被禁用了,此時點提交會是什么樣子呢?
嗯,只提交了 name 字段。
這個標簽是在瀏覽器不支持或禁用了 javascript 時才展示的,大多用于對 js 強依賴的應用,比如現在大部分的 SPA 頁面,一旦不支持 javascript,頁面基本上什么內容都沒了,此時可以靠這個標簽做友好提示。
一般我們不需要特地去使用,大多都是在打包過程中自動插入到 html 靜態文件里去的,例如:
// init.js
const root=document.getElementById('root')
const button=document.createElement('button')
button.innerText='點擊出彈窗'
root.appendChild(button)
<!-- index.html -->
<script defer src="./init.js"></script>
<noscript>
不好意思,你的瀏覽器不支持或禁用了 JavaScript,請更換瀏覽器或啟用 JavaScript
</noscript>
<div id="root"></div>
未禁用 javascript 時,頁面是這樣的:
禁用了 javascript 時,是這樣的:
文要點
在 QCon 紐約大會的演講“ Java 的未來”中,Java 語言架構師 Brian Goetz 帶我們快速瀏覽了 Java 語言近期和未來的一些特性。在本文中,他深入探討了文本塊。
作為一項預覽特性,Java SE 13(2019 年 9 月)引入了文本塊,旨在減輕在 Java 中聲明和使用多行字符串字面量的痛苦。
隨后,第二個預覽版本對它做了一些細微的改進,并計劃在 Java SE 15(2020 年 9 月)中成為 Java 語言的一個永久特性。
文本塊是可以包含多行文本的字符串字面量,如下所示:
復制代碼
String address=""" 25 Main Street Anytown, USA, 12345 """;
在下面這個簡單的示例中,變量 address 是一個兩行的字符串,每行后面都有行終止符。如果沒有文本塊,我們必須這樣寫:
復制代碼
String address="25 Main Street\n" + "Anytown, USA, 12345\n";
或
復制代碼
String address="25 Main Street\nAnytown, USA, 12345\n";
每個 Java 開發人員都知道,這些寫法都非常麻煩。但是,更重要的是,它們更容易出錯(很容易忘記\n 并且發現不了),也更難閱讀(因為語言語法與字符串的內容混在一起)。由于文本塊通常沒有轉義字符和其他語法干擾,讀者更容易看明白字符串的內容。
字符串字面量中最常見的轉義字符是換行符(\n),文本塊支持直接表示多行字符串而不需要換行符。除了換行符之外,另一個最常用的轉義字符是雙引號("),這個必須轉義,因為它與字符串字面量分隔符沖突。文本塊不需要這樣,因為單引號與三引號文本塊分隔符并不沖突。
有人可能會說,這個特性應該叫“多行字符串字面量”(很多人可能會這樣稱呼它)。但是,我們選擇了一個不同的名稱文本塊,為的是強調:文本塊不是不相關行的集合,而是嵌入到 Java 程序中的二維文本塊。為了說明這里所說的“二維”是什么意思,我們舉一個稍微結構化一些的例子,在這個例子中,文本塊是一個 XML 片段。(同樣的考量也適用于其他“語言”的“程序”片段,如 SQL、HTML、JSON,甚至 Java,作為字面量嵌入到 Java 程序中。)
復制代碼
void m() { System.out.println(""" <person> <firstName>Bob</firstName> <lastName>Jones</lastName> </person> """);}
作者希望這段代碼打印什么?雖然我們無法讀取他們的想法,但似乎不太可能是想讓 XML 塊縮進 21 個空格;更可能是,這 21 個空格只是用來將文本塊與周圍的代碼對齊。另一方面,幾乎可以肯定,作者的意圖是輸出的第二行應該比第一行多縮進四個空格。此外,即使作者確實需要縮進 21 個空格,當程序修改、周圍代碼的縮進發生變化時,又會發生什么呢?我們不希望輸出的縮進僅僅因為源代碼被重新格式化而改變,也不希望文本塊因為沒有合理對齊而看起來“格格不入”。
從這個例子中,我們可以看到,嵌入在程序源代碼中的多行文本塊的自然縮進來自于文本塊各行行之間預期的相對縮進,以及文本塊與周圍代碼之間的相對縮進。我們希望字符串字面量與代碼對齊(因為如果不對齊就會顯得格格不入),我們希望字符串的行可以反映出行之間的相對縮進,但是這兩個縮進來源(我們可以稱之為附帶的和必需的)在源程序的表示中必然混雜在一起。(傳統的字符串字面量沒有這個問題,因為它們不能跨行,所以不用為了使內容對齊而在文本中添加額外的前導空格。)
解決這個問題的一種方法是使用庫方法,我們可以將它應用于多行字符串字面量,比如 Kotlin 的 trimIndent 方法,而 Java 確實也提供了這樣一個方法: String::stripIndent 。但是,因為這是很常見的問題,所以 Java 更進一步,會在編譯時自動去除附帶的縮進。
為了理順附帶的和必需的縮進,我們可以想象下,在包含整個代碼段的 XML 代碼段周圍繪制一個最小的矩形,并將這個矩形里的內容視為一個二維文本塊。這個“魔法矩形”是文本塊的內容,它反映了文本塊行之間的相對縮進,但是忽略了它所在的程序是如何縮進的。
這個“魔法矩形”的類比或許可以幫助我們理解文本塊的工作方式,但是細節要更微妙一些,因為我們可能會希望更好地控制哪些縮進是附帶的,哪些是必需的。可以使用結尾分隔符相對于內容的位置來平衡附帶縮進和必需縮進。
文本塊使用三引號(""")作為其開始和結束分隔符,開始分隔符所在行的其余部分必須為空白。文本塊的內容從下一行開始,一直延續到結束分隔符。塊內容的編譯時處理分為三個階段:
在我們的 XML 示例中,第一行和最后一行中的所有空格都將被刪除,而中間兩行會縮進四個空格,因為在這個例子中有五個確定行(包括四行 XML 代碼和結束分隔符所在的行),行的縮進都至少和第一行內容的縮進相同。通常情況下,這種縮進就是我們期望的,但有時我們可能不希望去掉所有的前導縮進。例如,如果我們想讓整個塊縮進 4 個空格,那么我們可以通過將結束分隔符向左移動 4 個空格來實現:
復制代碼
void m() { System.out.println(""" <person> <firstName>Bob</firstName> <lastName>Jones</lastName> </person> """);}
因為最后一行也是一個確定行,所以公共空格前綴現在是塊的最后一行中結束分隔符之前的空格數量,每一行都按這個數量刪除空格,整個塊縮進 4 個空格。我們也可以通過編程方式管理縮進,通過實例方法 String::indent,它接受一個多行字符串(不管它是否來自文本塊),并按固定數量的空格縮進每一行:
復制代碼
void m() { System.out.println(""" <person> <firstName>Bob</firstName> <lastName>Jones</lastName> </person> """.indent(4));}
在極端情況下,如果不需要刪除空格,則可以將結束分隔符一直移動到左邊界:
復制代碼
void m() { System.out.println(""" <person> <firstName>Bob</firstName> <lastName>Jones</lastName> </person>""");}
或者,我們可以通過將整個文本塊移到左邊界來達到同樣的效果:
復制代碼
void m() { System.out.println("""<person> <firstName>Bob</firstName> <lastName>Jones</lastName></person>""");}
乍聽起來,這些規則可能有些復雜,但選用這種規則是為了平衡各種競爭性問題(既希望能夠相對于周圍的程序縮進文本塊,又不會產生可變數量的附帶前導空格),同時,如果默認的算法不是想要的結果,還可以提供一個簡單的方法來調整或選擇不刪除空格。
Java 的字符串不支持表達式插值,而其他一些語言則支持;文本塊也不支持。(在某種程度上,我們將來可能會考慮這個特性,不限于文本塊,而是同樣適用于字符串。)過去,參數化的字符串表達式是通過普通的字符串連接(+)構建的;Java 5 添加了 String::format 以支持“printf”樣式的字符串格式。
由于需要對周圍的空格進行全面分析,當通過字符串連接組合成文本塊時,要想正確地縮進可不是一件簡單的事。但是,文本塊的處理結果是一個普通的字符串,所以我們仍然可以使用 String::format 來參數化字符串表達式。此外,我們可以使用新的 String::formatted 方法,它是 String::format 的實例版本。
復制代碼
String person=""" <person> <firstName>%s</firstName> <lastName>%s</lastName> </person> """.formatted(first, last));
遺憾的是,這個方法也不能命名為 format,因為我們不能重載具有相同名稱和參數列表的靜態實例方法。
雖然從某種意義上說,字符串字面量是一個“微不足道”的特性,但它們的使用頻率如此之高,足以讓小煩惱累積成大煩惱。因此也就不奇怪,為什么缺乏多行字符串是近年來對 Java 最常見的抱怨之一,而許多其他語言都有多種形式的字符串字面量來支持不同的用例。
這或許令人驚訝,在各種流行的語言中,這一特性有許多不同的表示方式。說“想要多行字符串”很容易,但是在研究其他語言時,我們發現,它們的語法和目標各不相同。(當然,對于哪一種才是“正確”的方法,開發人員的觀點也很不一樣。)雖然沒有任何兩種語言是相同的,但是對于大多數許多語言都具有的特性(例如 for 循環),通常有一些通用的方法可供選擇;在 15 種語言中找到同一特性的 15 種不同解釋很罕見,但是,我們發現,多行原始字符串字面量就是這樣。
下表顯示了各種語言中字符串字面量的部分選項。其中,…是字符串字面量的內容,對于轉義序列和嵌入式插值,可能會處理,也可能不會處理,xxx 表示一個分隔符,用戶可以任意定義,但不能與字符串的內容沖突,而##表示數量可變的#號(可能是零個)。
語言語法說明Bash‘…’[span]Bash$’…’[esc] [span]Bash“…”[esc] [interp] [span]C“…”[esc]C++“…”[esc]C++R"xxx(…)xxx"[span] [delim]C#“…”[esc]C#$"…"[esc] [interp]C#@"…"Dart‘…’[esc] [interp]Dart“…”[esc] [interp]Dart‘’’…’’’[esc] [interp] [span]Dart“”"…"""[esc] [interp] [span]Dartr’…’[prefix]Go“…”[esc]Go…[span]Groovy‘…’[esc]Groovy“…”[esc] [interp]Groovy‘’’…’’’[esc] [span]Groovy“”"…"""[esc] [interp] [span]Haskell“…”[esc]Java“…”[esc]Javascript‘…’[esc] [span]Javascript“…”[esc] [span]Javascript…[esc] [interp] [span]Kotlin“…”[esc] [interp]Kotlin“”"…"""[interp] [span]Perl‘…’Perl“…”[esc] [interp]Perl<<‘xxx’[here]Perl<<“xxx”[esc] [interp] [here]Perlq{…}[span]Perlqq{…}[esc] [interp] [span]Python‘…’[esc]Python“…”[esc]Python‘’’…’’’[esc] [span]Python“”"…"""[esc] [span]Pythonr’…’[esc] [prefix]Pythonf’…’[esc] [interp] [prefix]Ruby‘…’[span]Ruby“…”[esc] [interp] [span]Ruby%q{…}[span] [delim]Ruby%Q{…}[esc] [interp] [span] [delim]Ruby<<-xxx[here] [interp]Ruby<<~xxx[here] [interp] [strip]Rust“…”[esc] [span]Rustr##"…"##[span] [delim]Scala“…”[esc]Scala“”"…"""[span]Scalas"…"[esc] [interp]Scalaf"…"[esc] [interp]Scalaraw"…"[interp]Swift##"…"##[esc] [interp] [delim]Swift##"""…"""##[esc] [interp] [delim] [span]
說明:
雖然這個表說明了字符串字面量方法的多樣性,但它實際上只觸及了表面,因為語言解釋字符串字面量的方法有各種細節的差異,不可能通過這樣一個簡單的表格完全說明。雖然大多數語言受 C 語言啟發,使用一個轉義字符,但它們支持的轉義字符不同,是否支持以及如何支持 Unicode 轉義(例如,\unnnn),不完全支持轉義語言的形式是否仍然支持一些有限形式的分隔符字符轉義(比如,對于嵌入式引用,使用兩個引號而不是結束字符串。)簡單起見,該表還省略了一些其他形式(如 C++ 中用于控制字符編碼的各種前綴)。
語言之間最明顯的差別是分隔符的選擇,而不同的分隔符意味著不同形式的字符串字面量(有或沒有轉義字符,單行或多行,有或沒有插值,字符編碼選擇,等等),但從中我們可以看到,這些語法的選擇常常反映了該語言在設計哲學上的差異——如何平衡等各種目標,如簡潔性、表現力和用戶便利性。
毫不奇怪,腳本語言(bash、Perl、Ruby、Python)已經將“用戶選擇權”放在最高優先級,它們提供多種形式的字面量,可以用多種方式表示相同的東西。但是,一般來說,在鼓勵用戶如何看待字符串字面量、提供多少種形式以及這些形式的正交性方面,各種語言都不相同。我們還看到了一些關于多行字符串的理論。有一些(比如 JavaScript 和 Go)只是將行結束符看作另一個字符,允許所有形式的字符串字面量跨越多個行,有一些(比如 C++)把它們作為一種特殊的“原始”字符串,其他語言(比如 Kotlin)則將字符串分成“簡單”和“復雜”兩類,并將多行字符串歸為“復雜”這一類,還有一些提供的選項太多,甚至都無法這樣簡單的分類。同樣,它們對“原始字符串”的解釋也各不相同。真正的原始需要某種形式的用戶可控制的分隔符(如 C++、Swift 和 Rust 所具有的形式),盡管其他語言也號稱其字符串是“原始的”,但它們仍然為其結束(固定)分隔符保留著某種形式的轉義。
盡管有各種各樣的方法和觀點,但是從平衡原則性設計和表現力的角度來看,這項調查中有一個明顯的“贏家”: Swift 。它通過一種靈活的機制(單行變體和多行變體)提供轉義、插值和真正的原始字符串。在這些語言中,最新的語言擁有最動聽的故事,這并不奇怪,因為它有后見之明,可以從其他人的成功和錯誤中汲取經驗教訓。(這里,關鍵的創新是,雖然轉義分隔符各不相同,但都與字符串分隔符保持一致,可以避免在“熟(cooked)”和“生(raw)”模式之間進行選擇,同時仍然跨所有形式的字符串字面量共享轉義語言——這種方法“事后看來”是很值得贊揚的。)由于現有的語言限制,Java 無法大規模地采用 Swift 的方法,但是,Java 已盡可能地從 Swift 社區所做的優秀工作中汲取靈感——并為將來開展進一步的工作預留了空間。
文本塊并不是該特性的第一次迭代;第一次迭代是原始字符串字面量。與 Rust 的原始字符串一樣,它使用了一個大小可變的分隔符(任意數量的反單引號字符),并且完全不解釋其內容。在完成了全部設計和原型之后,這個提議被撤回了。當時的判斷是,這種做法雖然足夠合理,但總讓人覺得太過于死板——與傳統字符串字面量沒什么共同之處,因此,如果我們將來想擴展這個特性,就沒法把它們一起擴展。(由于現在的發布節奏很快,這只是讓該特性推遲了 6 個月,但我們會得到一個更好的特性。)
JEP 326 方法的一個主要缺點是,原始字符串的工作方式與傳統的字符串字面量完全不同:不同的分隔符字符、變長分隔符 vs 固定分隔符、單行 vs 多行、轉義 vs 非轉義。總會有人想要一些不同選項的組合,想要更多不同的形式,因此,我們走了 Bash 所走的路。最重要的是,它沒有解決“附加縮進”的問題,這顯然會成為 Java 程序脆弱性的根源。基于此,文本塊與傳統的字符串字面量(分隔符語法、轉義語言)有非常多的相似之處,只在一個關鍵方面有所不同——字符串是一維字符序列還是二維文本塊。
Oracle Java 團隊的 Jim Laskey 和 Stuart Marks 發布了一份程序員指南,詳細介紹了文本塊,并提供了樣式建議。
在可以提高代碼清晰度時使用文本塊。連接、轉義換行和轉義引號分隔符使字符串字面量的內容變得混亂;文本塊解決了這個問題,內容更清晰,但在語法上,它們比傳統的字符串字面量更重量級。務必要在好處大于額外成本的地方使用文本塊;如果一個字符串可以放在一行中,沒有轉義換行,那么最好還是使用傳統的字符串字面量。
避免復雜表達式中的內聯文本塊。文本塊是字符串值表達式,因此,可以在任何需要字符串的地方使用,但將文本塊嵌套在復雜的表達式中有時候并不好,把它放到一個單獨的變量中會更好。當閱讀下面的代碼時,其中的文本塊會打斷代碼流,迫使讀者轉換思維:
復制代碼
String poem=new String(Files.readAllBytes(Paths.get("jabberwocky.txt")));String middleVerses=Pattern.compile("\n\n") .splitAsStream(poem) .match(verse -> !""" ’Twas brillig, and the slithy toves Did gyre and gimble in the wabe; All mimsy were the borogoves, And the mome raths outgrabe. """.equals(verse)) .collect(Collectors.joining("\n\n"));
如果我們把文本塊放入它自己的變量中,讀者就更容易理解計算流程:
復制代碼
String firstLastVerse=""" ’Twas brillig, and the slithy toves Did gyre and gimble in the wabe; All mimsy were the borogoves, And the mome raths outgrabe. """;String poem=new String(Files.readAllBytes(Paths.get("jabberwocky.txt")));String middleVerses=Pattern.compile("\n\n") .splitAsStream(poem) .match(verse -> !firstLastVerse.equals(verse)) .collect(Collectors.joining("\n\n"));
避免在文本塊縮進中混合使用空格和制表符。刪除附加縮進的算法會計算公共空格前綴,因此,如果混合使用空格和制表符進行縮進,該算法仍然有效。不過,這顯然更容易出錯,所以最好避免混合使用它們,而只使用其中的一種。
將文本塊與相鄰的 Java 代碼對齊。由于附加空格會被自動刪除,所以我們應該利用這一點使代碼更易于閱讀。雖然我們可能會忍不住寫成下面這樣:
復制代碼
void printPoem() { String poem="""’Twas brillig, and the slithy tovesDid gyre and gimble in the wabe;All mimsy were the borogoves,And the mome raths outgrabe."""; System.out.print(poem);
因為我們不想字符串中有任何前導縮進,但大多數時候,我們應該寫成下面這樣:
復制代碼
void printPoem() { String poem=""" ’Twas brillig, and the slithy toves Did gyre and gimble in the wabe; All mimsy were the borogoves, And the mome raths outgrabe. """; System.out.print(poem);}
因為這樣讀者更容易理解。
不是一定要將文本與開始分隔符對齊。不過,我們可以選擇將文本塊內容與開始分隔符對齊:
復制代碼
String poem=""" ’Twas brillig, and the slithy toves Did gyre and gimble in the wabe; All mimsy were the borogoves, And the mome raths outgrabe. """;
這樣看起來很漂亮,但是如果行很長,或者分隔符從距離左邊距很遠的地方開始,就會很麻煩,因為現在文本會一直插入到右邊距。但是,這樣的縮進并不是必需的;我們可以使用任何縮進方式,只要前后一致:
復制代碼
String poem=""" ’Twas brillig, and the slithy toves Did gyre and gimble in the wabe; All mimsy were the borogoves, And the mome raths outgrabe. """;
當文本塊中嵌入了三重引號時,只轉義第一個引號。雖然可以每個引號都轉義,但這沒必要,并且會降低可讀性;只需要轉義第一個引號:
復制代碼
String code=""" String source=\""" String message="Hello, World!"; System.out.println(message); \"""; """;
使用\分割非常長的行。除了文本塊之外,我們還獲得了兩個新的轉義序列,\s(空格字面量)和 <newline>(續行指示符)。如果文本的行非常長,可以使用 <newline> 在源代碼中放一個換行符,它會在字符串編譯時轉義處理期間被刪除。
Java 程序中的字符串字面量并不局限于像“yes”和“no”這樣的短字符串;它們通常對應于結構化語言(如 HTML、SQL、XML、JSON 甚至 Java)的整段“程序”。保留嵌入的那段程序的二維結構,而又不引入轉義字符和其他語言上的干擾,這樣程序更不容易出錯,而且更易于閱讀。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。