【问题标题】:Treating Arrays of Instances Like Single Instances将实例数组视为单个实例
【发布时间】:2012-11-24 11:25:14
【问题描述】:

在 Java 中,我正在寻找一个通用模板,它意味着给定类型的数组(比如Foo),将允许对数组进行实例方法调用。在幕后,这将转化为遍历数组中的所有 Foo 实例,并在每个实例上调用实例方法。

也许一些代码会更好地证明这一点:

public class Foo{

   public Foo(){}

   public void Method1(){}

}

所以你有:

Foo foo = new Foo();
foo.Method1();

但是您能否为您的自定义类型制作一些通用模板,从而使这种事情成为可能:

Foo[] foos = new Foo[]{new Foo(),new Foo(), new Foo()};
foos.Method1();

这本质上是语法糖:

foreach(Foo f : foos){
  f.Method1();
}

我的动机是有人可以这样使用可变参数:

someHelper(fooInstance1,fooInstance2).Method1()

其中someHelper() 返回Foo[]

如果每个Method1() 调用都返回一个值,那么将其包装到一个返回值数组中会更好(其中ReturnVals.size == Foos.size)。

在最坏的情况下,我必须为我需要处理的每种类型编写一个单独的类来实现这一点,可能使用接口来描述适用于单个实例和实例数组的功能。

是否有任何 Java 魔法、设计模式或通用拼图游戏可以优雅地实现这一目标?

此外,如果没有,是否有任何语言在本质上促进了这一点?

我很欣赏它并不适用于所有场景,但我想这是由程序员自行决定的。

非常感谢

【问题讨论】:

  • 这些策略直接属于functional programming。例如,您可以轻松地像 array.each(function(item){ item.method() } 那样做 - 但不是在 Java afaik 中。
  • 在 JVM 上运行的 groovy 使用特殊运算符支持这一点。我想不出一种在 Java 中为泛型类而不使用反射的方法。我不确定是否有一种方法可以在运行时以与 groovy/grails 类似的方式注入方法。
  • Scala 也使用这种编程风格。
  • 我本质上是函数式编程的爱好者,所以我想命令式语言本质上会做同样的事情是一厢情愿的想法。刚刚过去的学年,我在 Scala 中为我的硕士项目编写了一个编译器。您指的是@PhiLho的地图功能吗?

标签: java design-patterns syntactic-sugar


【解决方案1】:

您正在拨打Composite Pattern。您可以在项目 PerfectJPattern 中找到组件化的通用和可重用实现,确保签出与 GoF 书中示例匹配的 Composite documentation pageexample

示例相关部分的逐字副本,假设您有 IGraphic 接口和一些实现,例如矩形和直线,那么你可以这样做:

// build the composite
IComposite<IGraphic> myComposite = new Composite<IGraphic>(IGraphic.class);
myComposite.add(new Rectangle());                
myComposite.add(new Line());
myComposite.add(new Line());

// use the composite, invokes the IGraphic#draw() in the 
// underlying Rectangle and two Line instances
myComposite.getComponent().draw();

这就是它适用于您的具体情况的方式:

Foo fooInstance1 = new Foo();
Foo fooInstance2 = new Foo();
IComposite<Foo> myComposite = new Composite<Foo>(Foo.class);
myComposite.add(fooInstance1);    
myComposite.add(fooInstance2);    
// invokes Method1 on instance1 and instance2 transparently
myComposite.getComponent().Method1();

// alternatively do
Foo myCompositeFoo = myComposite.getComponent();
// pass this myCompositeFoo around and do
myCompositeFoo.Method1();

请注意,IComposite 可重用实现包含实际的复合,它实现/提供Foo 接口,您必须通过getComponent 方法来获取它。这是一个小麻烦,它是必需的,因为在 Java 中没有其他方法可以创建实现任何任意和静态未知接口的事物的实例(在本例中为 Composite)。我能做的最好的就是给你一个Composite,它在下面为你构建一个真正的复合组件并返回你想要的接口类型Foo。这是使用动态代理实现的,但该实现是类型安全且完全组件化的,即您不必创建任何新的复合数组来实现您的接口。

【讨论】:

  • 我确实对 Composite 感到好奇,之前曾在一些情况下使用过它,例如。文件系统、网球记分牌(比赛、比赛、比赛)。在上面的示例中,类型 Rectangle 是否不等同于我需要在每种情况下定义以使其工作的实例数组类?
  • 不完全是。 Graphic 是具有多种特化的抽象级别:Line is a Graphic 和 Rectangle is a Graphic,然后您可以创建包含所有 Graphic 特化的组合。
  • 啊,所以你的意思是说我有一个方法可以获取 Foo 的可变参数列表,它可以返回 myComposite.getComponent() 的类型。因此,调用代码不需要知道它们与组合交互,但可以在返回的项目上调用方法,就好像它是单个实例一样。这看起来很有希望。复合组件上调用的方法的返回值呢?
  • 'return the type of myComposite.getComponent()' 我的意思是,将myComposite.getComponent()中的对象返回给调用者
  • 如果该方法不是 void,它如何处理来自被调用方法的返回值
【解决方案2】:

你可以通过反射来做到这一点。

创建一个泛型类,它在运行时查看 T 的所有公共方法,并向自身注入具有相同签名的方法和一个循环其所有对象并在其上调用相同方法的主体。

这个问题是如何调用动态创建的方法。由于这只能通过反射实现,这意味着原始代码中的任何方法调用都必须通过反射完成。

【讨论】:

  • 我对反射的另一个担忧是潜在的时间开销?我认为,如果我让某人将其用作 API,由于您所说的原因,它可能不是非常用户友好。
  • @HodeCode 我完全同意,我不想成为使用它的人。这纯粹是一个建议,我什至不确定是否可行。 :)
  • 这是一个受欢迎的建议。我一直在寻找解决这个问题的可能途径,而反射绝对是我认为可能有助于解决问题的一种方法。我只是喜欢谨慎使用它!
【解决方案3】:

有多种方法可以做到这一点,大多数使用包装对象等...... 但是,如果您想要的类型是接口,那么您很幸运,这实际上是可行的 使用dynamic proxies

例如

public static void main(String[] args) throws IOException {
    B[] objs = {new B(), new B(), new B()};
    A oneForAll = createMulticastObject(objs, A.class);
    oneForAll.print();
}

@SuppressWarnings("unchecked")
public static <T, U extends T> T createMulticastObject(final U[] targets, Class<T> interfaceClass) {
    return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object ret = null;
            for (U target : targets) {
                ret = method.invoke(target, args);
            }
            return ret;
        }
    });
}


interface A {
    void print();
}

static class B implements A {
    @Override
    public void print() {
        System.out.println("Invoked.");
    }
}

明显的限制是A(即您将在多播方法调用中使用的对象类型必须是一个接口。

另一个明显的限制是多播对象只能从所有这些调用中返回一个返回值。

代码在一个地方可能看起来很难看,但是createMulticastObject 方法是一种可以用作实用方法的方法,而且AB 无论如何都已经编写好了。 所以dynamic proxies 将通常的包装器等减少为:

A oneForAll = createMulticastObject(objs, A.class);

【讨论】:

  • 链接表明这是基于反射的?正如刚才在另一个答案中所指出的那样,我担心编写代码有多容易,以及它是否会很慢。您注意到的限制也可能是一个障碍
  • 嗯,就代码清洁而言,它相当容易编码。其他障碍:如果您需要所有方法调用返回,则不能有一个很好的通用方法。接口存在的要求对我来说不是问题,因为我经常使用接口。缓慢:通过代理调用方法比直接调用方法慢大约 40 倍。请注意,调用速度较慢,而不是方法本身,即空方法慢 40 倍(几百纳秒),如果方法确实有效,则相对差异要小得多。我使用反射在 RPC 中调度 - 不明显的减速
  • 如果您使用反射来调用在紧密循环中运行它们的方法,那么您会注意到速度很慢,否则不会。
  • 虽然我认为我不能在这种情况下使用它,但我很感谢展示我的动态代理。我不知道它们,它们可能被证明是未来问题的答案。感谢您的帮助
  • MyBatis ORM 库是动态代理使用的优秀示例(使用接口作为映射器,然后由动态代理实现对 DB 的调用)。
【解决方案4】:

查看apaches的CollectionUtils中的方法。您必须为要在列表中调用的每个方法实现一个接口。

例子:

CollectionUtils.collect(foos, new Transformer() {
    public Object transform(Object foo) {
        return ((Foo) foo).method();
    }
});

这里我使用了一个任意类,但你也可以在一个单独的类中实现 Transformer 并重用它。

我不知道是否存在带有泛型的版本。

还可以尝试一种函数式语言(scala、haskell、...)。他们使这类事情变得非常容易。

【讨论】:

  • 我认为考虑到某些类的大小,为每个方法定义一个接口可能会非常耗时,而且在这种情况下不实用?
  • @HodeCode 是的,但您至少可以将它用于您最常用的方法(当然,除非您找到更好的解决方案)
【解决方案5】:

是否有任何 Java 魔法、设计模式或通用拼图游戏可以优雅地实现这一目标?

并非如此 - 并非没有大量样板,这意味着您自己编写该死循环的代码更少。

此外,如果没有,是否有任何语言在本质上促进了这一点?

大多数函数式语言都支持类似的东西。在 Scala 中,您可以使用foos.foreach(_.Method1()),其中foreach 是一种在所有元素上调用代码块的方法,下划线是当前元素的别名。可运行代码:http://ideone.com/QRtPlK

在 Java 8 中,您将能够使用集合的流视图以及方法引用来执行以下操作:

foos.stream().forEach(Foo::method1);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-18
    相关资源
    最近更新 更多