0

Example code:

public abstract class AbstractEvent
{
    // extended by AbstractEvent subclasses, used as a lambda function interface
    // (all sub-interfaces define one method)
    public interface EventListener
    {
    }
    // assigned by subclasses in a static block
    protected static Class<? extends EventListener> listenerClass;
    // this line obviously does not work
    protected static Set<listenerClass> listeners = new HashSet<>();

    public final boolean addListener(listenerClass listener)
    {
        return listeners.add(listener);
    }

    public final boolean removeListener(listenerClass listener)
    {
        return listeners.remove(listener);
    }
}

Is something like this even possible in Java, and if so, how? I've made it in Scala by defining a type without a body in the abstract class and defining it in subclasses (and it works great), but I'd like to make it in Java aswell.

The point of this is that I want to avoid duplicating the set code in all subclasses of AbstractEvent.

jurchiks
  • 1,354
  • 4
  • 25
  • 55
  • This may not work for some obvious reason, but why not just make the set of type Object, and just cast the object to the type you need it to be when you extract the element? – aeskreis Jun 23 '14 at 19:50
  • 3
    Also, it seems like you could accomplish this using generics. – aeskreis Jun 23 '14 at 19:51
  • @aeskreis - Object is definitely not a viable option. The idea is that whenever I type `SubOfAbstractEvent.addListener(` in my IDE, the auto-complete requires the right type of event as the parameter. – jurchiks Jun 23 '14 at 19:57
  • What's wrong with `protected static Set extends EventListener> listeners = new HashSet<>();`? – PM 77-1 Jun 23 '14 at 20:08
  • @PM77-1 - How do you specify that type to the add/removeListener methods? And how do you use that type when triggering the event in each subclass of AbstractEvent? You need to cast the listeners to the specific sub-interface of `EventListener`, but what do you cast to if you have no specific interface? – jurchiks Jun 23 '14 at 20:12
  • @jurchiks in that case it seems generics would be the way to go. The generic type used would be the concrete type of the EventListener implementation. – aeskreis Jun 23 '14 at 20:18

2 Answers2

2

You can accomplish this using generics:

public abstract class AbstractEvent<T extends EventListener>
{
    public interface EventListener
    {
    }

    protected static Set<T> listeners = new HashSet<T>();

    public final boolean addListener(T listener)
    {
        return listeners.add(listener);
    }

    public final boolean removeListener(T listener)
    {
        return listeners.remove(listener);
    }
}

Edit 1: To avoid having to create a separate file for each interface, you may use the following code:

public static void main(String[] args)
{
    EventListener anEventListener = new EventListener() {
       // Any methods that you override for EventListener go in here
    };

    SomeEvent evt = new SomeEvent();

    // You can even pass an anonymous one as a parameter.
    evt.addListener(new EventListener() {
    });
} 

You may also do the following:

public class SomeClass
{
    // This event listener is a private member of a class
    private EventListener mEventListener = new EventListener() {
           // Any methods that you override for EventListener go in here
    };
}
aeskreis
  • 1,923
  • 2
  • 17
  • 24
  • I've never written code like this so I didn't know you could pass the type like that. Looks like this is what I need, thanks! – jurchiks Jun 23 '14 at 20:30
  • Uhh, it seems that `public abstract class AbstractEvent` won't work if `EventListener` is inside the class... – jurchiks Jun 23 '14 at 20:31
  • @aeskreis - damn it, this requires all interfaces to be in separate classes. That's what I would like to avoid, after all, they're all literally 5 lines long at most. – jurchiks Jun 23 '14 at 20:33
  • @jurchiks Welcome to Java, enjoy your stay! – aeskreis Jun 23 '14 at 20:36
  • btw, you don't need all interfaces to be in a separate class file. I will post an edit that explains. – aeskreis Jun 23 '14 at 20:39
  • @aeskreis - yeah, I figured I can put all interfaces in one final class, but this is still not nice in any way. – jurchiks Jun 23 '14 at 20:41
  • See my edit, this is the cleanest solution for implementing multiple interfaces (using "anonymous" interfaces) – aeskreis Jun 23 '14 at 20:42
  • @aeskreis - not what I'm looking for because EventListener will not contain any methods, it exists only for grouping all event listeners under a common type. – jurchiks Jun 23 '14 at 20:49
  • If you want each EventListener implementor to be a concrete type, then you must create a new file for each one (unless you do them as internal subclasses, but that may not be the syntax you want). However, if EventListener doesn't implement any functionality, I would beg the question as to how it is any different from a regular Object subclass. The point of an interface is to add functionality to an object. If the interface has no functionality, then it really serves no purpose. – aeskreis Jun 23 '14 at 21:55
  • So what, you would write `AbstractEvent` instead of `AbstractEvent`? And anyone who extends `AbstractEvent` can specify absolutely anything for `T`? – jurchiks Jun 24 '14 at 13:40
  • You can actually just write AbstractEvent, since every class in Java implicitly extends Object. And yes, then they could use anything as T. – aeskreis Jun 24 '14 at 13:44
  • And you don't see a problem with that? – jurchiks Jun 24 '14 at 16:45
  • I don't see a problem with it. The interface doesn't implement any functionality. It isn't special in any way. I can have any class implement that interface and it is remains unchanged, so what difference does it make if a class is required to be an EventListener? It doesn't change the object at all, so I essentially CAN use any type with AbstractEvent. You effectively gain nothing from using an interface. You say you want to "group all classes under a common type", but that doesn't make any sense unless that type has certain shared properties or functionality. – aeskreis Jun 24 '14 at 17:54
  • At this point we're really talking about OOP philosophy. When you create a type, it should have a purpose. If the type has no members, no functions, and no purpose, then why does it exist? – aeskreis Jun 24 '14 at 17:57
  • It has a purpose, the purpose I give it. I want events to only be able to pass an EventListener interface, not anything that exists. I want it to be as strict as possible. In any case, it's done and I won't change it. – jurchiks Jun 24 '14 at 18:22
1

You can't override static variables (see Is there a way to override class variables in Java?), and assigning a value to a protected static variable from a static block of a subclass won't work as you want either:

class Base {
    protected static String value = "Base";

    public static String getValue() {
        return value;
    }
}

class Derived extends Base {
    static {
        value = "Derived";
    }
}

class Main {
    public static void main(String[] args) throws Exception {
        // Make sure we run static initializer for Derived class
        Class.forName("Derived");
        System.out.println(Derived.getValue());
        System.out.println(Base.getValue());
    }
}

This will print:

Derived
Derived

That is probably not what you want. For the same reason your current code will have listeners set shared across different subclasses.

If you are fine with having addListener and removeListener non-static, you can go with something like this:

// AbstractEvent.java
public abstract class AbstractEvent<T extends AbstractEvent.EventListener> {
    public interface EventListener { }

    protected static Map<Class<? extends AbstractEvent>, Set<EventListener>> listeners = new HashMap<>();

    public final boolean addListener(T listener) {
        Set<EventListener> eventListeners = listeners.getOrDefault(getClass(), new HashSet<>());
        if (!listeners.containsKey(getClass())) {
            listeners.put(getClass(), eventListeners);
        }
        return eventListeners.add(listener);
    }

    public final boolean removeListener(T listener) {
        Set<EventListener> eventListeners = listeners.get(getClass());
        if (eventListeners != null) {
            return eventListeners.remove(listener);
        }
        return false;
    }
}

// SomeEvent.java
public class SomeEvent extends AbstractEvent<SomeEvent.SomeEventListener> {
    public interface SomeEventListener extends AbstractEvent.EventListener {
        void someMethod();
    }
}

If you want to have addListener and removeListener static, then generics are not going to help you because you can't reference type parameters from static methods. In that case I am afraid you are going to have to explicitly declare them in each subclass.

Community
  • 1
  • 1
izstas
  • 5,004
  • 3
  • 42
  • 56
  • Right, looks like using singletons would be best. I don't want a map at the base, that would increase lookup times when doing any operations on the listeners of each specific event. – jurchiks Jun 23 '14 at 20:29
  • If you are going to use singletons, then you can have a non-static set declared in base class without those issues. – izstas Jun 23 '14 at 20:31
  • Obviously. It just really sucks that the interfaces have to be in separate files. I wanted the result be that for each event, there is only one file that does not change or affect anything else until used in other code. – jurchiks Jun 23 '14 at 20:38
  • You can have the interfaces in the same file. I edited my answer. – izstas Jun 23 '14 at 20:43
  • ok, that's much better! Now the only thing left is to figure out if the instance of each event (i.e. the getInstance() method and the private variable) can be stored in the base class. – jurchiks Jun 23 '14 at 21:49