4

I have a simple Screen class in C# that has a bunch of events (with corresponding delegates) like the FadeOutEvent.

I want to port my library to Java, and I find that the mechanism for events/delegates is really cludgey. Specifically, I cannot easily write code like:

if (someVar == someVal) {
  this.FadeOutComplete += () => {
    this.ShowScreen(new SomeScreen());
  };
} else {
  this.FadeOutComplete += () => {
    this.ShowScreen(new SomeOtherScreen());
  };
}

For all you Java-only guys, essentially, what I'm whinging about is the inability to reassign the event-handling method in the current class to something else dynamically, without creating new classes; it seems that if I use interfaces, the current class must implement the interface, and I can't change the code called later.

In C#, it's common that you have code that:

  • In a constructor / early on, assign some event handler code to an event
  • Later during execution, remove that code completely
  • Often, change that original handler to different handler code

Strategy pattern can solve this (and does), albeit that I need extra classes and interfaces to do it; in C#, it's just a delcarative event/delegate and I'm done.

Is there a way to do this without inner/anonymous classes?

Edit: I just saw this SO question, which might help.

Community
  • 1
  • 1
ashes999
  • 9,925
  • 16
  • 73
  • 124
  • Just search SO for `[java] strategy pattern` and you'll find many many examples. – Hovercraft Full Of Eels Oct 01 '11 at 21:16
  • 1
    @HovercraftFullOfEels I did. Most of them require lots of additional classes (even anonymous classes), which I really really don't like. As a C# developer, this is trivial and doesn't require classes. Sure, I can do it with private/inner classes, but why must I do it that way? – ashes999 Oct 01 '11 at 21:20
  • 1
    @ashes999 Umn how do you implement your event without a lambda - which is basically the same as an anonymous inner class (actually pretty much identical from its constraints and all) in c#? The largest difference apart from Javas clumsy syntax is that you need an interface to define the delegate/event. And there's no way around this since functions aren't first class objects in Java. – Voo Oct 01 '11 at 21:45
  • 1
    @Voo lambdas are equivalent to an anonymous method, not an anonymous class. The extra interface, plus all the plumbing for adding/removing instances of your event observers, seems very clumsy. – ashes999 Oct 01 '11 at 22:37
  • @ashes999 Yeah and the difference between an anonymous class and a method from an implementation point of view is.. minimal to say the least. The C# implementation is basically just an already implemented "interface" (which you do for Java often enough, ActionListeners, etc.; you can define own events a bit shorter yes - the usual advantage of first class functions) and hides the bookkeeping. Which obviously isn't possible, if you haven't found a way to overload operators in Java. As I said a bit clumsy, but oh well - the boiler plate code is 2 lines of code, that's not too bad. – Voo Oct 01 '11 at 22:43
  • @Voo it's not just two lines. You have to remember the method name from the interface, eg. `new { public void blah ... }`. Plus you get a warning in Eclipse about emulation by a synthetic accessor method. Sigh. – ashes999 Oct 01 '11 at 22:45
  • Any modern IDE should automatically create the stumps you have to overwrite (if not, fix that bug first, that's pretty annoying for both languages). The synthetic accessor method only happens because you basically made a programming "error" that the compiler fixes with some additional method. No the boilerplate code we have to write ourselves is the add/removeListener code and the list declaration, which is nicely hidden by C#. – Voo Oct 01 '11 at 23:05
  • @Voo for anyone reading the comments, I also want to point out that C# allows inner anonymous classes to access non-final variables from the outer class. This doesn't quite work in Java. Sigh. – ashes999 Oct 06 '11 at 11:48
  • @ashes999 That's the usual effort/gain tradeoff. Allowing non-final locals (instance variables and co are fine obviously) extremely complicates the implementation and since it's mostly only a problem for primitives, it's not that bad - though certainly annoying (nice example of a leaky abstraction). With lambda expressions that should be fixed in Java8 though. – Voo Oct 06 '11 at 12:28
  • @Voo awesome, now all I have to do is wait one year to get my hands on it :) – ashes999 Oct 06 '11 at 14:51
  • @ashes999 Personally I couldn't care less about non-final locals, but lambdas? Now THAT'S a great addition :D – Voo Oct 06 '11 at 15:04

2 Answers2

13

Most of the time, it's done the other way round:

this.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        if (someVar == someVal) {
            showSomeScreen();
        }
        else {
            showSomeOtherScreen();
        }
    }
});

But you could do something similar to your C# code by delegating to two other objects:

private Runnable delegate;

// ...
this.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        delegate.run();
    }
});

// ...
if (someVar == someVal) {
    this.delegate = new Runnable() {
        @Override
        public void run() {
            showSomeScreen();
        }
    };
}
else {
    this.delegate = new Runnable() {
        @Override
        public void run() {
            showSomeOtherScreen();
        }
    };
}

Delegates were proposed by Microsoft for Java a long long time ago, and were refused by Sun. I don't remember if anonymous inner classes already existed at that time or if they were chosen as the alternative.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • +1 this is the conclusion I came to too. Maybe we can still do better somehow, but I fear this is as good as it will get. – ashes999 Oct 01 '11 at 22:39
  • Also, my example was a bit superficial; what I'm getting at is adding/removing/changing delegates often. That seems hard in Java. – ashes999 Oct 01 '11 at 22:40
1

With lambdas in JDK 1.8 / Java 8:

private Runnable delegate;
public void delegateTest()  {

// ...
   this.addActionListener(e -> delegate.run());
   
// ...
   if (someVar == someVal) {
      this.delegate = () -> showSomeScreen();
   }
   else {
      // or like this:
      this.delegate = this::showSomeOtherScreen;
   }
}

private void showSomeOtherScreen() {
}

private void showSomeScreen() {
}
Orwellophile
  • 13,235
  • 3
  • 69
  • 45