我正在阅读Effective Java Edition 3。在第 2 章第 14 页中,作者讨论了构建器模式并呈现了以下代码:
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}
}
上述抽象类的实现:
public class NyPizza extends Pizza {
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() { return this; }
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
我们可以使用这样的代码:
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE).addTopping(ONION).build();
书中引用:
注意
是 泛型 和 递归类型 参数。这与抽象 self 方法一起允许方法 链接可以在子类中正常工作,而不需要强制转换。Pizza.Builder
现在我的问题是
<T extends Builder<T>>
添加到Pizza
类中的力量/价值是什么以及它与<T extends Builder>
有何不同?如果你要用简单的英语向一个五岁的孩子解释<T extends Builder<T>>
,你会如何解释?
我无法弄清楚超类中抽象 self 方法的用途?
我因为评论区而添加这部分
想象一下我已经像这样更改了上面的代码(这不会是仅用于说明目的的最佳示例):
我将
NyPizza.Builder
更改为通用:
public class NyPizza extends Pizza {
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder<T> extends Pizza.Builder<Builder<T>> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() { return this; }
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
和像这样的
Pizza
类:
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
abstract static class Builder<T extends Builder> {
T obj;
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
public T builder(){
return obj;
}
abstract Pizza build();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}
}
并像这样使用上面的类:
NyPizza.Builder<String> test = new NyPizza.Builder<String>(SMALL).builder();
现在因为我没有以这种形式定义
class Builder<T extends Builder>
:class Builder<T extends Builder<T>>
类中的builder
方法不应该能够检测到T的类型是Pizza
,但它可以。这怎么可能?如何将其更改为需要铸造?
NyPizza.Builder<String>
这意味着
class Foo<T extends Foo<T>> {
T foo() { ... }
}
类中的
foo()
方法将返回一个 Foo<T>
的实例,因为 Foo<T>
表示的所有可能类型都是 T
的子类。所以它可以返回自身 - 这就是为什么它对于与构建器的方法链接很有用;但实际上没有什么可以阻止它返回范围内的其他类。 如果您将其声明为:
Foo<T>
然后这使得
class Foo<T extends Foo> {
T foo() { ... }
}
成为
原始类型
T
:Foo
返回foo()
,而不是Foo
。因为原始类型会删除所有泛型,这意味着在像 Foo<T>
这样的调用链中,您将在第一次调用后丢失类型信息。在许多情况下,这可能并不“感觉”超级重要。但在几乎所有情况下都应该避免使用原始类型。这里原始类型的后果是: 如果您没有返回 self 类型(上面提到“实际上没有什么可以阻止它返回其他类”),那么您将在第一个方法调用后丢失该“其他类”。
如果您在foo().foo()
Foo
List<String> myList() { ... }
将返回原始 Foo
,而不是 List
(原始类型会擦除 所有泛型,而不仅仅是与省略的类型变量相关的泛型)。 这些显然并不适用于所有情况;但由于引入不必要的原始类型是一种不好的做法,因此请确保不要引入它们。
问题:“现在因为我没有以这种形式定义 Builder 类:class Builder
Pizza 类中的 builder 方法应该无法检测到 T 的类型是 NyPizza.Builder 但它可以。这怎么可能?如何将其更改为需要铸造?”