原文: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上关注我。
请坚持编程!