【问题标题】:Shortcut for Guava's Optional use with exceptions?Guava 的 Optional use 的快捷方式有例外吗?
【发布时间】:2011-12-16 12:53:43
【问题描述】:

在 Optional 不存在的情况下,我会继续编写特定的异常抛出器。

例如:

Optional<?> optional = ...;
if (!optional.isPresent()) {
  throw new MyException();
}
Object result = optional.get();

我发现这段代码不是很流畅,尤其是 bang (!) 的使用。我宁愿写这样的东西:

Optional<?> optional = ...;
Object result = optional.orThrow(MyException.class);

Guava 中是否有这样一个我还没找到的捷径?

【问题讨论】:

  • 一般问题。如果你正在检查的东西是可选的,那么有一个像“Optional.orThrow”这样的方法是不是违反“可选”的概念?
  • 完全同意。如果isAbsent(在版本 11 中添加)是有效条件,则不应导致异常。 Optional 的要点是缺席是一个有效的非例外情况。如果缺席是例外情况,您的方法应该只返回对象并在缺席情况下抛出异常。
  • @black panda:那为什么我可以用or() 方法给一个默认值呢?这是一种允许默认行为发生的行为,为什么不是另一个呢? @John B:其中一个用例是一个相当通用的 XML 属性提取器:该属性可能存在也可能不存在,因此该方法返回一个 Optional。然后调用者可能想要强制值的存在(通过抛出异常),或者获取值(如果有),甚至检索默认值。提取器方法只是不知道调用者想对这个值做什么,所以它必须返回一个Optional。为什么这个用例会无效?
  • 另外,您回答的唯一问题是我强烈不同意 Guava:“在我们有限的用例集中完全按照我们的意图使用或离开”方法。这种方法是我在 Guava 中看到的唯一缺点——一个很大的缺点,但仍然是唯一的缺点。我爱所有的休息。我本来希望不要在这里找到同样的“不”墙。顺便说一句,第 11 版不包括​​ isAbsent()(参见 Guava 的第 734 期)
  • @ogregoire:如果某些东西是可选的,那么默认值是可以接受的,这是一种常见且预期的行为。这是可选内容的自然延伸。 “你不指定?哦,好吧,反正是可选的,我就用默认值。”不是“这是可选的。你没有提供它?然后我会抛出一个异常,因为它必须存在。”

标签: java guava


【解决方案1】:

Java 8 的 Optional 有一个允许请求行为的 orElseThrow(Supplier) 方法毫无价值。

它是这样使用的:

Optional<Object> reference = Optional.empty()
reference.orElseThrow(MyOwnException::new); // Throws a new MyOwnException

此外,Java 10 中添加了一个方法 orElseThrow(),如果不存在任何值,则会抛出 NoSuchElementException

Optional<Object> reference = Optional.empty();
reference.orElseThrow(); // Throws a new NoSuchElementException

【讨论】:

    【解决方案2】:

    作为一个 Guava 开发者,让我试着解开这里的逻辑。回复原始问题和直接对问题的评论线程:

    绝对是我们试图强迫 Guava 用户尊重我们良好编程习惯的标准。 (我们的标准强烈受到 Effective Java 等的影响。)

    也就是说,我同意您在这个特定问题中所指的行为有非常好的用例:“如果不存在,则抛出异常。”也许您正在实现一个可以通过两种方式访问​​的类——一种具有可选返回值的方法,另一种假定该值将始终存在的方法,否则抛出异常。例如,Deque 接口提供了 peek、poll 和 offer 的特殊值和抛出异常的版本。

    所有这一切,据我所知,真正的番石榴方法是......

    if (value.isPresent()) {
      return value.get();
    } else {
      throw new MyException();
    }
    

    您提出的“orThrow”方法需要反射(!!),不允许您使用有用的消息自定义异常等。“正常方式”完全可读且更高效。

    有时 Guava 不提供对事物的明确支持,因为对于这些用例,我们认为最好以“正常方式”完成。我认为这里就是这种情况。

    【讨论】:

    • 好吧,orThrow() 方法并不特别需要反射:可以考虑将Exception 的实例化委托给SupplierFunction。无论如何,要点是“真正的 Guava 方法”重复了大约 50 次,大约有 10 个不同的异常,并且仅使用自 Guava 10 以来产生的新代码!我们不想一次又一次地重复这种代码。我们已经创建了几个辅助方法(每个 Exception 构造函数 + 额外的 1 个),这对我们来说已经足够了。我只是想知道是否存在替代方案,但不存在。我不会开票。
    • 供应商或函数将需要使用 icky 匿名类,并且比直接方法所需的代码还要多。如果你有这么多重复,它可能是合适的,虽然我认为最有效的帮助方法是每个异常类型的一个帮助方法,这似乎是你所做的。
    • 鉴于 Java 的 Optional 已经存在了一段时间,并且 Guava 建议尽可能使用 Java 的 Optional,因此我从您的回答中删除了检查以将其放在我的回答中,其中谈到了 Java 的 @987654329 @ 能力。希望你不要介意。
    【解决方案3】:

    这是另一种不添加番石榴的方法:

    Object result = optional.or(new Supplier() {
        public Object get() {
            throw new MyException();
        }
    });
    

    MyException 必须取消选中,但这确实允许您将参数传递给它的构造函数。当然,如果您经常这样做,您可以将 Supplier 存储在某个地方,并在您需要的每个地方使用它。

    Object result = optional.or(SomethingIsMissing.INSTANCE);
    

    【讨论】:

      【解决方案4】:

      我认为这不属于图书馆。我发现很难找到一个接收异常实例的库,以防万一某些事情没有按预期进行,特别是因为在许多情况下,异常必须有一条消息指示出了什么问题。

      话虽如此,您可以创建自己的 Optional 类来满足您的需求。或者,您可以创建自己的 OptionalHelper 类,其中有一个方法可以满足您的需求:

      public class OptionalHelper {
         public <T> T valueOrThrow(final Optional<T> optional) throws MyException {
            if (optional.isPresent()) {
                return optional.get();
            } else {
                throw new MyException();
            }
         }
      }
      

      编辑:

      假设您有一个自定义类,该类接收您需要检查的参数/字段名称,您可以采用类似于 Preconditions 所做的更好的方法:

      public class OptionalHelper {
         public <T> T valueOrFail(final Optional<T> optional, final String fieldName) throws OptionalNotPresentError {
            if (optional.isPresent()) {
                return optional.get();
            } else {
                throw new OptionalNotPresentError(fieldName);
            }
         }
      }
      

      【讨论】:

      • 如果你传递你自己的异常类,你必须在抛出它之前用 Class.newInstance() 实例化它。
      • 如何在 throws 子句中声明这个 .newInstance 的异常?您必须有一个您知道的通用 Exception 子类,其中包含您要抛出的异常,对吗?我觉得很乱。
      • 这肯定是一种混乱的方法。这就是为什么他们在库中没有它,这就是为什么我没有将它包含在我的示例中(仅将其添加为评论)。回答你的问题,如果你从 RuntimeException (而不是 Exception)扩展你的异常类,你可以避免声明它。
      • 最好有一个预定义的异常类并传递一个消息/枚举值/与正在检查的情况相关的其他内容,然后如果所需对象不存在则抛出该异常可选课程。
      • @ogregoire 我不认为它有,我认为这种方法不属于 API。
      【解决方案5】:

      这对我有用(没有反射,只是类型推断):

      public class ExceptionSupplier<T, E extends RuntimeException> implements Supplier<T> {
      
          private final E exception;
      
          private ExceptionSupplier(E exception) {
              this.exception = exception;
          }
      
          public static <T, E extends RuntimeException> ExceptionSupplier<T, E> throwA(E exception) {
              return new ExceptionSupplier<T, E>(exception);
          }    
      
          public static <T, E extends RuntimeException> ExceptionSupplier<T, E> throwA(@SuppressWarnings("UnusedParameters") Class<T> class_, E exception) {
              return new ExceptionSupplier<T, E>(exception);
          }
      
          @Override
          public T get() {
              throw exception;
          }
      }
      

      用法:

      Something something = optionalSomething.or(throwA(Something.class, new SomeException()));
      

      这可能可以进一步扩展,但对于我的用例来说,它已经足够且易于理解。

      【讨论】:

        【解决方案6】:

        official issue here

        Decision: NO - 太贵了,不是通用模式,可以直接使用 !isPresent(), throw

        由于optional.orThrow(new Exception())对性能不好,我更喜欢静态导入,和@timk 's answer类似。

        Result result = optional.or(throwException());
        

        等于

        Result result = optional.or(SomeSupplier.throwException());
        

        静态方法

        public static Supplier<Result> throwException() {
            return new Supplier<Result>() {
                @Override
                public Result get() {
                    throw new RuntimeException();
                }
        
            };
        }
        

        ============

        堆栈跟踪看起来像

        Exception ... RuntimeException
        at SomeSupplier$1.get(SomeSupplier.java:line where throw RuntimeException)
        at SomeSupplier$1.get(SomeSupplier.java:1)
        at com.google.common.base.Absent.or(Absent.java:60)
        at line where call optional.or(throwException());
        

        【讨论】:

        • 这个的堆栈跟踪是什么样的?当您在日志中看到此问题时,它如何跟踪问题?
        • @Ray 我认为这是一种引发特定异常的模式,然后某个地方会处理它。更进一步,日志可以理解。
        • 感谢添加。不确定匿名类如何参与堆栈跟踪,我认为这与讨论有关
        • @Ray 我的荣幸。注意“SomeSupplier$1”中的“$1”,即 SomeSupplier 中定义的匿名类。
        【解决方案7】:

        聚会有点晚了,但在 Guava 中这样做很优雅:

        Optional<?> optional = ...;
        Object result = optional.or(supplyError(MyException::new));
        

        使用以下辅助方法:

        public static Supplier supplyError(Supplier<Error> errorSupplier) {
            return () -> { throw errorSupplier.get(); };
        }
        

        【讨论】:

          猜你喜欢
          • 2020-03-01
          • 1970-01-01
          • 2015-07-28
          • 1970-01-01
          • 2010-10-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多