此代码显示我想要做的事情(但不起作用)。
abstract class Item<I extends Item<I>> {
public abstract void add();
public abstract void test();
public static <T extends Item<T>> Item<T> getZero() {
return T.ZERO();
}
protected static <T extends Item<T>> Item<T> ZERO() {
class ZERO extends Item<T> {
public void add() {}
public void test() { System.out.println("item"); } // don't call this
}
return new ZERO();
}
public static void main(String[] args) {
Item.<Book>getZero().test();
}
}
class Book extends Item<Book> {
public void add() { /* addition implementation goes here */ }
public void test() { System.out.println("book"); } // this should be called
protected static Item<Book> ZERO() { return new Book(); }
}
我想确保在Item.<Book>getZero()
时,它会调用Book.ZERO()
而不是Item.ZERO()
。但是,由于类型擦除,这不起作用,程序打印item
。
我想修改它以使其工作(最好避免使用反射)。以下是此目的:
如果我拨打Arrays.stream(books).collect(Item.<Book>getZero(), Item::add, Item::add);
,我希望能够添加所有项目。
为了便于计算(并避免处理Optional<Book>
),我想定义一个具体的ZERO对象。但是,由于我打算Item
的所有实现都是可变的,我想确保Item
的每个子类(例如book)都有自己的可变实现。 换句话说,ZERO的默认实现是操作的占位符,不应使用。
我想这样做而不将额外的对象传递给调用方法(在Item
的每个实现中都应该添加/ zero方法)并且不需要Item
对象的实例来创建ZERO对象。这对我很有用,因为我想用Item
执行操作只知道可以添加和比较它们,这使我可以保存Item
的实现以供日后使用。
答案 0 :(得分:0)
假设您要收集的项目具有相同的类型(无法静态检查),您可以使实例方法(非静态方法)返回零并推迟创建容器,直到收集到第一个元素。像这样:
abstract class Item<I extends Item<I>> {
public abstract void add(I another);
public abstract void test();
// Instance method: returns new ZERO-container of the same type like this object
// Actually could be abstract
public I getZero() {
class ZERO extends Item<I> {
@Override
public void add(I another) {}
@Override
public void test() { System.out.println("item"); } // don't call this
}
return (I) new ZERO();
}
// Creates collector which aggregates any specific type of items
public static <T extends Item<T>> Collector<T, ?, T> collector() {
class Container {
T acc;
}
return Collector.of(Container::new, (cont, t) -> {
// accumulator is initialized only on first addition
// so we can use the first element to request the ZERO of the same type
if(cont.acc == null) cont.acc = t.getZero();
cont.acc.add(t);
}, (c1, c2) -> {
if(c1.acc == null) return c2;
if(c2.acc != null) c1.acc.add(c2.acc);
return c1;
}, cont -> cont.acc); // unpack in finisher (returns null for empty stream)
}
}
现在我们可以在子类中重新定义方法:
class Book extends Item<Book> {
public void add(Book another) { System.out.println("Book added"); }
public void test() { System.out.println("book"); }
public Book getZero() { return new Book(); }
}
class Food extends Item<Food> {
public void add(Food another) { System.out.println("Food added"); }
public void test() { System.out.println("food"); }
public Food getZero() { return new Food(); }
}
无论实际收集的对象类型如何,您都可以使用相同的收集器:
Book books = Stream.of(new Book(), new Book()).collect(Item.collector());
Food foods = Stream.of(new Food(), new Food()).collect(Item.collector());
请注意,对于空输入流,您将返回null
,因为您没有可以模仿ZERO
的对象。如果这是不可接受的,那么我可以建议的唯一解决方案是传递Class<? extends Item>
对象并使用反射创建相应的ZERO
(如clazz.getMethod("getZero").invoke(null)
假设getZero
是静态的)。
答案 1 :(得分:0)
当你说你想写的时候
Arrays.stream(books).collect(Item.<Book>getZero(), Item::add, Item::add);
您似乎不知道由于类型擦除,方法Item.getZero()
不知道您指定的类型参数<Book>
。由于参数化,方法的行为不会改变。事实上,当Item.<Book>getZero()
方法根本不是通用的时候,你甚至可以写Item.getZero()
。
但更重要的是,您希望拥有独立于实际类型的代码根本无法满足。在术语Item.<Book>getZero()
中,存在对类型Book
的引用,当流元素类型是Item
的不同子类型时,必须对其进行调整。那么为什么不首先指定Book::ZERO
(或只是Book::new
)?
abstract class Item<I extends Item<I>> {
public abstract void add(I other);
public abstract void test();
}
class Book extends Item<Book> {
public void add(Book b) { /* addition implementation goes here */ }
public void test() { System.out.println("book"); } // this should be called
public static Book ZERO() { return new Book(); }
}
可用作
Book b = Arrays.stream(books).collect(Book::ZERO, Item::add, Item::add);
因此,在使设计复杂化之前,您应该考虑一下您实际可以从变化中获得什么。
请注意,作为Supplier
的第一个参数所需的collect
正是一个返回您正在寻找的特定对象(如零实例)的方法的抽象。从collect
操作中提取供应商时,您可以抽象操作,例如,将以下方法添加到Item
类
static <T extends Item<T>> Collector<T,T,T> sum(Supplier<T> getZero) {
return Collector.of(getZero, Item::add, (a,b)->{ a.add(b); return a; });
}
你可以像
一样使用它Book b = Arrays.stream(books).collect(Item.sum(Book::ZERO));