1

The pattern is always the same:

  • Add new ActionListener to JCheckBox X. The Listener's body does this:
  • Obtain object Y reference. If reference !=null, do this:
  • Call method Z on Y with argument X.isSelected()

Example code:

jChecKBoxWindowSizeLocked.addActionListener(e -> {
    final WindowConfig lastWin = getLastTouchedWindowConfig();
    if (lastWin != null) {
        lastWin.setSizeLocked(jChecKBoxWindowSizeLocked.isSelected());
    }
});
jChecKBoxRememberSize.addActionListener(e -> {
    final WindowConfig lastWin = getLastTouchedWindowConfig();
    if (lastWin != null) {
        lastWin.setRememberSize(jChecKBoxRememberSize.isSelected());
    }
});
etc.

This looks awfully redundant and as if it should be solvable with a Lambda or method, but how would I give a reference to method Z without already nailing down object Y? I tried this method, but the call I'm using is probably not right:

private void addCheckboxListener(final JCheckBox checkBox, final Consumer<Boolean> setter) {
    checkBox.addActionListener(e -> {
        if (setter != null) {
            setter.accept(checkBox.isSelected());
        }
    });
}

The call is:

addCheckboxListener(cbRememberSize, getLastTouchedWindowConfig()::setRememberSize);

I am pretty sure that this gives the reference to whatever the getter returns at the time of calling the add method, not a general reference that is then resolved later. Consequently, this causes a NullPointerException in its own line (but compiles just fine):

addCheckboxListener(cbRememberLocation, ((WindowConfig) null)::setRememberLocation);

Of course I could just give the target class' setter Method as declared via reflection, but that would be complete overkill for the 10 or so JCheckBoxes this is about. Now, if it's just about 10, then I should just copy the code and not play scientist, right.

But my point/question is that there should be some kind of Lambda-ish method-reference-y way to get this done. Or does this possibility indeed not exist?





EDIT





Thanks to Holger's reply, I now successfully tried using static setters that have an additional instance parameter, so that the addCheckboxListener can just take a static method reference and therefore work just as intended. (EDIT2: Nope. Not static. Please see comments to Holger's (accepted) response. The whole rest of this "EDIT" section was written before this EDIT2.)

I'm not happy to make the setters static, but for now it seems to be the most elegant solution, and it's certainly a good knife to have in the tool shed.

A new problem/question arose from this. Before I possibly make a new post about this, I'll ask it here:

An unexplainable compile error occurs in this call:

addCheckboxListener(jCheckBoxRememberLocation, WindowConfig::setRememberLocation);

The method's head:

private void addCheckboxListener(JCheckBox checkBox, BiConsumer<WindowConfig, Boolean> setter) {

The error:

Error:(72, 49) java: incompatible types: invalid method reference
reference to setRememberLocation is ambiguous
both method setRememberLocation(WindowConfig,Boolean) in WindowConfig and method setRememberLocation(boolean) in WindowConfig match

The error only occurs when I have both the old non-static setter as well as the new static setter in the WinConfig class, plus they both need to have a different set of parameters. Is this possibly a Java bug? Here are the methods:

public void setRememberLocation(final boolean rememberLocation) {
    this.rememberLocation = rememberLocation;
}

public static void setRememberLocation(final WindowConfig instance, final Boolean rememberLocation) {
    instance.rememberLocation = rememberLocation;
}

The error seems wrong because only one of the two methods fits the profile.

What's REALLY odd is: If I change the non-static method's head to the very same as the static method's head, the error vanishes.

But what's REALLY super damn odd is: If I then swap the first and 2nd parameter of the non-static method, the error still does not occur. WTF!?

C:\>java -version
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)

C:\Program Files\Java\jdk1.8.0_74\bin>javac -version
javac 1.8.0_74

winver
Windows 7 Ultimate
Version 6.1 (Build 7601: Service Pack 1)

That Java version is used by the project. It's also the only one on my system. (No, also no 32 bit version somewhere else.)

Dreamspace President
  • 1,060
  • 13
  • 33
  • 2
    @2nd question: WindowConfig::setRememberLocation can be interpreted as either a static method that accepts a WindowConfig and a boolean, or an instance method in WindowConfig that accepts a boolean. I would recommend keeping the instance method. – srborlongan Apr 19 '16 at 12:34
  • 1
    @srborlongan Thanks, Holger just kind of told me the same, so I changed everything accordingly (Works like a charm.) and will now edit the post a little. – Dreamspace President Apr 19 '16 at 13:01

1 Answers1

2

Apparently you want a method like this:

private void addCheckboxListener(
    JCheckBox checkBox, BiConsumer<WindowConfig, Boolean> setter) {

    Objects.requireNonNull(setter);
    checkBox.addActionListener(e -> {
        final WindowConfig lastWin = getLastTouchedWindowConfig();
        if(lastWin != null) {
            setter.accept(lastWin, checkBox.isSelected());
        }
    });
}

which you can invoke as

addCheckboxListener(cbRememberSize,     WindowConfig::setRememberSize);
addCheckboxListener(cbRememberLocation, WindowConfig::setRememberLocation);

The WindowConfig instance on which the method ought to be invoked becomes part of the functional signature and will be fetched and passed to the function when the event occurs.

Note that I changed the behavior to immediately reject a null setter parameter instead of silently ignoring such an erroneous condition.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    +1 Thanks, this is one possible way to do it that I didn't think about. I tried it and it works. Don't worry, I'll eventually get around to marking this as the accepted answer, but I want to wait for other possible answers. Now, could you please read the long edit I made to the post? Because a new very odd problem occurred that is kind of related. – Dreamspace President Apr 19 '16 at 12:28
  • 2
    That’s exactly as the compiler tells you, a `static` method with the parameters `(WindowConfig,Boolean)` has the same functional signature as an instance method of the type `WindowConfig` accepting a single `Boolean`, as they both consume a `WindowConfig` instance and a `Boolean`. See also http://stackoverflow.com/q/21873829/2711488 To solve this, just remove the `static` methods, I don’t understand why you added them at all… – Holger Apr 19 '16 at 12:33
  • 2
    See also http://stackoverflow.com/q/23533345/2711488 and http://stackoverflow.com/q/22516331/2711488 – Holger Apr 19 '16 at 12:38
  • I just assumed that if you call setter.accept() with two arguments, the method *needs to have* two arguments, so I logically added the static version. I had no idea the Lambda/method-reference implementation in the background was this flexible. I guess I now kind of understand what caused the error and why its behavior is as described. Since your answer seems like the golden solution to the problem, I'll mark it as accepted. Thanks, dude! – Dreamspace President Apr 19 '16 at 12:59
  • I have a related question. I want to store references to non-static methods in variables and later call them, giving the obviously required instance. But I don't see how I could do this. Purpose: I'm creating a CPU/assembler/disassembler, for which I create an instructions-array in which I have instances of the instruction-class with a few describing fields. Now I want to add a field to it referring to an instance-method of the CPU class. There are probably other/better ways to do this, but the question would remain: How can an instance-method reference be stored like this? – Dreamspace President Jul 26 '16 at 07:18
  • 1
    Well, you can’t create an array like `BiConsumer[]` because Generics and arrays don’t work well together, but you can create an `ArrayList>`, which, given a sufficient initial size, doesn’t perform worse than an array. Or use something like `List> list=Arrays.asList( WindowConfig::setRememberSize, WindowConfig::setRememberLocation )`, which even has a form similar to an array initializer. – Holger Aug 16 '16 at 09:06