【问题标题】:How can I create a concrete version of an abstract data type for Stream purposes?如何为 Stream 目的创建抽象数据类型的具体版本?
【发布时间】:2016-02-12 03:22:24
【问题描述】:

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

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.&lt;Book&gt;getZero() 时,它调用Book.ZERO() 而不是Item.ZERO()。但是,由于类型擦除,这不起作用,程序打印item

我想修改它以使其工作(最好避免使用反射)。这是这样做的目的:

如果我打电话给Arrays.stream(books).collect(Item.&lt;Book&gt;getZero(), Item::add, Item::add);,我希望能够添加所有项目。

为了方便计算(并避免处理Optional&lt;Book&gt;),我想定义一个具体的零对象。但是,由于我希望 Item 的所有实现都是可变的,因此我想确保 Item 的每个子类(例如 book)都有自己的可变实现。 换句话说,ZERO 的默认实现是操作的占位符,不应使用。

我想在不向调用方法传递额外对象的情况下执行此操作(在 Item 的每个实现中都应该固有加法/零方法),并且不需要 Item 对象的实例来创建零对象。这对我很有用,因为我想用 Items 执行操作,只知道它们可以相加和比较,这样我就可以保存 Item 的实现以备后用。

【问题讨论】:

  • 为什么你的泛型是自引用的?
  • 为什么不能只使用Book.ZERO() 而不是Item.&lt;Book&gt;getZero()
  • 我从 Java 8 中定义 BaseStream 的方式中获得了这个想法。另外,我想确保我可以调用 Item book = new Book();因为 Item 对象是我打算使用的对象。
  • @Codebender 的方法几乎和它将会得到的一样好。你不能在 Java 中做你想做的事情,因为你不能有像可覆盖的静态方法这样的东西。
  • @Codebender 调用流的类具有类型参数>,因此我打算使用 Item. 定义。但是,如果这没有任何帮助,我可以切换。

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


【解决方案1】:

假设您要收集的项目具有相同的类型(这不能静态检查),您可以使实例方法(不是静态方法)返回零并推迟容器创建,直到收集到第一个元素。像这样的:

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&lt;? extends Item&gt; 对象并使用反射创建相应的ZERO(如clazz.getMethod("getZero").invoke(null) 假设getZero 是静态的)。

【讨论】:

  • Stream中什么都没有的情况如何处理(我还是想返回一个ZERO对象)
【解决方案2】:

当你说你想写作时

Arrays.stream(books).collect(Item.<Book>getZero(), Item::add, Item::add);

您似乎没有意识到由于类型擦除,方法Item.getZero() 不知道您指定的类型参数&lt;Book&gt;。方法的行为不会由于其参数化而改变。事实上,当Item.getZero() 方法根本不是泛型时,你甚至可以写Item.&lt;Book&gt;getZero()

但更重要的是,你想要拥有独立于实际类型的代码的愿望根本没有实现。在术语Item.&lt;Book&gt;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));

【讨论】:

  • 在这种情况下,Book 本身是调用 Stream.collect() 的类的类型参数。因此, Book::ZERO 可能不起作用。我正在考虑像其他答案一样做一个解决方法,但我知道它看起来有点骇人听闻,所以我对允许我将 Item 子类的创建推迟到开发结束的其他选项感兴趣。
  • 直接调用者不要求使用Book::ZERO;调用者可能会通过工厂Supplier。在任何一种情况下,都有一个知道实际类型的发起者,并且该发起者可以提供Supplier
  • 在这种情况下,Book 是另一个类的类型变量,正如之前有人告诉我的,我不能在类型变量上调用 lambda 方法引用。
  • 我可以找到一种方法来做到这一点(通过将这样的对象传递给方法),但它看起来很粗略,我想看看我是否可以隐藏这个对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-13
  • 2017-10-22
  • 2019-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多