具有递归类型参数的泛型类型以及抽象 self 方法如何允许方法链接正常工作?
how generic type with a recursive type parameter along with the abstract self method allows method chaining to work properly?
我正在阅读有效Java第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
}
}
上述抽象的实现 class:
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();
书中引述:
Note that Pizza.Builder
is a generic type with recursive type
parameter. this, along with the abstract self method, allows method
chaining to work properly in subclasses, without the need for casts.
现在我的问题是 power/value <T extends Builder<T>>
添加到 Pizza
class 的内容以及它与 <T extends Builder>
有何不同?如果你要用简单的英语向一个五岁的孩子解释<T extends Builder<T>>
,你会怎么解释?
而且我无法弄清楚 super class?
中抽象 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
class 这样的:
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
}
}
并像这样使用上面的 classes:
NyPizza.Builder<String> test = new NyPizza.Builder<String>(SMALL).builder();
现在因为我没有以这种形式定义class Builder<T extends Builder>
: class Builder<T extends Builder<T>>
Pizza
class 中的builder
方法应该不能检测 T 的类型是 NyPizza.Builder<String>
但它可以。这怎么可能?如何将其更改为需要转换?
想象一个 class:
class Foo<T extends Foo<T>> {
T foo() { ... }
}
这就是说,Foo<T>
class 中的 foo()
方法将 return 也是 Foo<T>
的实例,因为 T
表示的所有可能类型都是 Foo<T>
.
的子class
所以它 可以 return 本身 - 这就是为什么它对于与构建器的方法链接很有用;但实际上没有什么可以阻止它 return 在范围内 class 其他 class。
如果您将其声明为:
class Foo<T extends Foo> {
T foo() { ... }
}
然后这使得 T
成为 原始类型 Foo
:foo()
return 成为 Foo
,不是 Foo<T>
。因为原始类型擦除所有泛型,这意味着在像 foo().foo()
这样的调用链中,您将在第一次调用后丢失类型信息。
在许多情况下,这可能 感觉 不是特别重要。但是raw types should be avoided in pretty much all situations。这里原始类型的结果是:
- 如果你不是 return 自我类型(上面提到“实际上没有什么可以阻止它 return 其他 class”),你会在第一次方法调用后丢失“other class”。
- 如果您在
Foo
中有其他泛型方法,例如List<String> myList() { ... }
,原始类型 Foo
将 return 原始 List
,而不是 List<String>
(原始类型擦除 所有 泛型,而不仅仅是与省略的类型变量相关的那些)。
这些显然并不适用于所有情况;但由于引入不必要的原始类型只是一种不好的做法,因此请确保不要引入它们。
我正在阅读有效Java第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
}
}
上述抽象的实现 class:
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();
书中引述:
Note that
Pizza.Builder
is a generic type with recursive type parameter. this, along with the abstract self method, allows method chaining to work properly in subclasses, without the need for casts.
现在我的问题是 power/value <T extends Builder<T>>
添加到 Pizza
class 的内容以及它与 <T extends Builder>
有何不同?如果你要用简单的英语向一个五岁的孩子解释<T extends Builder<T>>
,你会怎么解释?
而且我无法弄清楚 super class?
中抽象 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
class 这样的:
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
}
}
并像这样使用上面的 classes:
NyPizza.Builder<String> test = new NyPizza.Builder<String>(SMALL).builder();
现在因为我没有以这种形式定义class Builder<T extends Builder>
: class Builder<T extends Builder<T>>
Pizza
class 中的builder
方法应该不能检测 T 的类型是 NyPizza.Builder<String>
但它可以。这怎么可能?如何将其更改为需要转换?
想象一个 class:
class Foo<T extends Foo<T>> {
T foo() { ... }
}
这就是说,Foo<T>
class 中的 foo()
方法将 return 也是 Foo<T>
的实例,因为 T
表示的所有可能类型都是 Foo<T>
.
所以它 可以 return 本身 - 这就是为什么它对于与构建器的方法链接很有用;但实际上没有什么可以阻止它 return 在范围内 class 其他 class。
如果您将其声明为:
class Foo<T extends Foo> {
T foo() { ... }
}
然后这使得 T
成为 原始类型 Foo
:foo()
return 成为 Foo
,不是 Foo<T>
。因为原始类型擦除所有泛型,这意味着在像 foo().foo()
这样的调用链中,您将在第一次调用后丢失类型信息。
在许多情况下,这可能 感觉 不是特别重要。但是raw types should be avoided in pretty much all situations。这里原始类型的结果是:
- 如果你不是 return 自我类型(上面提到“实际上没有什么可以阻止它 return 其他 class”),你会在第一次方法调用后丢失“other class”。
- 如果您在
Foo
中有其他泛型方法,例如List<String> myList() { ... }
,原始类型Foo
将 return 原始List
,而不是List<String>
(原始类型擦除 所有 泛型,而不仅仅是与省略的类型变量相关的那些)。
这些显然并不适用于所有情况;但由于引入不必要的原始类型只是一种不好的做法,因此请确保不要引入它们。