6

我已经阅读了一些关于在访问器中抛出异常的优缺点的答案,但我想我会用一个例子来提出我的具体问题:

public class App {
  static class Test {       
    private List<String> strings;

    public Test() {
    }

    public List<String> getStrings() throws Exception {
        if (this.strings == null)
            throw new Exception();

        return strings;
    }

    public void setStrings(List<String> strings) {
        this.strings = strings;
    }
}

public static void main(String[] args) {
    Test t = new Test();

    List<String> s = null;
    try {
        s = t.getStrings();
    } catch (Exception e) {
        // TODO: do something more specific
    }
  }
}

getStrings()正在抛出一个尚未设置的Exception时间。strings这种情况是否可以通过某种方法更好地处理?

4

8 回答 8

7

是的,那很糟糕。我建议在声明中初始化字符串变量,例如:

private List<String> strings = new ArrayList<String>();

你可以用类似的东西替换setter

public void addToList(String s) {
     strings.add(s);
}

所以没有NPE的可能性。

(或者不要在声明中初始化字符串变量,将初始化添加到 addToList 方法,并让使用它的代码检查 getStrings() 以查看它是否返回 null。)

至于为什么不好,很难用这样一个人为的例子来说明,但似乎有太多的状态改变,你通过让用户处理异常来惩罚这个类的用户。还有一个问题是,一旦您的程序中的方法开始抛出异常,那么它往往会转移到您的代码库中。

检查这个实例成员是否被填充需要在某个地方完成,但我认为它应该在比这个类更了解正在发生的事情的地方完成。

于 2011-09-07T13:07:42.457 回答
7

这实际上取决于您获得的列表正在做什么。我认为一个更优雅的解决方案是返回一个Colletions.EMPTY_LIST或者,正如@Nathan 建议的那样,一个空的ArrayList。然后,使用该列表的代码可以检查它是否为空(如果需要),或者像处理任何其他列表一样使用它。

区别在于没有字符串和没有字符串列表之间的区别。第一个应该由一个空列表表示,第二个应该通过返回null或抛出异常来表示。

于 2011-09-07T13:08:16.840 回答
6

假设在调用 getter之前需要在其他位置设置列表,则处理此问题的正确方法如下:

public List<String> getStrings() {
    if (strings == null)
        throw new IllegalStateException("strings has not been initialized");

    return strings;
}

作为一个未经检查的异常,您不需要在方法上声明它,因此您不会用大量的 try/catch 噪音污染客户端代码。此外,由于这种情况是可以避免的(即客户端代码可以确保列表已经初始化),这是一个编程错误,因此未经检查的异常是可以接受的并且是可取的。

于 2011-09-07T13:09:59.763 回答
2

一般来说,这是一种设计气味。

如果确实是未初始化字符串的错误,则要么删除设置器,并在构造函数中强制执行约束,要么按照 Bohemian 所说的那样,抛出带有解释性消息的 IllegalArgumentException。如果你做前者,你会得到快速失败的行为。

如果不是错误是字符串为空,则考虑将列表初始化为空列表,并在设置器中强制它为非空,类似于上面。这样做的好处是您不需要在任何客户端代码中检查 null。

for (String s: t.getStrings()) {
  // no need to check for null
}

这可以大大简化客户端代码。

编辑:好的,我刚刚看到你正在从 JSON 序列化,所以你不能删除构造函数。因此,您需要:

  1. 从 getter 抛出 IllegalArgumentException
  2. 如果字符串为空,则返回 Collections.EMPTY_LIST。
  3. 或者更好:在反序列化之后添加一个验证检查,专门检查字符串的值,并抛出一个合理的异常。
于 2011-09-07T13:16:55.120 回答
1

我认为您在原始问题背后暴露了一个更重要的问题:您是否应该努力防止 getter 和 setter 出现异常情况?

答案是肯定的,你应该努力避免琐碎的异常。

您在这里拥有的是有效的惰性实例化,在此示例中根本没有动机:

在计算机编程中,延迟初始化是延迟对象的创建、值的计算或其他一些昂贵的过程直到第一次需要它的策略。

在这个例子中你有两个问题:

  1. 您的示例不是线程安全的。对 null 的检查可以同时在两个线程上成功(即发现对象为 null)。然后,您的实例化会创建两个不同的字符串列表。非确定性行为将随之而来。

  2. 在此示例中没有充分的理由推迟实例化。这不是一个昂贵或复杂的操作。这就是我所说的“努力避免微不足道的异常”的意思:值得投资循环来创建一个有用的(但为空的)列表,以确保您不会抛出延迟爆炸空指针异常。

请记住,当您对外部代码施加异常时,您基本上是希望开发人员知道如何用它做一些明智的事情。您还希望等式中没有第三个开发人员将所有内容包装在异常食者中,只是为了捕获和忽略像您这样的异常:

try {
    // I don't understand why this throws an exception.  Ignore.
    t.getStrings();
} catch (Exception e) {
    // Ignore and hope things are fine.
}

在下面的示例中,我使用Null Object 模式向未来的代码指示您的示例尚未设置。但是,Null 对象作为非异常代码运行,因此不会对未来开发人员的工作流程产生开销和影响。

public class App {   
    static class Test {            
        // Empty list
        public static final List<String> NULL_OBJECT = new ArrayList<String>();

        private List<String> strings = NULL_OBJECT;

        public synchronized List<String> getStrings() {
            return strings;
        }      

        public synchronized void setStrings(List<String> strings) {         
            this.strings = strings;     
        } 
    }  

    public static void main(String[] args) {     
        Test t = new Test();      

        List<String> s = t.getStrings();

        if (s == Test.NULL_OBJECT) {
            // Do whatever is appropriate without worrying about exception 
            // handling.

            // A possible example below:
            s = new ArrayList<String>();
            t.setStrings(s);
        }

        // At this point, s is a useful reference and 
        // t is in a consistent state.
    }
}
于 2011-09-07T13:35:45.807 回答
0

我会就此咨询Java Beans规范。最好不要抛出 java.lang.Exception 而是使用 java.beans.PropertyVetoException 并将您的 bean 注册为 java.beans.VetoableChangeListener 并触发适当的事件。这有点矫枉过正,但可以正确识别您要执行的操作。

于 2011-09-07T13:11:43.007 回答
0

这真的取决于你想要做什么。如果您试图强制执行在调用 getter 之前调用设置的条件,则可能会引发 IllegalStateException。

于 2011-09-07T13:13:44.783 回答
-1

我建议从 setter 方法中抛出异常。有什么特别的理由为什么不这样做更好?

public List<String> getStrings() throws Exception {
    return strings;
}

public void setStrings(List<String> strings) {
    if (strings == null)
        throw new Exception();

    this.strings = strings;
}

另外,根据特定的上下文,不能简单地List<String> strings由构造函数直接传递吗?这样也许你甚至不需要 setter 方法。

于 2011-09-07T13:06:23.910 回答