如何为Stream目的创建抽象数据类型的具体版本?

时间:2016-02-12 03:22:24

标签: java generics java-stream reduction abstract-data-type

此代码显示我想要做的事情(但不起作用)。

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的实现以供日后使用。

2 个答案:

答案 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));