用于字符串操作的Java 8 Streams

时间:2017-02-10 05:00:45

标签: java-8 java-stream

我想在一个字符串上执行多个任务。 我需要使用分隔符("/")获取字符串并提取不同的子字符串,然后反转子字符串列表,最后使用另一个分隔符(".")连接它们,使{{1}会变成:/tmp/test/hello/world/

使用Java 7代码如下:

world.hello.test.tmp

我想知道如何使用Java 8流和它对字符串的连接函数做同样的事情

4 个答案:

答案 0 :(得分:10)

最直接的方法是将条款收集到列表中,反转列表并加入新的分隔符:

import static java.util.stream.Collectors.toCollection;

List<String> terms = Pattern.compile("/")
        .splitAsStream(str)
        .filter(s -> !s.isEmpty())
        .collect(toCollection(ArrayList::new));

Collections.reverse(terms);

String result = String.join(".", terms);

你可以在不收集中间列表的情况下进行,但它的可读性较差,不值得为实际目的而烦恼。

要考虑的另一个问题是您的字符串似乎是路径。通常最好使用Path类而不是手动分割“/”。以下是您将如何执行此操作(此方法还演示了如何使用IntStream对索引向后流式传输列表):

Path p = Paths.get(str);

result = IntStream.rangeClosed(1, p.getNameCount())
        .map(i -> p.getNameCount() - i)  // becomes a stream of count-1 to 0
        .mapToObj(p::getName)
        .map(Path::toString)
        .collect(joining("."));

这将具有独立于操作系统的优势。

答案 1 :(得分:5)

如果您不想要中间列表,只想反向加入String

String delimiter = ".";
Optional<String> result = Pattern.compile("/")
                                 .splitAsStream(str)
                                 .filter(s -> ! s.isEmpty())
                                 .reduce((s, s2) -> String.join(delimiter, s2, s));

或者只使用.reduce((s1, s2) -> s2 + '.' + s1);,因为它可能与String.join(".", s2, s1);一样可读(感谢Holger提供建议)。

从那时起,您可以执行以下操作之一:

result.ifPresent(System.out::println); // print the result
String resultAsString = result.orElse(""); // get the value or default to empty string
resultAsString = result.orElseThrow(() -> new RuntimeException("not a valid path?")); // get the value or throw an exception

使用StreamSupportSpliterator的另一种方式(受Mishas建议启发使用Path):

Optional<String> result = StreamSupport.stream(Paths.get(str).spliterator(), false)
                                       .map(Path::getFileName)
                                       .map(Path::toString)
                                       .reduce((s, s2) -> s2 + '.' + s);

当然,您可以通过省略中间Optional - 对象来简化它,并立即调用您想要的方法:

stream(get(str).spliterator(), false)
    .map(Path::getFileName)
    .map(Path::toString)
    .reduce((s, s2) -> s2 + '.' + s)
    .ifPresent(out::println); // orElse... orElseThrow

在上一个示例中,您将添加以下静态导入:

import static java.lang.System.out;
import static java.nio.file.Paths.get;
import static java.util.stream.StreamSupport.stream;

答案 2 :(得分:2)

您的Java 7代码不是我称之为直接解决方案的代码 这就是我将如何在Java 7中实现它:

String str = "/tmp/test/";

StringBuilder sb = new StringBuilder(str.length()+1);
for(int s=str.lastIndexOf('/'), e=str.length(); e>=0; e=s, s=str.lastIndexOf('/', e-1)) {
    if(s+1<e) sb.append(str, s+1, e).append('.');
}
if(sb.length()>0) sb.setLength(sb.length() - 1);
System.out.println("result " + sb);

再次考虑它,这也是我在Java 8中实现它的方式,因为使用Stream API并没有真正改善这种操作。

答案 3 :(得分:0)

您可以这样写:

String newPath = Arrays.asList(path.split("/")).stream()
            .filter(x -> !x.isEmpty())
            .reduce("",(cc,ss)->{
                if(!cc.isEmpty())
                    return ss+"."+cc;
                else return ss;
            },(s1,s2)->s2+s1);

过滤器消除了第一个反斜杠,并且reduce方法必须控制是否还有其他最后一个空字符串。