使用Optional

时间:2016-10-25 00:07:38

标签: java optional

我有一个类型的树层次结构,可以识别他们的孩子,但不知道他们的父母。现在我正在创建一个外部注册表,从外部提供相反的路径:

public interface Registry<X>{
    Optional<X> parent(X node);
}

现在我想在该接口中实现一个方法,该方法从任何给定节点获取此层次结构的根音(根节点可以是传入的节点或任何祖先)。

我到目前为止:

default X root(X node) {

    X current = node;

    for (Optional<X> opt = Optional.of(current);
         opt.isPresent();
         opt = opt.flatMap(this::parent)) {

        if (opt.isPresent()) {
            current = opt.get();
        }
    }

    return current;

}

虽然这有效,但感觉有点笨拙。 Optional.isPresent()被调用两次,变量current被重新分配。有什么方法可以让它更优雅和实用吗?

4 个答案:

答案 0 :(得分:2)

我在考虑

default X root(X node) {
    X root = node;

    for (Optional<X> parentOpt = parent(root); parentOpt.isPresent(); root = parentOpt.get())
        ;

    return root;
}

我不喜欢处理null个参数。因此,我们遵循parent的实现,您可能会将其记录为在Optional参数上返回空null

如果参数为null,我们也会返回null

如果参数不是null,我们将其值保存在root中并开始循环。我们得到了潜在的父母。如果它存在,我们更新root,然后重试。否则,我们会中断,并返回root中最后保存的值,因为这是我们得到的。

我认为笨拙的感觉来自于Optional左右node。我认为你不需要那个。

答案 1 :(得分:0)

下面是Guava和JDK流版本,但绝对的赢家是JavaSlang。我喜欢它!

import javaslang.collection.Stream;
import java.util.Optional;
// ...
default X root(X node) {
    return Stream.iterate(Optional.of(node),
                              t -> t.flatMap(this::parent)
                         ).takeWhile(Optional::isPresent)
                          .last()
                          .orElseThrow(NoSuchElementException::new);
}

Stream.iterate()方法正是我在其他两个版本中所缺少的。

<强> Guava

首先,我们将创建一个名为Optionals的辅助类:

// to avoid misunderstandings:
import java.util.Optional;
import java.util.function.Function;

import com.google.common.collect.AbstractIterator;

public final class Optionals {

    private Optionals(){}

    /**
     * Given an instance of type T and a function from T to Optional&lt;T&gt;,
     * return an Iterable&lt;T&gt;. This Iterable will keep returning values
     * by repeatedly applying the supplied function until the returned Optional 
     * is not present.
     */
    public static <T> Iterable<T> stream(
        final T start, final Function<T, Optional<T>> increment) {

        return () -> new AbstractIterator<T>() {
            Optional<T> current = Optional.of(start);

            @Override
            protected T computeNext() {
                if (!current.isPresent()) return endOfData();
                final T data = current.get();
                current = current.flatMap(increment);
                return data;
            }
        };
    }
}

这当然是更多的样板,但是它可以重复使用,现在我们可以用简洁的方式编写接口方法:

default X root(final X node) {
    return Iterables.getLast(Optionals.stream(node, this::parent));
}

显然,没有番石榴就可以实现这一点,但是它会更加混乱。

Java 8 Stream版本:

public final class Streams {
    private Streams(){}

/**
 * Given an instance of type T and a function from T to Optional&lt;T&gt;,
 * return a Stream&lt;T&gt;. This Stream will keep returning values by
 * repeatedly applying the supplied function until the returned Optional
 * is not present.
 */
public static <X> Stream<X> stream(
        final X start, final Function<X, Optional<X>> increment) {

    return StreamSupport.stream(
            new Spliterator<X>() {

                Optional<X> next = Optional.ofNullable(start);

                @Override
                public boolean tryAdvance(final Consumer<? super X> action) {
                    final boolean present = next.isPresent();
                    if (present) action.accept(next.get());
                    next = next.flatMap(increment);
                    return present;
                }

                @Override
                public Spliterator<X> trySplit() { return null; }

                @Override
                public long estimateSize() { return Long.MAX_VALUE; }

                @Override
                public int characteristics() { return Spliterator.ORDERED; }
            }, false
    );
}

这导致了这段代码:

default X root(final X node) {
    return Streams.stream(node, this::parent)
                  .reduce((left, right) -> right)
                  .orElseThrow(NoSuchElementException::new);
}

答案 2 :(得分:0)

@Sotirios的答案简洁而且非常好。

您在尝试使用解决方案时实施了Iterator,我的解决方案是将其作为Spliterator / Stream实施。对于您的特定问题,它可能过于复杂,但它可能对您的图书馆的其他地方有用,或者对于浏览堆栈溢出的其他人可能有用。

public class ParentSpliterator<T> implements Spliterator<T> {
    private final Registry<T> registry;
    private Optional<T> currentNodeOpt;

    public ParentSpliterator(Registry<T> registry, T startNode) {
        this.registry = registry;
        this.currentNodeOpt = Optional.of(startNode);
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        if (!currentNodeOpt.isPresent()) {
            return false; // stream is empty
        } else {
            T currentNode = currentNodeOpt.get();
            action.accept(currentNode);
            currentNodeOpt = registry.parent(currentNode);
            return true;
        }

//        // Alternative implementation (more Stream-ish):        
//        return currentNodeOpt.map(node -> {
//            action.accept(node);
//            currentNodeOpt = registry.parent(node);
//            return node;
//        }).isPresent();
    }

    @Override
    public Spliterator<T> trySplit() {
        return null;  // Cannot be split.
    }

    @Override
    public long estimateSize() {
        return Long.MAX_VALUE; // No quick way to estimate size.
    }

    @Override
    public int characteristics() {
        return Spliterator.ORDERED; // maybe others?
    }

    public static void main(String[] args) {
        Registry.CountDownRegistry cdr = new Registry.CountDownRegistry();
        ParentSpliterator<Integer> parentSpliterator = new ParentSpliterator<>(cdr, 3);
        Stream<Integer> stream = StreamSupport.stream(parentSpliterator, false);
        //stream.forEach(System.out::println);

        // Using a reduce to pick the last element of the Stream:
        Integer root = stream.reduce((node, nextNode) -> nextNode).get();
        System.out.println(root);
    }
}

您需要此实用程序类才能使示例正常工作:

public interface Registry<X> {
    Optional<X> parent(X node);

    public static class CountDownRegistry implements Registry<Integer> {

        @Override
        public Optional<Integer> parent(Integer node) {
            if (node > 0) {
                return Optional.of(node - 1);
            } else {
                return Optional.empty();
            }
        }
    }
}

答案 3 :(得分:0)

我认为你已经非常接近你尝试过的循环了。它可以简化一点,因为只有在循环条件为真时才执行循环体,因此您不必在循环内重新测试opt.isPresent。如果重复使用node参数,则可以保存变量。 (我知道,它是一种风格的东西。)增量部分中的flatMap调用并不会给你带来太大的影响,因为你知道opt存在于此点;您也可以在parent结果opt.get时调用node,同时指定default X root(X node) { for (Optional<X> opt = Optional.of(node); opt.isPresent(); opt = parent(node = opt.get())) ; return node; } node这给出了:

parent

你几乎不得不假设default X root(X node) { for (Optional<X> opt; (opt = parent(node)).isPresent(); node = opt.get()) ; return node; } 是非空的,所以你可以将default Stream<X> stream(X node) { return Stream.iterate(Optional.of(node), Optional::isPresent, op -> op.flatMap(this::parent)) .map(Optional::get); } 调用移到循环条件中,简化了一些事情:

default X root(X node) {
    return Optional.of(node).flatMap(this::parent).map(this::root).orElse(node);
}

Java 9将有一个新的three-arg Stream.iterate factory,可以让您非常方便地创建从叶到根的节点流。这三个参数就像for循环的三个语句一样,除了它们不会产生副作用。您可以按如下方式将原始for循环音译为流:

Optional

最后,有这样的:

Optional

关于这项技术有几点需要注意。

  • 它违反了我一直支持的MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuV7OdmPSutrlOE9lF3YdW4ymGn+qselCOycMk95Tobw1PcqCeAWkrnxUECpAdnHGrUKmFbEDHs3wnwzLTbfa3GvE5dvvmluug78X3RYEFQiMh1QpfS5fBfvs4WQKw7oigko3G0UwZLZFnZ4E4WKTQi4wbCgjwQJFMnMGJfFYNcoSJluVg/q8z3bVxfDOV0ZPWccmvA3bTf9YFHKCC3clscQrGf1NPnBGcBGm+s06t3EljoSmpjtyTgSiGrqBZ8TSCQxoyXxS+RkhNTigg6mqW9hIisxYYqlbzvRnCDhuqgZfmP7t65QG5raELVE7d+Ia+dgh024luZ9+vSk4Qb65DQIDAQAB 风格规则之一,尤其是这一规则:&#34;#4:创建{一般不是一个好主意{1}}用于链接方法以获取值的特定目的。&#34; (link
  • 它是递归的,这意味着如果您的层次结构太深,它可能会破坏您的堆栈。
  • 如果你使用这种技术,你的同事会避开你。

我不建议写这样的代码,但我想在其他人做之前把它放在那里。 : - )