Java 11的十一颗隐藏的珍珠

Java 11没有引入任何创新功能,但是它包含了一些您可能还没有听说过的瑰宝。 已经查看过StringOptionalCollection和其他主要功能中的最新功能? 如果没有,那么您来了:今天,我们将看一下Java 11中的11个隐藏的宝石!


Lambda参数的类型推断


在编写lambda表达式时,可以在显式指定类型和跳过它们之间进行选择:


 Function<String, String> append = string -> string + " "; Function<String, String> append = (String s) -> s + " "; 

Java 10引入了 var ,但是不能在lambdas中使用:


 //    Java 10 Function<String, String> append = (var string) -> string + " "; 

在Java 11中已经可以实现。 但是为什么呢? 看起来var提供的不仅仅是类型传递。 尽管是这种情况,但使用var有两个次要优点:


  • 通过删除规则的异常,使使用var更通用
  • 允许您向参数类型添加注释,而无需使用其全名

这是第二种情况的示例:


 List<EnterpriseGradeType<With, Generics>> types = /*...*/; types .stream() // ,     @Nonnull   .filter(type -> check(type)) //  Java 10    ~>  .filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type)) //  Java 11    ~>   .filter((@Nonnull var type) -> check(type)) 

尽管可以在形式为(var type, String option, index) -> ... lambda表达式中混合派生的显式和隐式类型,但是( 在JEP-323框架中 )这项工作没有执行。 因此,有必要选择三种方法之一,并对lambda表达式的所有参数都坚持使用。 需要为所有参数指定var以便为其中一个参数添加注释可能会有些烦人,但通常是可以忍受的。


使用'String::lines'对字符串进行流处理


有多行字符串? 想要对每一行做点什么? 那么String::lines是正确的选择:


 var multiline = "\r\n\r\n\r\n"; multiline .lines() //Stream<String> .map(line -> "// " + line) .forEach(System.out::println); // : //  //  //  //  

请注意,原始行使用\r\n螺丝定界符,尽管我在Linux上,但lines()仍然将其破坏。 这是由于以下事实:尽管使用了当前的操作系统,该方法仍将\r\n\r\n为换行符-即使它们混在同一行中。


行流永远不会包含行分隔符本身。 行可以为空( "\n\n \n\n" ,其中包含5行),但是如果原始行的最后一行为空,则忽略该行( "\n\n" ; "\n\n" ; 2行)。 (译者注:他们有line ,但是有string ,我们都很方便。)


不同于split("\R") ,各lines()惰性的, 我引用 “通过更快地搜索新的换行符来提供更好的性能”。 (如果有人想在JMH上发布基准进行验证,请告诉我)。 它还可以更好地反映处理算法,并使用更方便的数据结构(流而不是数组)。


'String::strip'等删除空格


最初, String有一个trim方法来删除空格,这被认为是所有代码不超过U+0020 。 是的, BACKSPACEU+0008)是一个类似于BELLU+0007 )的空白,但是不再将LINE SEPARATORU+2028 )视为空格。


Java 11引入了strip方法,该方法具有更多细微差别。 它使用Java 5中的Character::isWhitespace来确定确切需要删除的内容。 从其文档中可以明显看出:


  • SPACE SEPARATORLINE SEPARATOR ,参数PARAGRAPH SEPARATOR ,但不是不可分割的空间
  • HORIZONTAL TABULATIONU+0009 ), U+000A LINE FEEDU+000A ), VERTICAL TABULATIONU+000B ), FORM FEEDU+000C ), CARRIAGE RETURNU+000D
  • FILE SEPARATOR U+001EU+001C ), GROUP SEPARATOR U+001EU+001D ), RECORD SEPARATOR U+001EU+001E ), UNIT SEPARATOR U+001EU+001F

按照相同的逻辑,还有另外两种清洁方法, stripLeadingstripTailing ,它们完全可以完成对它们的期望。


最后,如果您只需要找出删除空格后该行是否为空,则无需真正删除它们-只需使用isBlank


 " ".isBlank(); //  ~> true " ".isBlank(); //   ~> false 

'String::repeat'重复字符串


抓住主意:


步骤1:监视JDK

密切关注JDK开发


步骤2:查找与StackOverflow相关的问题

寻找有关Stackoverflow的相关问题


步骤3:根据未来的变化得出新的答案

根据即将发生的变化提供新答案


步骤4:????

步骤4:获利

¯\ _(ツ)_ /¯


可以想象, String有一个新的repeat(int)方法。 它的工作完全符合期望,因此几乎没有讨论。


使用'Path::of'创建路径


我真的很喜欢Path API,但是在不同视图之间转换路径(例如PathFileURLURIString )仍然很烦人。 通过将两个Paths::get方法复制Paths::get方法的Path::of这一点在Java 11中变得不再那么混乱了:


 Path tmp = Path.of("/home/nipa", "tmp"); Path codefx = Path.of(URI.create("http://codefx.org")); 

它们可以被认为是规范的,因为两个旧的Paths::get方法都使用新选项。


使用'Files::readString''Files::writeString'读写文件


如果需要读取大文件,通常会使用Files::lines来获取其行的惰性流。 类似地,要写入可能无法完全存储在内存中的大量数据,我使用Files::write作为Iterable<String>传递它们。


但是,当我想将文件内容作为一行处理时,这种简单情况又如何呢? 这不是很方便,因为Files::readAllBytesFiles::write的相应变体在字节数组上运行。


然后出现Java 11,将readStringwriteString添加到Files


 String haiku = Files.readString(Path.of("haiku.txt")); String modified = modify(haiku); Files.writeString(Path.of("haiku-mod.txt"), modified); 

清晰易用。 如有必要,可以将Charset传递给readString ,在writeStringOpenOptions一个OpenOptions数组。


使用'Reader::nullReader'等来清空I / O。


需要一个不会在任何地方写的OutputStream吗? 还是一个空的InputStream ? 什么都不做的ReaderWriter呢? Java 11拥有全部:


 InputStream input = InputStream.nullInputStream(); OutputStream output = OutputStream.nullOutputStream(); Reader reader = Reader.nullReader(); Writer writer = Writer.nullWriter(); 

(译者注:在commons-io这些类自2014年左右就已经存在。)


但是,我很惊讶null真的是最好的前缀吗? 我不喜欢用它来表示“故意缺席”……也许最好使用noOp(译者注:由于/dev/null的常用用法,因此很可能选择了此前缀。)


{ } ~> [ ]'Collection::toArray'


如何将集合转换为数组?


 //  Java 11 List<String> list = /*...*/; Object[] objects = list.toArray(); String[] strings_0 = list.toArray(new String[0]); String[] strings_size = list.toArray(new String[list.size()]); 

第一个选项objects会丢失有关类型的所有信息,因此它正在运行中。 其余的呢? 两者都很笨重,但第一个较短。 后者创建了所需大小的数组,因此看起来效率更高(也就是说,“似乎效率更高”,请参见信誉 )。 但这真的更有生产力吗? 不,相反,它 (现在) 比较慢


但是我为什么要在乎呢? 有没有更好的方法可以做到这一点? 在Java 11中有:


 String[] strings_fun = list.toArray(String[]::new); 

Collection::toArrayCollection::toArray新变体,它接受IntFunction<T[]> ,即 接收数组大小并返回所需大小的数组的函数。 它可以简短地表示为对T[]::new形式的构造函数的引用(对于著名的T )。


有趣的是, Collection#toArray(IntFunction<T[]>)的默认实现始终将0传递给数组生成器。 最初,我认为此解决方案基于零长度数组的最佳性能,但现在我认为原因可能是对于某些集合而言,计算大小可能是一项非常昂贵的操作,并且您不应该在Collection的默认实现中使用此方法。 但是,特定的集合实现(例如ArrayList )可以更改此方法,但是在Java 11中不会更改。 我猜这不值得。


使用'Optional::isEmpty'缺席检查


随着Optional的大量使用,尤其是在大型项目中,您经常会遇到非Optional方法,因此您通常必须检查它是否有价值。 Optional::isPresent有一个Optional::isPresent方法。 但是,您经常需要了解相反的情况Optional空。 没问题,只需使用!opt.isPresent()吧?


当然,可以这样做,但是if条件没有反转,则几乎总是更容易理解if逻辑。 有时,在一连串的通话结束时会弹出Optional ,如果您不需要检查任何内容,则必须下注! 在开始时:


 public boolean needsToCompleteAddress(User user) { return !getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isPresent(); } 

在这种情况下,请跳过! 非常容易 从Java 11开始,有一个更好的选择:


 public boolean needsToCompleteAddress(User user) { return getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isEmpty(); } 

'Predicate::not'反转谓词


说到反转... Predicate接口具有一个 negate 实例 negate :它返回一个新的谓词,该谓词执行相同的检查,但是将其结果反转。 不幸的是,我很少设法使用它...


 //      Stream .of("a", "b", "", "c") // ,  ~>        .filter(s -> !s.isBlank()) //          ~>  .filter((String::isBlank).negate()) // ,  ~>       .filter(((Predicate<String>) String::isBlank).negate()) .forEach(System.out::println); 

问题是我很少访问Predicate实例。 更常见的是,我想通过方法的链接来获得这样的实例(并将其反转),但是要使其正常工作,编译器必须知道将对该方法的引用带到何处-没有它,它什么也做不了。 如果您使用(String::isBlank).negate() ,这就是发生的情况:编译器不再知道应在此String::isBlank什么。 正确指定的种姓可以解决此问题,但要付出什么代价?


虽然有一个简单的解决方案。 不要使用negate实例negate ,而要使用Java 11中的新静态方法Predicate.not(Predicate<T>)


 Stream .of("a", "b", "", "c") //   `java.util.function.Predicate.not` .filter(not(String::isBlank)) .forEach(System.out::println); 

已经更好了!


'Pattern::asMatchPredicate'作为谓词的正则表达式


有正则表达式吗? 需要过滤数据吗? 怎么样:


 Pattern nonWordCharacter = Pattern.compile("\\W"); Stream .of("Metallica", "Motörhead") .filter(nonWordCharacter.asPredicate()) .forEach(System.out::println); 

我很高兴找到这种方法! 值得补充的是,这是Java 8中的一种方法。糟糕,我当时错过了它。 Java 11添加了另一个类似的方法: Pattern::asMatchPredicate 。 有什么区别?


  • asPredicate检查字符串字符串的一部分是否与模式匹配(类似于s -> this.matcher(s).find()
  • asMatchPredicate检查整个字符串是否与模式匹配(类似于s -> this.matcher(s).matches()

例如,我们有一个检查电话号码的正则表达式,但是它不包含^$来跟踪行的开头和结尾。 然后,以下代码将无法正常运行:


 prospectivePhoneNumbers .stream() .filter(phoneNumberPatter.asPredicate()) .forEach(this::robocall); 

你有没有发现错误? 诸如" -152 ? +1-202-456-1414"行将被过滤,因为其中包含有效的电话号码。 另一方面, Pattern::asMatchPredicate将不允许这样做,因为整个字符串将不再与模式匹配。


自检


这是所有11颗珍珠的概述-您还记得每种方法的作用吗? 如果是这样,则说明您已通过测试。


  • String
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • Path
    • static Path of(String, String...)
    • static Path of(URI)
  • Files
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • InputStreamstatic InputStream nullInputStream()
  • OutputStreamstatic OutputStream nullOutputStream()
  • Readerstatic Reader nullReader()
  • Writerstatic Writer nullWriter()
  • in CollectionT[] toArray(IntFunction<T[]>)
  • Optionalboolean isEmpty()
  • Predicatestatic Predicate<T> not(Predicate<T>)
  • PatternPredicate<String> asMatchPredicate()

玩Java 11!

Source: https://habr.com/ru/post/zh-CN441154/


All Articles