【问题标题】:Is there a way to pass a method reference in Java?有没有办法在 Java 中传递方法引用?
【发布时间】:2013-09-18 14:41:13
【问题描述】:

首先,这不是this question 的重复,尽管它们的标题相同。这个问题是指传递与 C 函数基本相同的方法:它们不需要属于特定对象。在这种情况下,您可以传递 RunnableCallable 对象。

相反,我要问的是:是否可以传递对类中特定方法的引用,并为特定对象调用该方法?

例如,我在 Swing 中查看 FlowLayout 的代码,并注意到 preferredLayoutSizeminimumLayoutSize 的实现完全相同,除了一行:

public Dimension preferredLayoutSize(Container target) {
  synchronized (target.getTreeLock()) {
    Dimension dim = new Dimension(0, 0);
    int nmembers = target.getComponentCount();
    boolean firstVisibleComponent = true;
    boolean useBaseline = getAlignOnBaseline();
    int maxAscent = 0;
    int maxDescent = 0;

    for (int i = 0 ; i < nmembers ; i++) {
        Component m = target.getComponent(i);
        if (m.isVisible()) {
            Dimension d = m.getPreferredSize();
            dim.height = Math.max(dim.height, d.height);
            if (firstVisibleComponent) {
                firstVisibleComponent = false;
            } else {
                dim.width += hgap;
            }
            dim.width += d.width;
            if (useBaseline) {
                int baseline = m.getBaseline(d.width, d.height);
                if (baseline >= 0) {
                    maxAscent = Math.max(maxAscent, baseline);
                    maxDescent = Math.max(maxDescent, d.height - baseline);
                }
            }
        }
    }
    if (useBaseline) {
        dim.height = Math.max(maxAscent + maxDescent, dim.height);
    }
    Insets insets = target.getInsets();
    dim.width += insets.left + insets.right + hgap*2;
    dim.height += insets.top + insets.bottom + vgap*2;
    return dim;
  }
}

public Dimension minimumLayoutSize(Container target) {
  synchronized (target.getTreeLock()) {
    boolean useBaseline = getAlignOnBaseline();
    Dimension dim = new Dimension(0, 0);
    int nmembers = target.getComponentCount();
    int maxAscent = 0;
    int maxDescent = 0;
    boolean firstVisibleComponent = true;

    for (int i = 0 ; i < nmembers ; i++) {
        Component m = target.getComponent(i);
        if (m.visible) {
            Dimension d = m.getMinimumSize();
            dim.height = Math.max(dim.height, d.height);
            if (firstVisibleComponent) {
                firstVisibleComponent = false;
            } else {
                dim.width += hgap;
            }
            dim.width += d.width;
            if (useBaseline) {
                int baseline = m.getBaseline(d.width, d.height);
                if (baseline >= 0) {
                    maxAscent = Math.max(maxAscent, baseline);
                    maxDescent = Math.max(maxDescent,
                                          dim.height - baseline);
                }
            }
        }
    }

    if (useBaseline) {
        dim.height = Math.max(maxAscent + maxDescent, dim.height);
    }

    Insets insets = target.getInsets();
    dim.width += insets.left + insets.right + hgap*2;
    dim.height += insets.top + insets.bottom + vgap*2;
    return dim;
  }
}

preferredLayoutSize 方法调用mi-th 组件)的preferredLayoutSize 方法,而minimumLayoutSize 调用mminimumLayoutSize 方法。据我所知,这两种方法在其他方面是相同的。

任何programmer 都会告诉你,重复代码是一件坏事。然而,在这种情况下,如何摆脱重复代码并不明显。很明显,应该有一个私有方法,其中包含两个公共方法调用的代码,传入对Component 类的preferredLayoutSizeminimumLayoutSize 方法的引用。在 C 中,我可以使用函数指针来做到这一点,因此在 Java 中应该有某种方式来做到这一点是有道理的。传入RunnableCallable 几乎可以工作,但都没有返回值。 编辑:这是错误的。 Callable 中的重写方法确实 返回一个值,并且它所作用的对象可以作为参数传入。

既然我已经输入了所有内容,我想到了一个解决方案:您可以使用名为 Dimension layoutSize(Component comp) 的方法编写一个 interface 并编写两个实现,其中一个返回 comp.preferredLayoutSize(),另一个返回返回comp.minimumLayoutSize()。然后,您可以编写一个将该接口的实例作为参数的私有方法,并使用它在代码中的正确位置运行单独的方法。您甚至可以使用匿名内部类,因此您不必为每种布局大小类型编写新类。不过,对于一个相当简单的问题来说,这似乎仍然很麻烦。有没有更简单的方法?

【问题讨论】:

  • 通过反射,查找Method 类。您还需要对 invoke() 它的对象的引用。
  • 我猜 Java 8 是可能的。
  • 或者,更好的是,使用MethodHandle
  • 不通过Callable 仍然适用吗? Callable 中的代码将调用对象上的相关方法。
  • 代码重复并不好,但如果人们仅仅因为认为代码重复是一件可怕的事情而竭尽全力,那就更糟了。然后你想出令人费解的不可读的“解决方案”,其中重复的代码被删除,但剩下的代码看起来像是一个疯子写的。

标签: java swing customization layout-manager


【解决方案1】:

您可以将其中一个方法包装在类LayoutSizing(可能是本地类)中,使用抽象方法在m 上进行计算,以m 作为参数,然后在这两个方法中实例化new LayoutSizing() { @Override ... }实现方法。

在 Java 8 中,希望它看起来会更好看。

【讨论】:

  • 有什么理由使用抽象类而不是接口?
  • @Donkey_2009 为代码,原方法内容。如果我理解的话,在 Java 8 中它确实可以是一个带有 default 方法的接口,因此有资格使用函数符号。
  • 请注意,@ajb 的答案是人们经常看到的;一个带有额外参数的私有方法commonCode,一个实现内部计算的接口。然后有一个额外的方法,所以我想提一下这个解决方案。
  • 是的,这就是我在我的问题中提出的建议。不过,我认为这稍微干净一些。
【解决方案2】:

你可以使用接口:

interface DimensionReturningThingy {
    public Dimension getDim (Component c);
}

public Dimension preferredLayoutSize(Container target) {
    commonCode (target, new DimensionReturningThingy () {
        public Dimension getDim (Component m) {
            return m.getPreferredSize ();
        });
}

// and similarly for minimumLayoutSize

public Dimension commonCode (Container target, DimensionReturningThingy drt) {

// now repeat code above, except that replace

        Dimension d = m.getPreferredSize();

// with

        Dimension d = drt.getDim (m);

我不擅长命名事物,所以我相信您可以为其中的一些想出更好的名称。

编辑:在您编辑原始帖子以提及界面解决方案之前,我想我正在努力回答这个问题。

【讨论】:

  • 好的,谢谢。您也可以使用Callable 接口代替DimensionReturningThingy,因为它已经存在了。
  • 由于Callable.call() 不接受任何参数,这将如何工作?如果commonCode 使用Callable 而不是DimensionReturningThingy,那么commonCode 如何将m 参数传递给被调用的方法?也许我遗漏了一些东西,但我不知道这是如何工作的。
  • 哦,你是对的。出于某种原因,我认为Callable 中的重写方法可以带参数。
  • 它似乎是Callable&lt;V&gt; 的变体,它带有一个Object... 变量参数列表可能只是有用,尽管你会失去参数中的类型安全......
【解决方案3】:

对于这种特殊情况,最简单的解决方案是将这两种方法重构为三个:

private Dimension layoutSize(Container target, boolean prederred) {
    ...
    Dimension d = prederred?m.getPreferredSize():m.minimumLayoutSize();
    ...
}

public Dimension preferredLayoutSize(Container target) {
    return layoutSize(target, true);
}

public Dimension minimumLayoutSize(Container target) {
    return layoutSize(target, false);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-21
    • 1970-01-01
    • 2019-08-26
    • 1970-01-01
    相关资源
    最近更新 更多