Lambda表达式 vs 匿名内部类 vs 静态嵌套类

4
这个类有三个方法,它们都做同样的事情,等待三秒钟以使页面加载完成,并在页面加载完成后返回true。
我的问题是,我如何决定哪一段代码最好?
我知道Lambda优于匿名类,但为什么静态嵌套类(pageLoaded3)不可以?根据这篇文章,它也应该适用。
public final class ExpectedConditions {

    private ExpectedConditions() {
    }

    public static ExpectedCondition<Boolean> pageLoaded1() {
        return (driver) -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return !((JavascriptExecutor) driver).executeScript("return performance.timing.loadEventEnd", new Object[0])
                    .equals("0");
        };
    }

    public static ExpectedCondition<Boolean> pageLoaded2() {
        return new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver driver) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return !((JavascriptExecutor) driver)
                        .executeScript("return performance.timing.loadEventEnd", new Object[0]).equals("0");
            }
        };
    }

    public static ExpectedCondition<Boolean> pageLoaded3() {
        return new PageLoaded();
    }

    private static class PageLoaded implements ExpectedCondition<Boolean> {
        @Override
        public Boolean apply(WebDriver driver) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return !((JavascriptExecutor) driver).executeScript("return performance.timing.loadEventEnd", new Object[0])
                    .equals("0");
        }
    }

}

这是三种方法返回类型的接口:
public interface ExpectedCondition<T> extends Function<WebDriver, T> {}

3
您的三种方法在功能上是等效的(您的代码将执行相同的操作)。什么让您认为静态嵌套类是不合适的? - assylias
我该如何决定哪一段代码是最好的?您能更具体地说明您正在寻找什么吗?对于这种问题,我的一般回答是“最易读的版本”... - Ivo Mori
这是我的问题。我知道这三个都可以工作,但在这种情况下,匿名类不应该被使用。当阅读成员内部类时,他们也明确表示,当没有外部类变量被使用时,使用静态内部类。那么是否存在规则或约定,也表明应该或不应该使用静态内部类?还是只是个人偏好? - Yoshua Nahar
我认为第三个最易读。但是喜欢使用lambda的人可能会说第一个? - Yoshua Nahar
我会使用你认为最简单的方法。注意:只有一两行的lambda表达式可能是最好的选择。我会添加一个辅助方法,例如 static void pause(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } - Peter Lawrey
最好不要使用None,避免使用Thread.sleep。https://dev59.com/WmUp5IYBdhLWcg3wrY9p - Alexander Anikin
1个回答

3

如评论中已经提到的:这些功能是等价的。因此,从技术上讲,它们之间的差异微不足道。所有方法都编译成类似的字节码。所以关于哪个是“最好”的问题只能在考虑代码本身时回答。而在那里,可以应用不同的标准。

首先,正如 Peter Lawrey 在评论中建议的那样:您可以将 Thread.sleep 调用提取到一个帮助方法中:

private static void pause(long ms)
{
    try
    {
        Thread.sleep(ms);
    }
    catch (InterruptedException e)
    {
        Thread.currentThread().interrupt();
    }
}

此外,您可以将实现的核心功能提取为辅助方法:
private static Boolean execute(WebDriver driver)
{
    pause(3000);
    return !((JavascriptExecutor) driver)
        .executeScript("return performance.timing.loadEventEnd", new Object[0])
        .equals("0");
}

使用这种方法,给定方法之间的相似性变得更加明显:它们只是调用此方法的不同方式!我认为引入这种方法将具有额外的优点。该方法是一个明确的“构建块”,具有清晰的接口(并希望具有清晰的JavaDoc)。
另外,这个方法将为第四个实现打开大门 - 也就是使用方法引用的实现。
public static ExpectedCondition<Boolean> pageLoaded4()
{
    return ExpectedConditions::execute;
}

现在您有四种实现所需功能的方法:

  1. 使用lambda表达式
  2. 使用匿名类
  3. 使用静态内部类
  4. 使用方法引用

在实际操作中,可以讨论利弊:

  • Approach 3, with the static innner class: This is not necessary. If the static inner class is only a wrapper to a method call, it does not serve a real purpose. It introduces a new name (PageLoaded in your case), and a new indirection in the source code - meaning that the programmer has to look up the PageLoaded class to see what it does. This might be a different story in other cases. E.g. if the PageLoaded class had additional methods or fields, but this is not the case here.

  • Approach 2, with an anonymous inner class: One could argue that an anonymous class for a functional interface is just "old-fashioned" or the "pre-Java8-way" of writing it. If the type that has to be implemented is a functional interface (i.e. has a single abstract method), then using a lambda or method reference is just shorter and more convenient, and does not have any obvious drawbacks. (Of course, this would not be possible if the type that has to be implemented had multiple abstract methods. But again, this is not the case here)

  • Approach 1, with a lambda: This is the standard way of implementing such a functional interface in Java 8. I, personally, prefer to have short lambda bodies. A lambda like the one in your question

    return (driver) -> {
        // "Many" lines of code, maybe even nested, with try-catch blocks
    };
    

    is IMHO not so readable and concise, and I usually try to keep these bodies as short as possible. In the best case, the { brackets } can be omitted - in doubt, by introducing a method that comprises the lambda body. And in this case, one can often boil it down to the last approach:

  • Approach 4, with a method reference. It may be a bit subjective, but I consider this one as the most readable. The method has the advantage of being a "building block" with a clear functionality that can be clearly specified in the JavaDocs. Moreover, you might even consider to omit the wrapping method (called pageLoaded4 above), and simply offer the method as a public one. The user, who formerly called

    ExpectedCondition<Boolean> condition = 
        ExpectedConditions::pageLoaded4();
    

    might then just use the method directly

    ExpectedCondition<Boolean> condition = 
        ExpectedConditions::execute;
    

    But this depends on the exact application pattern and the intended interface of this class. It might not be desired to expose this method directly.


非常好的答案,我很感激。我的问题是我找不到任何地方说明是否应该使用带有静态内部类的方法3。而你很好地总结了它。 - Yoshua Nahar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接