使用不同的最后分隔符连接字符串

13

使用stream.collect(Collectors.joining(", ")),我可以轻松地将流中的所有字符串用逗号分隔符连接起来。 可能的结果是"a, b, c"。但是,如果我想将最后一个分隔符更改为不同的内容呢?例如,将其改为" and ",以便获得"a, b and c"作为结果。 是否有简单的解决方案?


3
老实说,这是不可能的。Collectors.joining使用了一些非常笨拙的技巧,当你尝试做类似的事情时,它们会完全崩溃。 - Louis Wasserman
1
实现自己的Collector。至于“容易”,那就取决于技能和观点。 - Andreas
@Andreas,是否有可能实现这样的收集器?收集器如何知道何时使用特殊分隔符? - principal-ideal-domain
这个问题有一种将其转换为数组的方法(一行代码)。然后你可以遍历它并添加自定义分隔符。使用数组应该很简单 :) - RisingSun
4
如果你想要牛津逗号,需要将分隔符指定为 ", "", and "。(我也喜欢用它。) - David Conrad
1
实际上,那样做不太行,但我已经在我的答案中添加了牛津逗号的变体。 - David Conrad
7个回答

17
如果它们已经在列表中,就不需要流;只需连接除最后一个元素以外的所有子列表,并连接其他分隔符和最后一个元素:
int last = list.size() - 1;
String joined = String.join(" and ",
                    String.join(", ", list.subList(0, last)),
                    list.get(last));

这里有一种使用 Collectors.collectingAndThen 实现上述功能的版本:

stream.collect(Collectors.collectingAndThen(Collectors.toList(),
    joiningLastDelimiter(", ", " and ")));

public static Function<List<String>, String> joiningLastDelimiter(
        String delimiter, String lastDelimiter) {
    return list -> {
                int last = list.size() - 1;
                if (last < 1) return String.join(delimiter, list);
                return String.join(lastDelimiter,
                    String.join(delimiter, list.subList(0, last)),
                    list.get(last));
            };
}

这个版本还可以处理流为空或只有一个值的情况。感谢Holger和Andreas的建议,极大地改进了这个解决方案。

我在评论中建议使用", "", and"作为定界符来实现牛津逗号,但对于两个元素会产生错误的结果"a, and b",所以为了好玩,这里提供一个可以正确处理牛津逗号的版本:

stream.collect(Collectors.collectingAndThen(Collectors.toList(),
    joiningOxfordComma()));

public static Function<List<String>, String> joiningOxfordComma() {
    return list -> {
                int last = list.size() - 1;
                if (last < 1) return String.join("", list);
                if (last == 1) return String.join(" and ", list);
                return String.join(", and ",
                    String.join(", ", list.subList(0, last)),
                    list.get(last));
            };
}

另外,我将其粘贴到Eclipse中,然后出现了编译错误,可以通过显式给出类型来解决:Collector.<String,List<String>,String>of( - Andreas
1
嗯,javac没有给我编译错误,但我可以添加显式类型。使用的是哪个版本的Eclipse? - David Conrad
2
由于这里唯一相关的功能是 finisher,因此您可以简单地使用 Collectors.collectingAndThen(Collectors.toList(), list -> { ... })。 顺便说一下,您还可以使用 String.join(lastDelimiter, String.join(delimiter, list.subList(0, last)), list.get(last)) 作为返回语句。 - Holger
1
@Ivin 我刚试了 joiningLastDelimiter()joiningOxfordComma() 两种解决方案,它们对于空列表和只有一个元素的列表都可以正常工作。只有第一种解决方案需要为这些情况添加保护条件。 - David Conrad
1
这可以更简单,不需要返回一个函数,即只需使用一个静态方法来接受一个列表并返回一个字符串(然后使用函数引用MyClass::joining...作为“finisher”参数的参考)。 - Joshua Goldberg
显示剩余2条评论

7
如果你对使用 "a, b, 和 c" 没有问题,那么可以使用我 StreamEx 库的 mapLast 方法,它扩展了标准的 Stream API 并添加了额外的操作。请注意保留 HTML 标签。
String result = StreamEx.of("a", "b", "c")
                        .mapLast("and "::concat)
                        .joining(", "); // "a, b, and c"

mapLast 方法将给定的映射应用于最后一个流元素,保持其他元素不变。我甚至有类似的单元测试


1
这不是OP想要的答案,但如果你是牛津逗号的粉丝,这个答案在语法上更加“正确”。谢谢Tagir。 - Sean Connolly

1
如果你正在寻找旧版Java解决方案,使用Guava libraries会很容易。
    List<String> values = Arrays.asList("a", "b", "c");
    String output = Joiner.on(",").join(values);
    output = output.substring(0, output.lastIndexOf(","))+" and "+values.get(values.size()-1);
    System.out.println(output);//a,b and c

1
尝试先用stream.collect(Collectors.joining(" and "))连接最后两个字符串。
然后使用你在问题中使用的代码将所有剩余字符串和这个新字符串连接起来:stream.collect(Collectors.joining(", "))

0
String str = "a , b , c , d";
String what_you_want = str.substring(0, str.lastIndexOf(","))
        + str.substring(str.lastIndexOf(",")).replace(",", "and");

// what_you_want is : a , b , c and d

0
    List<String> names = Arrays.asList("Thomas", "Pierre", "Yussef", "Rick");
    int length = names.size();
    String result = IntStream.range(0, length - 1).mapToObj(i -> {
        if (i == length - 2) {
            return names.get(i) + " and " + names.get(length - 1);
        } else {
            return names.get(i);
        }
    }).collect(Collectors.joining(", "));

0

这不是一个Streams-API的解决方案,但速度相当快。享受吧!

public static final <E> String join(
    Iterable<E> objects, String separator, String lastSeparator) 
{
    Objects.requireNonNull(objects);

    final String sep = separator == null ? "" : separator;
    final String lastSep = lastSeparator == null ? sep : lastSeparator;

    final StringBuilder builder = new StringBuilder();

    final Iterator<E> iterator = objects.iterator();
    while (iterator.hasNext()) {
        final E next = iterator.next();
        if (builder.length() > 0) {
            if (iterator.hasNext()) {
                builder.append(sep);
            }
            else {
                builder.append(lastSep);
            }
        }
        builder.append(next);
    }

    return builder.toString();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接