原文:Java 8 API by Example: Strings, Numbers, Math and Files java
譯者:飛龍 git
協議:CC BY-NC-SA 4.0github
大量的教程和文章都涉及到Java8中最重要的改變,例如lambda表達式和函數式數據流。可是此外許多現存的類在JDK 8 API中也有所改進,帶有一些實用的特性和方法。正則表達式
這篇教程涉及到Java 8 API中的那些小修改 -- 每一個都使用簡單易懂的代碼示例來描述。讓咱們好好看一看字符串、數值、算術和文件。編程
兩個新的方法可在字符串類上使用:join
和chars
。第一個方法使用指定的分隔符,將任何數量的字符串鏈接爲一個字符串。api
String.join(":", "foobar", "foo", "bar"); // => foobar:foo:bar
第二個方法chars
從字符串全部字符建立數據流,因此你能夠在這些字符上使用流式操做。函數
"foobar:foo:bar" .chars() .distinct() .mapToObj(c -> String.valueOf((char)c)) .sorted() .collect(Collectors.joining()); // => :abfor
不單單是字符串,正則表達式模式串也能受益於數據流。咱們能夠分割任何模式串,並建立數據流來處理它們,而不是將字符串分割爲單個字符的數據流,像下面這樣:工具
Pattern.compile(":") .splitAsStream("foobar:foo:bar") .filter(s -> s.contains("bar")) .sorted() .collect(Collectors.joining(":")); // => bar:foobar
此外,正則模式串能夠轉換爲謂詞。這些謂詞能夠像下面那樣用於過濾字符串流:post
Pattern pattern = Pattern.compile(".*@gmail\\.com"); Stream.of("bob@gmail.com", "alice@hotmail.com") .filter(pattern.asPredicate()) .count(); // => 1
上面的模式串接受任何以@gmail.com
結尾的字符串,而且以後用做Java8的Predicate
來過濾電子郵件地址流。code
Java8添加了對無符號數的額外支持。Java中的數值老是有符號的,例如,讓咱們來觀察Integer
:
int
可表示最多2 ** 32
個數。Java中的數值默認爲有符號的,因此最後一個二進制數字表示符號(0爲正數,1爲負數)。因此從十進制的0開始,最大的有符號正整數爲2 ** 31 - 1
。
你能夠經過Integer.MAX_VALUE
來訪問它:
System.out.println(Integer.MAX_VALUE); // 2147483647 System.out.println(Integer.MAX_VALUE + 1); // -2147483648
Java8添加了解析無符號整數的支持,讓咱們看看它如何工做:
long maxUnsignedInt = (1l << 32) - 1; String string = String.valueOf(maxUnsignedInt); int unsignedInt = Integer.parseUnsignedInt(string, 10); String string2 = Integer.toUnsignedString(unsignedInt, 10);
就像你看到的那樣,如今能夠將最大的無符號數2 ** 32 - 1
解析爲整數。並且你也能夠將這個數值轉換回無符號數的字符串表示。
這在以前不可能使用parseInt
完成,就像這個例子展現的那樣:
try { Integer.parseInt(string, 10); } catch (NumberFormatException e) { System.err.println("could not parse signed int of " + maxUnsignedInt); }
這個數值不可解析爲有符號整數,由於它超出了最大範圍2 ** 31 - 1
。
Math
工具類新增了一些方法來處理數值溢出。這是什麼意思呢?咱們已經看到了全部數值類型都有最大值。因此當算術運算的結果不能被它的大小裝下時,會發生什麼呢?
System.out.println(Integer.MAX_VALUE); // 2147483647 System.out.println(Integer.MAX_VALUE + 1); // -2147483648
就像你看到的那樣,發生了整數溢出,這一般是咱們不肯意看到的。
Java8添加了嚴格數學運算的支持來解決這個問題。Math
擴展了一些方法,它們所有以exact
結尾,例如addExact
。當運算結果不能被數值類型裝下時,這些方法經過拋出ArithmeticException
異常來合理地處理溢出。
try { Math.addExact(Integer.MAX_VALUE, 1); } catch (ArithmeticException e) { System.err.println(e.getMessage()); // => integer overflow }
當嘗試經過toIntExact
將長整數轉換爲整數時,可能會拋出一樣的異常:
try { Math.toIntExact(Long.MAX_VALUE); } catch (ArithmeticException e) { System.err.println(e.getMessage()); // => integer overflow }
Files
工具類首次在Java7中引入,做爲NIO的一部分。JDK8 API添加了一些額外的方法,它們能夠將文件用於函數式數據流。讓咱們深刻探索一些代碼示例。
Files.list
方法將指定目錄的全部路徑轉換爲數據流,便於咱們在文件系統的內容上使用相似filter
和sorted
的流操做。
try (Stream<Path> stream = Files.list(Paths.get(""))) { String joined = stream .map(String::valueOf) .filter(path -> !path.startsWith(".")) .sorted() .collect(Collectors.joining("; ")); System.out.println("List: " + joined); }
上面的例子列出了當前工做目錄的全部文件,以後將每一個路徑都映射爲它的字符串表示。以後結果被過濾、排序,最後鏈接爲一個字符串。若是你還不熟悉函數式數據流,你應該閱讀個人Java8數據流教程。
你可能已經注意到,數據流的建立包裝在try-with
語句中。數據流實現了AutoCloseable
,而且這裏咱們須要顯式關閉數據流,由於它基於IO操做。
返回的數據流是
DirectoryStream
的封裝。若是須要及時處理文件資源,就應該使用try-with
結構來確保在流式操做完成後,數據流的close
方法被調用。
下面的例子演示瞭如何查找在目錄及其子目錄下的文件:
Path start = Paths.get(""); int maxDepth = 5; try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) -> String.valueOf(path).endsWith(".js"))) { String joined = stream .sorted() .map(String::valueOf) .collect(Collectors.joining("; ")); System.out.println("Found: " + joined); }
find
方法接受三個參數:目錄路徑start
是起始點,maxDepth
定義了最大搜索深度。第三個參數是一個匹配謂詞,定義了搜索的邏輯。上面的例子中,咱們搜索了全部JavaScirpt文件(以.js
結尾的文件名)。
咱們可使用Files.walk
方法來完成相同的行爲。這個方法會遍歷每一個文件,而不須要傳遞搜索謂詞。
Path start = Paths.get(""); int maxDepth = 5; try (Stream<Path> stream = Files.walk(start, maxDepth)) { String joined = stream .map(String::valueOf) .filter(path -> path.endsWith(".js")) .sorted() .collect(Collectors.joining("; ")); System.out.println("walk(): " + joined); }
這個例子中,咱們使用了流式操做filter
來完成和上個例子相同的行爲。
將文本文件讀到內存,以及向文本文件寫入字符串在Java 8 中是簡單的任務。不須要再去擺弄讀寫器了。Files.readAllLines
從指定的文件把全部行讀進字符串列表中。你能夠簡單地修改這個列表,而且將它經過Files.write
寫到另外一個文件中:
List<String> lines = Files.readAllLines(Paths.get("res/nashorn1.js")); lines.add("print('foobar');"); Files.write(Paths.get("res/nashorn1-modified.js"), lines);
要注意這些方法對內存並不十分高效,由於整個文件都會讀進內存。文件越大,所用的堆區也就越大。
你可使用Files.lines
方法來做爲內存高效的替代。這個方法讀取每一行,並使用函數式數據流來對其流式處理,而不是一次性把全部行都讀進內存。
try (Stream<String> stream = Files.lines(Paths.get("res/nashorn1.js"))) { stream .filter(line -> line.contains("print")) .map(String::trim) .forEach(System.out::println); }
若是你須要更多的精細控制,你須要構造一個新的BufferedReader
來代替:
Path path = Paths.get("res/nashorn1.js"); try (BufferedReader reader = Files.newBufferedReader(path)) { System.out.println(reader.readLine()); }
或者,你須要寫入文件時,簡單地構造一個BufferedWriter
來代替:
Path path = Paths.get("res/output.js"); try (BufferedWriter writer = Files.newBufferedWriter(path)) { writer.write("print('Hello World');"); }
BufferedReader
也能夠訪問函數式數據流。lines
方法在它全部行上面構建數據流:
Path path = Paths.get("res/nashorn1.js"); try (BufferedReader reader = Files.newBufferedReader(path)) { long countPrints = reader .lines() .filter(line -> line.contains("print")) .count(); System.out.println(countPrints); }
目前爲止你能夠看到Java8提供了三個簡單的方法來讀取文本文件的每一行,使文件處理更加便捷。
不幸的是你須要顯式使用try-with
語句來關閉文件流,這會使示例代碼有些凌亂。我期待函數式數據流能夠在調用相似count
和collect
時能夠自動關閉,由於你不能在相同數據流上調用終止操做兩次。
我但願你能喜歡這篇文章。全部示例代碼都託管在Github上,還有來源於我博客其它Java8文章的大量的代碼片斷。若是這篇文章對你有所幫助,請收藏個人倉庫,而且在Twitter上關注我。
請堅持編程!