java8流和状态

时间:2014-10-07 15:00:22

标签: java java-8 java-stream

我编写了这段代码,我厌倦了在这里使用流,但实现是hacky并且使用tradictional for-each循环会更好。是否有更好的方法来使用流?

它是一个非常简单的XML生成器,来自两个部分路径。

    input = "aaa.zzz\r\n"
            + "  \r\n"
            + "aaa.xxxx    \r\n"
            + "bbb.cccc";

输出:

<aaa><xxxx>xxxx</xxxx>
<zzz>zzz</zzz>
</aaa><bbb><cccc>cccc</cccc>
</bbb>

代码:

public static void main(String[] args) {
  String input = "aaa.zzz\r\n"
          + "  \r\n"
          + "aaa.xxxx    \r\n"
          + "bbb.cccc";
  List<String> list = Arrays.stream(input.split("\r\n")).map(String::trim)
          .filter(q -> !q.isEmpty())
          .sorted()
          .map(PathToXml::extracted)
          .collect(Collectors.toList());
  list.add("</" + lastTag + ">");
  for (String data : list) {
    System.out.println(data);
  }
}

private static String lastTag = null;

private static String extracted(String path) {
  String[] paths = path.split("\\.");

  String tag = paths[0];
  String node = paths[1];

  String s = "";
  if (!tag.equals(lastTag)) {
    if (lastTag != null) {
      s += "</" + lastTag + ">";
    }
    s += "<" + tag + ">";
  }
  s += "<" + node + ">" + node + "</" + node + ">";

  lastTag = tag;
  return s;
}

我知道我可以将它转换为foreach循环并使其更少hacky,但也许可以使用流来完成它。

1 个答案:

答案 0 :(得分:3)

我会改变一些事情。首先,您的XML转换路径看起来并不像是可以轻松扩展到更复杂的用例。除非您坚持使用两个嵌套标记,否则您将需要一个可以为Collection API和Stream API提供的层次结构建模的类。以下通用实用程序类可能是一个起点:

public final class Tree<T> {
  T value;
  Map<T,Tree<T>> sub=Collections.emptyMap();
  public Tree(T value) {
    this.value=value;
  }
  public Tree<T> add(T value) {
    if(sub.isEmpty()) sub=new HashMap<>();
    return sub.computeIfAbsent(value, Tree::new);
  }
  public void addAll(Tree<T> tree) {
    if(!tree.sub.isEmpty()) {
      if(sub.isEmpty()) sub=new HashMap<>();
      for(Tree<T> t: tree.sub.values()) add(t.value).addAll(t);
    }
  }
  public <R> R forAll(
    Function<T, R> open, Function<T, R> single, Function<T, R> close,
    BiFunction<R,R,R> combiner) {
      if(sub.isEmpty()) return single.apply(value);
      else {
        Iterator<Tree<T>> it=sub.values().iterator();
        R result = value!=null? open.apply(value):
          it.next().forAll(open, single, close, combiner);
        while(it.hasNext())
          result=combiner.apply(result, it.next().forAll(open,single,close,combiner));
        return value!=null? combiner.apply(result, close.apply(value)): result;
      }
  }
}

故意保持极简主义,不受特定用例的约束。为了支持从Stream(例如从路径)生成层次结构,可以使用以下Collector

public final class TreeCollector<T>
    implements Collector<T, TreeCollector<T>, Tree<T>> {
  T value;
  Tree<T> root, current;
  public TreeCollector(T rootValue) {
    value=rootValue;
    current=root=new Tree<>(value);
  }
  public Supplier<TreeCollector<T>> supplier() {
    return ()->new TreeCollector<>(value);
  }
  public BiConsumer<TreeCollector<T>, T> accumulator() {
    return (c,t)->{ c.current=c.current.add(t); };
  }
  public BinaryOperator<TreeCollector<T>> combiner() {
    return (a,b)->{ a.root.addAll(b.root); return a; };
  }
  public Function<TreeCollector<T>, Tree<T>> finisher() {
    return x->x.root;
  }
  public Set<Characteristics> characteristics() {
    return Collections.emptySet();
  }
}

第二件事是,如果您想有效地使用Stream API,则不应使用String.split使用生成的数组创建Stream,也不应使用String.trim映射操作已经是模式匹配的结果。有Pattern.splitAsStream允许处理模式匹配结果而不将它们存储到中间数组中。

将它组合在一起,复制问题代码结果的用例如下:

Pattern dot = Pattern.compile(".", Pattern.LITERAL);

Tree<String> root=
Pattern.compile("\\s+", Pattern.DOTALL).splitAsStream(input)
       .map(path->dot.splitAsStream(path).collect(new TreeCollector<>(null)))
       .collect(()->new Tree<>(null), Tree::addAll,  Tree::addAll);
String xml=root.forAll(s->'<'+s+'>', s->'<'+s+'>'+s+"</"+s+">\n", s->"</"+s+'>',
                       String::concat);
System.out.println(xml);

虽然以下对我来说更自然:

Tree<String> root=
Pattern.compile("\\s+", Pattern.DOTALL).splitAsStream(input)
       .map(path->dot.splitAsStream(path).collect(new TreeCollector<>(null)))
       .collect(()->new Tree<>(null), Tree::addAll,  Tree::addAll);
String xml=root.forAll(s->'<'+s+'>', s->'<'+s+"/>", s->"</"+s+">\n", String::concat);
System.out.println(xml);

它产生

<aaa><xxxx/><zzz/></aaa>
<bbb><cccc/></bbb>

请注意,您可以使用此代码收集path.strings.of.arbitrary.length。

另一个优点是构建分层结构可以在不对整个流进行排序的情况下工作,如果您有更多的路径元素,这将是一个很大的好处。


附录

如果您遵守原始问题的限制,则可以使用collect操作创建Map<…,Set<…>>Map<…,List<…>>来建模二级层次结构。在这种情况下,您可以在没有其他类的情况下解决任务有两种方法可以实现这一点,使用现有Collectors的组合或通过指定供应商,累加器和组合器函数创建特别收集器。

组合现有的收集器实现(使用import static java.util.stream.Collectors.*;):

Pattern dot = Pattern.compile(".", Pattern.LITERAL);
Pattern.compile("\\s+", Pattern.DOTALL).splitAsStream(input)
       .map(path -> dot.split(path, 2))
       .collect(groupingBy(path->path[0],
                    mapping(path->path[1], toCollection(TreeSet::new))))
       .forEach((p,l) -> {
           System.out.print('<'+p+'>');
           for(String s:l) System.out.println('<'+s+'>'+s+"</"+s+'>');
           System.out.print("</"+p+'>');
       });

创建ad-hoc收集器:

Pattern dot = Pattern.compile(".", Pattern.LITERAL);
Pattern.compile("\\s+", Pattern.DOTALL).splitAsStream(input)
       .map(path -> dot.split(path, 2))
       .collect(() -> new TreeMap<String,Set<String>>(),
                (m,p) -> m.computeIfAbsent(p[0], k->new TreeSet<>()).add(p[1]),
                (m1,m2) -> m2.forEach(
                           (k,v)->m1.computeIfAbsent(k,x->new TreeSet<>()).addAll(v)))
       .forEach((p,l) -> {
           System.out.print('<'+p+'>');
           for(String s:l) System.out.println('<'+s+'>'+s+"</"+s+'>');
           System.out.print("</"+p+'>');
       });