具有多个边界的编译时类型参数
Compile-time Type Parameters with Multiple Bounds
我可以使用具有多个边界的类型参数来保证在编译时容器的内容符合某些特征吗?
这可能最好用代码表达:
public class TestCase {
// our base type
public static abstract class Animal { }
// a container of animals, but we'd like to restrict to certain kinds
public static class Zoo<T extends Animal> {
private List<T> animals = new ArrayList<>();
public void addAnimal(T animal) {
this.animals.add(animal);
}
public List<T> getAnimals() {
return Collections.unmodifiableList(animals);
}
}
// Animal traits - we want to build a Zoo to only house animals with some traits
public static interface Bird {}
public static interface Mammal {}
public static interface Large {}
// An assortment of animals with different traits
public static class Sparrow extends Animal implements Bird { }
public static class Ostrich extends Animal implements Bird, Large { }
public static class Meerkat extends Animal implements Mammal { }
public static class Buffalo extends Animal implements Mammal, Large { }
// some different types of zoos we could build
public static class BirdZoo<T extends Animal & Bird> extends Zoo<T> {}
public static class LargeAnimalZoo<T extends Animal & Large> extends Zoo<T> {}
public static class LargeMammalZoo<T extends Animal & Large & Mammal> extends Zoo<T> {}
// BirdZoo should accept Ostrich & Sparrow, not Meerkat or Buffalo
public static void main(String[] args) {
BirdZoo rawBirdZoo = new BirdZoo();
rawBirdZoo.addAnimal(new Ostrich()); // warning - unchecked
rawBirdZoo.addAnimal(new Sparrow()); // warning - unchecked
rawBirdZoo.addAnimal(new Meerkat()); // warning - unchecked
rawBirdZoo.addAnimal(new Buffalo()); // warning - unchecked
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // error - incompatible types
wildBirdZoo.addAnimal(new Sparrow()); // error - incompatible types
wildBirdZoo.addAnimal(new Meerkat()); // error - incompatible types
wildBirdZoo.addAnimal(new Buffalo()); // error - incompatible types
BirdZoo<? extends Bird> boundedBirdZoo_B = new BirdZoo<>();
boundedBirdZoo_B.addAnimal(new Ostrich()); // error - incompatible types
boundedBirdZoo_B.addAnimal(new Sparrow()); // error - incompatible types
boundedBirdZoo_B.addAnimal(new Meerkat()); // error - incompatible types
boundedBirdZoo_B.addAnimal(new Buffalo()); // error - incompatible types
BirdZoo<? extends Animal> boundedBirdZoo_A = new BirdZoo();
boundedBirdZoo_A.addAnimal(new Ostrich()); // error - incompatible types
boundedBirdZoo_A.addAnimal(new Sparrow()); // error - incompatible types
boundedBirdZoo_A.addAnimal(new Meerkat()); // error - incompatible types
boundedBirdZoo_A.addAnimal(new Buffalo()); // error - incompatible types
BirdZoo<Ostrich> ostrichZoo = new BirdZoo<>();
ostrichZoo.addAnimal(new Ostrich());
ostrichZoo.addAnimal(new Sparrow()); // error - incompatible types
ostrichZoo.addAnimal(new Meerkat()); // error - incompatible types
ostrichZoo.addAnimal(new Buffalo()); // error - incompatible types
}
}
我想让BirdZoo 接受鸵鸟和麻雀,拒绝Meerkat 和Buffalo。还有其他方法可以构建这样的容器吗?
基本上,不,你不能。概念上的原因在于泛型类型是如何绑定的。当你创建一个像 Zoo<T extends Animal>
这样的 class 时,T
并不 意味着 "any type that extends Animal
",它意味着 "a specific type that extends Animal
that will be provided at runtime"。通常这可以让你做你想做的事,但你的案例似乎是在测试这个系统的界限(ba-dum-tiss)。
我认为更多的 nitty-gritty 答案必须进入通配符 (?
) 绑定系统 - 关于 ? extends A & B
的某些东西意味着它无法证明类型 C
扩展 A & B & D
实际上匹配。
实现您的目标的(更差的)设计如下所示:
public static abstract class Zoo{
private List<Animal> animals = new ArrayList<>();
protected void addAnimalHelper(Animal animal) {
this.animals.add(animal);
}
}
public static class BirdZoo extends Zoo {
public <T extends Animal & Bird> void addAnimal(T animal) {
addAnimalHelper(animal);
}
}
BirdZoo birdZoo = new BirdZoo();
birdZoo.addAnimal(new Ostrich()); // ok
birdZoo.addAnimal(new Sparrow()); // ok
birdZoo.addAnimal(new Meerkat()); // Meekrat doesn't conform to Bird
birdZoo.addAnimal(new Buffalo()); // Buffalo doesn't conform to Bird
通过在方法签名中编码的类型参数,类型系统可以在每次方法调用时自由选择一个新的 T
,这允许它在一次调用时绑定到 Ostrich,在下一次调用时绑定到 Sparrow。
显然,与您想要的设计相比,这有一些缺点:
- 底层存储是 "raw"(在本例中只是
Animal
),它取决于类型化的 subclass 来强制元素的类型化
- 同样,从存储中取出元素并保持已知类型是困难/混乱的。
- 需要在每个子程序中使用样板方法class + 访问帮助程序以在方法中编码类型。
另一个只能部分起作用的选项:
// Note - inheritance isn't used here since it breaks due to method overriding issues.
public static class BirdZoo<T extends Animal & Bird> {
private List<T> animals = new ArrayList<>();
public <X extends Animal & Bird> void addAnimal(X animal) {
this.animals.add((T)animal); // Unchecked cast warning
}
public List<T> getAnimals() {
return Collections.unmodifiableList(animals);
}
}
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // ok
wildBirdZoo.addAnimal(new Sparrow()); // ok
wildBirdZoo.addAnimal(new Meerkat()); // error - incompatible types
wildBirdZoo.addAnimal(new Buffalo()); // error - incompatible types
它抱怨将 X
转换为 T
的事实有点表明我们正在讨论这个问题。例如,当用 BirdZoo<?>
构造实例时,我们基本上没问题。另一方面,假设动物园是使用 BirdZoo<Ostrich>
构建的,并且调用了 ostrichBirdZoo.addAnimal(new Sparrow());
。然后我们有 T=Ostrich
和 X=Sparrow
。 T
和 X
都扩展了 Animal
和 Bird
,但是 T != X
、 但是 静态类型检查器不是聪明到可以分辨!
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // ok
wildBirdZoo.addAnimal(new Sparrow()); // ok
BirdZoo<Ostrich> ostrichBirdZoo = new BirdZoo<>();
ostrichBirdZoo.addAnimal(new Ostrich()); // ok
ostrichBirdZoo.addAnimal(new Sparrow()); // ok and doesn't throw at runtime --> Sparrow to Ostrich cast succeeds.
System.out.println(wildBirdZoo.getAnimals());
System.out.println(ostrichBirdZoo.getAnimals()); // Contains a sparrow...?
这似乎完全破坏了类型系统。
长话短说...这可能无法按您想要的方式工作。
我可以使用具有多个边界的类型参数来保证在编译时容器的内容符合某些特征吗?
这可能最好用代码表达:
public class TestCase {
// our base type
public static abstract class Animal { }
// a container of animals, but we'd like to restrict to certain kinds
public static class Zoo<T extends Animal> {
private List<T> animals = new ArrayList<>();
public void addAnimal(T animal) {
this.animals.add(animal);
}
public List<T> getAnimals() {
return Collections.unmodifiableList(animals);
}
}
// Animal traits - we want to build a Zoo to only house animals with some traits
public static interface Bird {}
public static interface Mammal {}
public static interface Large {}
// An assortment of animals with different traits
public static class Sparrow extends Animal implements Bird { }
public static class Ostrich extends Animal implements Bird, Large { }
public static class Meerkat extends Animal implements Mammal { }
public static class Buffalo extends Animal implements Mammal, Large { }
// some different types of zoos we could build
public static class BirdZoo<T extends Animal & Bird> extends Zoo<T> {}
public static class LargeAnimalZoo<T extends Animal & Large> extends Zoo<T> {}
public static class LargeMammalZoo<T extends Animal & Large & Mammal> extends Zoo<T> {}
// BirdZoo should accept Ostrich & Sparrow, not Meerkat or Buffalo
public static void main(String[] args) {
BirdZoo rawBirdZoo = new BirdZoo();
rawBirdZoo.addAnimal(new Ostrich()); // warning - unchecked
rawBirdZoo.addAnimal(new Sparrow()); // warning - unchecked
rawBirdZoo.addAnimal(new Meerkat()); // warning - unchecked
rawBirdZoo.addAnimal(new Buffalo()); // warning - unchecked
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // error - incompatible types
wildBirdZoo.addAnimal(new Sparrow()); // error - incompatible types
wildBirdZoo.addAnimal(new Meerkat()); // error - incompatible types
wildBirdZoo.addAnimal(new Buffalo()); // error - incompatible types
BirdZoo<? extends Bird> boundedBirdZoo_B = new BirdZoo<>();
boundedBirdZoo_B.addAnimal(new Ostrich()); // error - incompatible types
boundedBirdZoo_B.addAnimal(new Sparrow()); // error - incompatible types
boundedBirdZoo_B.addAnimal(new Meerkat()); // error - incompatible types
boundedBirdZoo_B.addAnimal(new Buffalo()); // error - incompatible types
BirdZoo<? extends Animal> boundedBirdZoo_A = new BirdZoo();
boundedBirdZoo_A.addAnimal(new Ostrich()); // error - incompatible types
boundedBirdZoo_A.addAnimal(new Sparrow()); // error - incompatible types
boundedBirdZoo_A.addAnimal(new Meerkat()); // error - incompatible types
boundedBirdZoo_A.addAnimal(new Buffalo()); // error - incompatible types
BirdZoo<Ostrich> ostrichZoo = new BirdZoo<>();
ostrichZoo.addAnimal(new Ostrich());
ostrichZoo.addAnimal(new Sparrow()); // error - incompatible types
ostrichZoo.addAnimal(new Meerkat()); // error - incompatible types
ostrichZoo.addAnimal(new Buffalo()); // error - incompatible types
}
}
我想让BirdZoo 接受鸵鸟和麻雀,拒绝Meerkat 和Buffalo。还有其他方法可以构建这样的容器吗?
基本上,不,你不能。概念上的原因在于泛型类型是如何绑定的。当你创建一个像 Zoo<T extends Animal>
这样的 class 时,T
并不 意味着 "any type that extends Animal
",它意味着 "a specific type that extends Animal
that will be provided at runtime"。通常这可以让你做你想做的事,但你的案例似乎是在测试这个系统的界限(ba-dum-tiss)。
我认为更多的 nitty-gritty 答案必须进入通配符 (?
) 绑定系统 - 关于 ? extends A & B
的某些东西意味着它无法证明类型 C
扩展 A & B & D
实际上匹配。
实现您的目标的(更差的)设计如下所示:
public static abstract class Zoo{
private List<Animal> animals = new ArrayList<>();
protected void addAnimalHelper(Animal animal) {
this.animals.add(animal);
}
}
public static class BirdZoo extends Zoo {
public <T extends Animal & Bird> void addAnimal(T animal) {
addAnimalHelper(animal);
}
}
BirdZoo birdZoo = new BirdZoo();
birdZoo.addAnimal(new Ostrich()); // ok
birdZoo.addAnimal(new Sparrow()); // ok
birdZoo.addAnimal(new Meerkat()); // Meekrat doesn't conform to Bird
birdZoo.addAnimal(new Buffalo()); // Buffalo doesn't conform to Bird
通过在方法签名中编码的类型参数,类型系统可以在每次方法调用时自由选择一个新的 T
,这允许它在一次调用时绑定到 Ostrich,在下一次调用时绑定到 Sparrow。
显然,与您想要的设计相比,这有一些缺点:
- 底层存储是 "raw"(在本例中只是
Animal
),它取决于类型化的 subclass 来强制元素的类型化 - 同样,从存储中取出元素并保持已知类型是困难/混乱的。
- 需要在每个子程序中使用样板方法class + 访问帮助程序以在方法中编码类型。
另一个只能部分起作用的选项:
// Note - inheritance isn't used here since it breaks due to method overriding issues.
public static class BirdZoo<T extends Animal & Bird> {
private List<T> animals = new ArrayList<>();
public <X extends Animal & Bird> void addAnimal(X animal) {
this.animals.add((T)animal); // Unchecked cast warning
}
public List<T> getAnimals() {
return Collections.unmodifiableList(animals);
}
}
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // ok
wildBirdZoo.addAnimal(new Sparrow()); // ok
wildBirdZoo.addAnimal(new Meerkat()); // error - incompatible types
wildBirdZoo.addAnimal(new Buffalo()); // error - incompatible types
它抱怨将 X
转换为 T
的事实有点表明我们正在讨论这个问题。例如,当用 BirdZoo<?>
构造实例时,我们基本上没问题。另一方面,假设动物园是使用 BirdZoo<Ostrich>
构建的,并且调用了 ostrichBirdZoo.addAnimal(new Sparrow());
。然后我们有 T=Ostrich
和 X=Sparrow
。 T
和 X
都扩展了 Animal
和 Bird
,但是 T != X
、 但是 静态类型检查器不是聪明到可以分辨!
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // ok
wildBirdZoo.addAnimal(new Sparrow()); // ok
BirdZoo<Ostrich> ostrichBirdZoo = new BirdZoo<>();
ostrichBirdZoo.addAnimal(new Ostrich()); // ok
ostrichBirdZoo.addAnimal(new Sparrow()); // ok and doesn't throw at runtime --> Sparrow to Ostrich cast succeeds.
System.out.println(wildBirdZoo.getAnimals());
System.out.println(ostrichBirdZoo.getAnimals()); // Contains a sparrow...?
这似乎完全破坏了类型系统。
长话短说...这可能无法按您想要的方式工作。