Java: 接口和工厂中的泛型
Java: Generics in interface and factory
我有一个基础 class Plant
,其中有两个子类型路径。
Fruit extends Plant
和 Vegetable extends Plant.
Fruit
和 Vegetable
也是抽象的 classes,然后它们还有其他 classes,如 Banana extends Fruit
和 Spinach extends Vegetable
。
现在让我们举个例子,一个操作必须通过这些classes来操作,并且有一个操作接口。
目前设计如下:
我有一个界面:
abstract class Plant {}
abstract class Fruit extends Plant {}
class Banana extends Fruit {}
public interface PlantWeigher<T extends Plant> {
public int weight(T fruit);
}
那么我有:
public abstract class FruitWeigher<Y extends Fruit> implements PlantWeigher<Y> { }
然后我有具体的 class:
public class BananaWeigher extends FruitWeigher<Banana> {
public int weight(Banana fruit);
}
现在,作为刚刚了解Plant的用户,我想获取正确的Weigher实例,然后进行操作。
所以Factory如下:
public class WeigherFactory {
public static PlantWeigher<? extends Plant> getInstance(Plant plant){
// based on some logic on Plant argument, I return, lets say:
return new BananaWeigher();
}
}
所以在用户class中,我调用了下面的函数:
PlantWeigher<? extends Plant> instance = WeigherFactory.getInstance(userPlant);
instance.weight(plant); // THIS LINE GIVES AN ERROR
有人可以帮助我理解这里的设计有什么问题吗,也有人可以建议一个解决方案。
谢谢。
您的错误大概类似于 The method weight(capture#2-of ? extends Plant) in the type PlantWeigher<capture#2-of ? extends Plant> is not applicable for the arguments (Banana)
.
您不能将 Banana
与具有方法 int weight(T fruit)
的 PlantWeigher<? extends Plant>
一起使用。 PlantWeigher<? extends Plant>
实际上可能是一个 PlantWeigher<? extends Orange>
,大概不能称重 Banana
s。
最简单的解决方案是将工厂方法更改为:
public static <T extends Plant> PlantWeigher<T> getInstance(T plant) {
并像这样使用它:
PlantWeigher<Plant> instance = WeigherFactory.getInstance(myBanana);
instance.weight(myBanana); // No more error
这就是 PECS 原则。我建议您阅读 The Java Tutorials > Guidelines for Wildcard Use and Java Generics: What is PECS。
总的来说:
- Producers return data from a method and can use
? extends X
†
- Consumers 将数据接受到方法中(通过 方法参数)并且可以使用
? super X
‡
通常,您不能将上限通配符类型参数 (extends
) 与具有类型参数类型的参数的方法一起使用。在这种情况下,您可以发送到 instance.weight
的唯一有效值是 null
.
instance.weight(null); // No more error
使用带上(扩展)边界的通配符调用的类型参数是一个 "out" 变量。这是因为编译器充其量只能保证它是其绑定的某个子类型。在这种情况下,边界是 Plant
。因此,您可以从同一个 PlantWeigher<? extends Plant>
上的方法 T getTestPortion()
get Banana
,只要它最初设置为 PlantWeigher<Banana>
].
如果您要使用下界通配符,那么编译器可以保证类型参数是 Plant
的某种超类型,因此它可以权衡 Banana
或 Orange
.
不幸的是,PlantWeigher<Banana>
和 PlantWeigher<Orange>
不共享一些您可以使用的公共子类型 - 但这本身表明您的设计存在问题。
†对于方法的类型return变量
‡ 对于方法参数的类型
行:
PlantWeigher<? extends Plant> instance = WeigherFactory.getInstance(userPlant);
表示,"I have a weigher that can weight some subclass of Plant
, NOT all kinds of plants"。例如,如果工厂将 return a BananaWeigher
,尝试用它来加权 Tomato
是没有意义的。
要实现您想要的效果,您应该按如下方式更改工厂:
public class WeigherFactory{
public static <P extends Plant> PlantWeigher<P> getInstance(P plant){
// should return a correct Weigher for the subttype of Plant P
return ...
}
}
那么调用代码 return 就像:
PlantWeigher<Banana> instance =WeigherFactory.getInstance(banana);
instance.weigh(banana)
现在你有一个特定的 PlantWeigher
并且你可以用特定的类型调用它
或,如果你想权衡任何一种Plant
和任何一种PlantWeigher
,你应该改变后者:
public interface PlantWeigher {
public int weight(Plant p);
}
这确保 PlantWeigher
可以对任何计划进行加权,无论是否有意义。
问题在于您的代码的某些部分使用了泛型,而在其他部分则没有。您可以采取不安全的方式,并使用一种工厂方法 return 从一种方法获得不同类型的称重器。但是,您将失去通过使您的称重器通用而获得的类型安全。例如
Plant banana = ...;
Plant spinach = ...;
Weigher<Plant> bananaWeigher = WeigherFactory.getInstance(banana);
bananaWeigher.weigh(spinach); // Compiles, but will produce a runtime error!
另一种方法是使 WeigherFactory 对每个具体 Plant
class 具有不同的方法,并使使用 WeigherFactory
的代码也通用。例如
public class WeigherFactory {
public static BananaWeigher getInstance(Banana banana) {
return new BananaWeigher();
}
public static SpinachWeigher getInstance(Spinach spinach) {
return new SpinachWeigher();
}
}
public abstract class FactoryUser<P extends Plant> {
/**
* Method that knows how to call the appropriate WeigherFactory.getInstance method
* Defined by concrete implementations that know the full plant type.
*/
public abstract Weigher<P> getWeigher(P plant);
// common code for all FactoryUsers
public void run(P plant) {
Weigher<P> weigher = getWeigher(plant);
int weight = weigher.weigh(plant);
System.out.println("the plant weighs: " + weight);
}
}
public class Main {
public static void main(String[] args) {
Banana banana = new Banana();
FactoryUser<Banana> factoryUser = new FactoryUser<Banana>() {
// We know the generic type to be Banana now, so we can call the
// appropriate WeigherFactory.getInstance method.
// This technique is known as double dispatch
@Override
public BananaWeigher getWeigher(Banana banana) {
return WeigherFactory.getInstance(banana);
}
};
factoryUser.run(banana);
}
}
当您不知道 Plant 的实际类型(访问者模式)时
我们需要一个知道如何权衡所有类型 Plant
的 PlantWeigher
。如果需要,它可以委托专门的称重人员。但是我们需要一个中央称重器,因为您将无法 return 一个特定的称重器并以类型安全的方式使用它。中央称重器将使用 Visitor Pattern 发送正确的方法称量每种植物。
首先,您需要一个 Visitor
接口和一个 class 的实现,它知道如何处理每个混凝土厂 class。例如
public interface Visitor<T> {
public T visit(Banana banana);
public T visit(Spinach spinach);
}
public class WeighingVisitor implements Visitor<Integer> {
@Override
public Integer visit(Banana banana) {
return banana.getBananaWeight();
}
@Override
public Integer visit(Spinach spinach) {
return spinach.getSpinachWeight();
}
}
public class PlantWeigher {
public int weigh(Plant plant) {
return plant.accept(new WeighingVisitor());
}
}
其次,您需要修改 Plant
基础 class 以定义接受 Visitor
的抽象方法。只有 Plant
的具体实现需要实现此方法。
public interface Plant {
public <T> T accept(Visitor<T> visitor);
}
public class Banana implements Fruit {
private int weight;
public Banana(int weight) {
this.weight = weight;
}
public int getBananaWeight() {
return weight;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this); // the implementation is always exactly this
}
}
最后,如何使用这一切:
List<Plant> plants = asList(new Banana(10), new Spinach(20));
PlantWeigher weigher = new PlantWeigher();
int weight = 0;
for (Plant p : plants) {
weight += weigher.weigh(p);
}
System.out.println("total weight: " + weight); // expect 30
我有一个基础 class Plant
,其中有两个子类型路径。
Fruit extends Plant
和 Vegetable extends Plant.
Fruit
和 Vegetable
也是抽象的 classes,然后它们还有其他 classes,如 Banana extends Fruit
和 Spinach extends Vegetable
。
现在让我们举个例子,一个操作必须通过这些classes来操作,并且有一个操作接口。 目前设计如下:
我有一个界面:
abstract class Plant {}
abstract class Fruit extends Plant {}
class Banana extends Fruit {}
public interface PlantWeigher<T extends Plant> {
public int weight(T fruit);
}
那么我有:
public abstract class FruitWeigher<Y extends Fruit> implements PlantWeigher<Y> { }
然后我有具体的 class:
public class BananaWeigher extends FruitWeigher<Banana> {
public int weight(Banana fruit);
}
现在,作为刚刚了解Plant的用户,我想获取正确的Weigher实例,然后进行操作。
所以Factory如下:
public class WeigherFactory {
public static PlantWeigher<? extends Plant> getInstance(Plant plant){
// based on some logic on Plant argument, I return, lets say:
return new BananaWeigher();
}
}
所以在用户class中,我调用了下面的函数:
PlantWeigher<? extends Plant> instance = WeigherFactory.getInstance(userPlant);
instance.weight(plant); // THIS LINE GIVES AN ERROR
有人可以帮助我理解这里的设计有什么问题吗,也有人可以建议一个解决方案。
谢谢。
您的错误大概类似于 The method weight(capture#2-of ? extends Plant) in the type PlantWeigher<capture#2-of ? extends Plant> is not applicable for the arguments (Banana)
.
您不能将 Banana
与具有方法 int weight(T fruit)
的 PlantWeigher<? extends Plant>
一起使用。 PlantWeigher<? extends Plant>
实际上可能是一个 PlantWeigher<? extends Orange>
,大概不能称重 Banana
s。
最简单的解决方案是将工厂方法更改为:
public static <T extends Plant> PlantWeigher<T> getInstance(T plant) {
并像这样使用它:
PlantWeigher<Plant> instance = WeigherFactory.getInstance(myBanana);
instance.weight(myBanana); // No more error
这就是 PECS 原则。我建议您阅读 The Java Tutorials > Guidelines for Wildcard Use and Java Generics: What is PECS。
总的来说:
- Producers return data from a method and can use
? extends X
† - Consumers 将数据接受到方法中(通过 方法参数)并且可以使用
? super X
‡
通常,您不能将上限通配符类型参数 (extends
) 与具有类型参数类型的参数的方法一起使用。在这种情况下,您可以发送到 instance.weight
的唯一有效值是 null
.
instance.weight(null); // No more error
使用带上(扩展)边界的通配符调用的类型参数是一个 "out" 变量。这是因为编译器充其量只能保证它是其绑定的某个子类型。在这种情况下,边界是 Plant
。因此,您可以从同一个 PlantWeigher<? extends Plant>
上的方法 T getTestPortion()
get Banana
,只要它最初设置为 PlantWeigher<Banana>
].
如果您要使用下界通配符,那么编译器可以保证类型参数是 Plant
的某种超类型,因此它可以权衡 Banana
或 Orange
.
不幸的是,PlantWeigher<Banana>
和 PlantWeigher<Orange>
不共享一些您可以使用的公共子类型 - 但这本身表明您的设计存在问题。
†对于方法的类型return变量
‡ 对于方法参数的类型
行:
PlantWeigher<? extends Plant> instance = WeigherFactory.getInstance(userPlant);
表示,"I have a weigher that can weight some subclass of Plant
, NOT all kinds of plants"。例如,如果工厂将 return a BananaWeigher
,尝试用它来加权 Tomato
是没有意义的。
要实现您想要的效果,您应该按如下方式更改工厂:
public class WeigherFactory{
public static <P extends Plant> PlantWeigher<P> getInstance(P plant){
// should return a correct Weigher for the subttype of Plant P
return ...
}
}
那么调用代码 return 就像:
PlantWeigher<Banana> instance =WeigherFactory.getInstance(banana);
instance.weigh(banana)
现在你有一个特定的 PlantWeigher
并且你可以用特定的类型调用它
或,如果你想权衡任何一种Plant
和任何一种PlantWeigher
,你应该改变后者:
public interface PlantWeigher {
public int weight(Plant p);
}
这确保 PlantWeigher
可以对任何计划进行加权,无论是否有意义。
问题在于您的代码的某些部分使用了泛型,而在其他部分则没有。您可以采取不安全的方式,并使用一种工厂方法 return 从一种方法获得不同类型的称重器。但是,您将失去通过使您的称重器通用而获得的类型安全。例如
Plant banana = ...;
Plant spinach = ...;
Weigher<Plant> bananaWeigher = WeigherFactory.getInstance(banana);
bananaWeigher.weigh(spinach); // Compiles, but will produce a runtime error!
另一种方法是使 WeigherFactory 对每个具体 Plant
class 具有不同的方法,并使使用 WeigherFactory
的代码也通用。例如
public class WeigherFactory {
public static BananaWeigher getInstance(Banana banana) {
return new BananaWeigher();
}
public static SpinachWeigher getInstance(Spinach spinach) {
return new SpinachWeigher();
}
}
public abstract class FactoryUser<P extends Plant> {
/**
* Method that knows how to call the appropriate WeigherFactory.getInstance method
* Defined by concrete implementations that know the full plant type.
*/
public abstract Weigher<P> getWeigher(P plant);
// common code for all FactoryUsers
public void run(P plant) {
Weigher<P> weigher = getWeigher(plant);
int weight = weigher.weigh(plant);
System.out.println("the plant weighs: " + weight);
}
}
public class Main {
public static void main(String[] args) {
Banana banana = new Banana();
FactoryUser<Banana> factoryUser = new FactoryUser<Banana>() {
// We know the generic type to be Banana now, so we can call the
// appropriate WeigherFactory.getInstance method.
// This technique is known as double dispatch
@Override
public BananaWeigher getWeigher(Banana banana) {
return WeigherFactory.getInstance(banana);
}
};
factoryUser.run(banana);
}
}
当您不知道 Plant 的实际类型(访问者模式)时
我们需要一个知道如何权衡所有类型 Plant
的 PlantWeigher
。如果需要,它可以委托专门的称重人员。但是我们需要一个中央称重器,因为您将无法 return 一个特定的称重器并以类型安全的方式使用它。中央称重器将使用 Visitor Pattern 发送正确的方法称量每种植物。
首先,您需要一个 Visitor
接口和一个 class 的实现,它知道如何处理每个混凝土厂 class。例如
public interface Visitor<T> {
public T visit(Banana banana);
public T visit(Spinach spinach);
}
public class WeighingVisitor implements Visitor<Integer> {
@Override
public Integer visit(Banana banana) {
return banana.getBananaWeight();
}
@Override
public Integer visit(Spinach spinach) {
return spinach.getSpinachWeight();
}
}
public class PlantWeigher {
public int weigh(Plant plant) {
return plant.accept(new WeighingVisitor());
}
}
其次,您需要修改 Plant
基础 class 以定义接受 Visitor
的抽象方法。只有 Plant
的具体实现需要实现此方法。
public interface Plant {
public <T> T accept(Visitor<T> visitor);
}
public class Banana implements Fruit {
private int weight;
public Banana(int weight) {
this.weight = weight;
}
public int getBananaWeight() {
return weight;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this); // the implementation is always exactly this
}
}
最后,如何使用这一切:
List<Plant> plants = asList(new Banana(10), new Spinach(20));
PlantWeigher weigher = new PlantWeigher();
int weight = 0;
for (Plant p : plants) {
weight += weigher.weigh(p);
}
System.out.println("total weight: " + weight); // expect 30