【问题标题】:Joining Strings via stream通过流连接字符串
【发布时间】:2017-06-24 12:03:58
【问题描述】:

我正在尝试加入姓名列表:

List<String> names;
names = books.stream()
        .map( b -> b.getName() )
        .filter( n -> ( (n != null) && (!n.isEmpty()) ) )
        .collect(Collectors.joining(", "));

这不编译说:

不兼容的类型。推理变量 R 的边界不兼容

所以经过一番研究,似乎我误解了一些东西。我认为.map( b -&gt; b.getName() ) 将类型返回/更改为字符串,那里似乎有问题。如果我改用.map(Book::getName),我仍然会收到错误,但我可能是don't fully understand the difference

但是,这并没有抱怨:

List<String> names;
names = books.stream()
        .map( b -> b.getName() )
        .map( Book::getName )
        .filter( n -> ( (n != null) && (!n.isEmpty()) ) )
        .collect(Collectors.joining(", "));

谁能解释一下为什么?关于.map( b -&gt; b.getName() ).map(Book::getName) 之间差异的一些教学解释也很受欢迎,因为我认为我没有做对。

【问题讨论】:

  • 你真的认为这么多的括号(.filter( n -&gt; ( (n != null) &amp;&amp; (!n.isEmpty()) ) ) 比直接的.filter(n -&gt; n!=null &amp;&amp; !n.isEmpty()) 提高了可读性吗?
  • 嗯...我通常在任何时候使用括号,只要我有像 && 这样的运算符,只是为了清楚起见。在这种情况下你是对的,它看起来很麻烦
  • 我知道 &amp;&amp;|| 之间的运算符优先级不能直观地回答,因此即使在不必要的情况下,为了清楚起见放置大括号也是有道理的。但是对于唯一的&amp;&amp; 运算符,没有办法曲解表达式。此外,还有另一个与逻辑表达式无关的外括号对。由于您也没有写 b -&gt; (b.getName()),因此在另一个表达式中有一个外部对是不一致的。
  • 等待... .map(b -&gt; b.getName()).map(Book::getName) 有效,但只有 .map(b -&gt; b.getName()) 无效?所以books其实不是Book的集合?所以b.getName() 返回一个Book?可能这也是它不抱怨的原因。
  • @user1156544,你应该写这个有问题......这令人困惑

标签: java-8


【解决方案1】:

joining(", ") 收集器将使用给定的分隔符收集并加入所有字符串到单个字符串。在这种情况下collect 的返回类型是String,但您正试图将结果分配给List。如果要将字符串收集到列表中,请使用Collectors.toList()

如果您有一个包含Book 实例的集合,那么将Books 流映射到Strings 流一次就足够了。

lamdba 和方法引用的区别

  • 一个lamdba表达式可以写成一个块,包含多个操作:

    b -> {
        // here you can have other operations
        return b.getName(); 
    }
    

    如果 lambda 有单个操作,可以缩短:

    b -> b.getName()
    
  • 方法引用只是具有单个操作的 lambda 的“快捷方式”。这样:

    b -> b.getName()
    

    可以替换为:

    Book::getName
    

    但如果你有这样的 lambda:

    b -> b.getName().toLowerCase()
    

    您不能使用对getName 方法的引用,因为您正在对toLowerCase() 进行额外调用。

【讨论】:

  • 好的,我现在明白我的愚蠢错误了。在 Book::getName 上使用 b -> b.getName() 有什么影响?
  • 方法引用可能会稍微高效一些,但它确实有点,因此不应该驱动设计决策。另一点是Book::getName 指的是声明类,因此,有时在使用b -&gt; b.getName() 时编译器无法推断b 类型的情况下工作,尽管这也可以通过使用(Book b) -&gt; b.getName() 来解决.
  • 方法引用也可能很危险:seesee
【解决方案2】:

如果您使用 Collectors.joining(),则结果将是单个串联字符串:

String names = books.stream()
        .map( b -> b.getName() )
        .filter(n -> (n != null) && !n.isEmpty())
        .collect(Collectors.joining(", "));

Collectors.toList() 是返回列表的方法:

List<String> namesList = books.stream()
        .map( b -> b.getName() )
        .filter(n -> (n != null) && !n.isEmpty())
        .collect(Collectors.toList());

Book::getName 是一个method reference 并且将具有与b -&gt; b.getName() 相同的结果。方法引用更清晰,可以将其他现有方法作为参数传递给map()等方法,只要传递的方法符合预期的functional interface的签名即可。在这种情况下,map() 需要一个 Function 接口的实例。因此,您可以从此类接口对符合抽象R apply(T t) 方法签名的方法进行任何引用。

由于您将Book 映射到String,因此要赋予map() 的方法的实际签名必须是String apply(Book t)。这可以读作“接收一本书并返回一个字符串”。这样,您传递的任何符合此定义的方法都是有效的。当你传递一个方法引用Book::getName 时,getName 方法本身并不符合上面给出的签名(因为它根本没有参数),但它符合这样一个签名的定义:you传递一本书并从其名称中返回一个字符串

因此,考虑一下,在您拥有书单的类中,您还有一个方法可以对Book 执行任何操作,并返回String。下面的方法是一个接收Book并从其名称中获取前10个字符的示例:

public String getReducedBookName(Book b){
  if(b.getName() == null)
     return "";

  String name = b.getName();
  return name.substring(0, name.length() > 10 ? 10 : name.length());
}

您也可以将此方法(不在Book 类中)作为参数传递给map() 方法:

String names = books.stream()
            .map(this::getReducedBookName)
            .filter(n -> !n.isEmpty())
            .collect(Collectors.joining(", "));

【讨论】:

    【解决方案3】:

    如果你喜欢 mapping 超过 map

    作为String

    String names = books.stream().collect(mapping(Book::getName,
        filtering(s -> s != null && ! s.isBlank(),
          joining(", "))));
    

    作为List

    List<String> names = books.stream().collect(mapping(Book::getName,
        filtering(s -> s != null && ! s.isBlank(),
          toList())));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-14
      • 2020-01-23
      • 1970-01-01
      • 2021-07-29
      相关资源
      最近更新 更多