Java 7中的链式调用?

6

我刚刚在阅读一份Java 7预览演示文稿(pdf),其中有一张关于Chained Invocation的幻灯片。这是幻灯片中使用的示例:

// Construction with setters
DrinkBuilder margarita = new DrinkBuilder();
margarita.add("tequila");
margarita.add("orange liqueur");
margarita.add("lime juice");
margarita.withRocks();
margarita.withSalt();
Drink drink = margarita.drink();

// Construction with chained invocation
Drink margarita = new DrinkBuilder()
    .add("tequila")
    .add("orange liqueur")
    .add("lime juice")
    .withRocks()
    .withSalt()
    .drink();

我对此有着复杂的感觉。一个语句中不应该链接太多方法调用。另一方面,写 margarita.this()margarita.that() 也不是很方便。
我从 Delphi 的世界来到 Java。在 Delphi 中有一个名为 with 的语言结构。这个特性被少数人珍爱,但许多人则憎恶它(或者反过来?)。我认为 with 比链式调用更加优雅(我相信这是基于 void 方法返回其所调用对象的引用的原理 - 这是我不喜欢的部分,因为 void 应该返回nothing)。
我希望 Java 采用 with 语言特性,这样示例代码可以像下面这样编写:
Drink margarita = null;
with (new DrinkBuilder()) {
    add("tequila");
    add("orange liqueur");
    add("lime juice");
    withRocks();
    withSalt();
    margarita = drink();
}

我是不是唯一一个更喜欢这种解决方案而不是链式调用的人?还有其他人觉得with可能是Java语言的一种有用扩展吗?(让我想起有人问过是否需要“Java ++”...)


2
不仅限于Java 7,您可以通过在方法中简单地返回“this”来编写构建器。一个很好的例子是java.lang.StringBuilder。 - Tim Büthe
5
我认为重点在于Java 7允许链式调用而不需要返回"this",从而改变了方法调用的语义。 - OregonGhost
6个回答

13

使用带有初始化器的匿名类,可以将 with 语句在 Java 中进行转换:

Drink margarita = new DrinkBuilder() {{
    add(“tequila”);
    add(“orange liqueur”);
    add(“lime juice”);
    withRocks();
    withSalt();
}}.drink();

使用这种用法的缺点已经有很多记录,在这里可以找到。

链式调用是方法链的别名。这是一个众所周知的惯用法,并适用于任何版本的Java:

class Chained {

    public Chained withFoo() { 
        // ...
        return this;
    }

    public Chained withBar() { 
        // ...
        return this;
    }
}    

JDK 7的一个提案是允许链式调用空返回类型的方法

class ChainedJava7 {

    public void withFoo() { 
        // ...
    }

    public void withBar() { 
        // ...
    }
}    

1
你的答案出现的时候,我正在写有关双大括号初始化的内容 :-) - David Johnstone
2
请注意,JDK7的小语言更改的截止日期已经过去了几个月。 - Tom Hawtin - tackline

2

我相当喜欢那种形式的with语句,但我更喜欢它们的VB版本:

With testObject
    .Height = 100
    .Text = "Hello, World"
    .ForeColor = System.Drawing.Color.Green
End With

作为With块中的每个属性仍然必须以 . 开头,您知道您正在引用对象属性而不是本地变量,从而减少任何命名空间冲突。
如果我们以您的示例为例:
with (new DrinkBuilder()) {
    add(“tequila”);
    add(“orange liqueur”);
    add(“lime juice”);
    withRocks();
    withSalt();
    margarita = drink();
}

很难确定withSalt()DrinkBuilder类的方法还是本地类的方法。如果您只允许在with块中使用with对象的方法,那么我认为它们将变得不太有用。


我的建议是withSalt()应该是Drink Builder的一个方法。如果你发现应用程序没有按照预期运行,你可以调用this.withSalt();来明确指定哪个对象的withSalt()方法应该被调用。 - Peter Perháč

2

这个或许会引起你的兴趣。


1

我不喜欢这种使用 with 的方式;我更喜欢 Python with 语句。不过,我同意你的观点,void 应该意味着 void。在你提供的例子中,如果一个人真的想要能够链接方法调用,他们应该只需更改其方法的返回类型,使它们可链接。


嗯,它是void类型。作为生产者,您不关心返回任何内容,而消费者也不关心接收任何内容(尽管有时他们会)。您为什么要在意呢?从语义上讲,没有任何变化(除了需要输入这么多内容)。 - Chris K
除非你确实返回了某些东西,而且你是隐式地这样做的。如果你没有返回任何东西,那么你就无法调用它的方法。由于这是Java,而几乎所有其他内容都是显式的,我更希望返回值是显式的。 - Hank Gay

1

也许对一个对象进行很多次调用就意味着某些代码需要移动位置?


2
不,这是整个Bean口号“做一件事”的一部分,因为Java没有一流的属性,所以我们必须为每个属性编写getter和setter。这就是原因。还有一些其他的东西从Java 7中删除了(属性)。 - Chris K
@darthcoder - 很高兴你提到了这个问题!我不明白为什么这不是自从Java 1.0以来的一部分。 - Peter Perháč

1

Effective Java的第2条建议中,Joshua Bloch强烈推荐在构造函数有很多参数时使用Builder。原因之一是可以编写它以保证构建的对象始终处于一致的状态。它还避免了在构建对象的类中具有复杂的“telescoping constructors”。另一个原因是,如果您希望构建的对象是不可变的(例如,用于线程安全),则不能具有setter方法。


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