简体   繁体   中英

Is there a way to pass a method reference in Java?

First of all, this is not a duplicate of this question , even though they have the same title. That question was referring to passing methods that were essentially the same as C functions: they didn't need to belong to a particular object. In that case, you can pass a Runnable or Callable object.

Instead, I am asking the following: is it possible to pass a reference to a particular method in a class, and have the method called for a particular object?

As an example, I was looking at the code for the FlowLayout in Swing, and noticed that the preferredLayoutSize and minimumLayoutSize implementations were exactly the same, except for one line:

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;
  }
}

The preferredLayoutSize method calls the preferredLayoutSize method of m (the i -th component), whereas minimumLayoutSize calls the minimumLayoutSize method of m . As far as I can see, the two methods are otherwise identical.

As any programmer will tell you, code duplication is a bad thing. In this case, however, it's not obvious how to get rid of duplicated code. It's clear that there ought to be a private method with the code in it that both public methods call, passing in a reference to the preferredLayoutSize and minimumLayoutSize methods of the Component class. In CI could do that using function pointers, so it makes sense that there should be some way of doing it in Java. Passing in a Runnable or Callable almost works, but neither returns a value. Edit: This is wrong. The overridden method in a Callable does return a value, and the object it's acting on can be passed in as a parameter.

Now that I've typed all that out, a solution occurs to me: you could write an interface with a method called Dimension layoutSize(Component comp) and write two implementations, one of which returned comp.preferredLayoutSize() and the other of which returned comp.minimumLayoutSize() . You could then write a private method taking an instance of that interface as a parameter, and use that to run the separate methods at the right point in the code. You could even use anonymous inner classes so you didn't have to write a new class for each type of layout size. It still seems like quite a lot of trouble for a fairly simple problem, though. Is there an easier way?

You can wrap one of the methods in a class LayoutSizing (maybe a local class), with an abstract method for the calculation on m , with m as parameter and then in both methods instantiate new LayoutSizing() { @Override ... } with the method implemented.

In Java 8 this will then hopefully sill be a bit nicer looking.

You could use an interface:

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

I'm not good at naming things, so I'm sure you can come up with better names for some of these.

Edit: I think I was working on answering this before you edited your original post to mention the interface solution.

For this particular case, the simplest solution is to refactor this 2 methods in three:

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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM