1

I'm setting up an event system, and I want all my events to extend the Event class I've created. However, I also want to at any point be able to add in an additional setCanceled and isCanceled methods. Here's an example:

public class Event {}
public interface EventCancelable {
    public default void setCanceled(boolean canceled) {...}
    public default boolean isCanceled() {...}
}

public class PlayerEvent extends Event {
    public Player player;
    public PlayerEvent(Player player) {
        this.player = player;
    }
}

public class PlayerMovementEvent extends PlayerEvent implements EventCancelable {...}

As you can see, I used an interface to add in the methods later. The problem is how I have to store if an event is canceled:

public interface EventCancelable {
    Map<Object, Boolean> canceled = new HashMap<>();
    public void setCanceled(boolean canceled) {
        canceled.put(this, canceled);
    }
    public boolean isCanceled() {
        return canceled.get(this);
    }
}

Notice since Java only allows static fields, I have to create a map to store which events are canceled. This works fine, but after a while, this will take up more and more memory considering events are being called very frequently. Is there a way to add in cancelable features without using an interface, and without manually putting the code into every event I want to be able to cancel? I can't use an EventCancelable class, since then the PlayerMovementEvent wouldn't be able to extend PlayerEvent and EventCancelable at the same time, since I don't want all PlayerEvents to be cancelable.

Or is Java smart enough to empty the map of extra events no longer used since the map is only used in the interface with this added as the argument?

mega12345mega
  • 503
  • 1
  • 6
  • 17
  • 4
    You cannot declare a non-static or non-final field in a Java interface. And a `default` method can't directly access an instance field either. So the answer is No. Any functionality that needs to declare an (instance) field needs to be implemented in a class. – Stephen C Dec 31 '20 at 03:51
  • @StephenC Yah I got that part, I'm asking if there is a way to do this without using an interface. I assume that is a no? – mega12345mega Dec 31 '20 at 03:55
  • 2
    Well yes. Use a class; e.g. put that functionality into your `Event` class. – Stephen C Dec 31 '20 at 03:56
  • no, if your `Map` is alive, so are all it's inner entries, that have a strong reference to the `Map`. A `WeakHashMap` on the other hand... – Eugene Dec 31 '20 at 03:59
  • @StephenC I'm unable to use a class since I would like to have my ```PlayerMovementEvent``` for example be cancelable and extend ```PlayerEvent```. At the same time, I don't want all events that extend ```PlayerEvent``` be cancelable, so ```PlayerEvent``` can't extend ```EventCancelable```. For instance, I don't want a ```PlayerDisconnectEvent``` to be cancelable, but it makes sense for it also to extend ```PlayerEvent``` – mega12345mega Dec 31 '20 at 04:01
  • @Eugene That sounds like it would work (just looked it up) since the contents are discarded after they are no longer used. I'll mark it as a correct answer if you post it as one. Thanks :) – mega12345mega Dec 31 '20 at 04:04
  • So I'm assuming this doesn't need to support to be in a multithreaded environment, right? – Luiggi Mendoza Dec 31 '20 at 04:08
  • 1
    So create a `CancellablePlayerEvent` class that extends `PlayerEvent`. But either way, the **only** way to implement an instance field and a method that refers to it is to put the field declaration and the corresponding methods into a `class`. – Stephen C Dec 31 '20 at 04:08

2 Answers2

1

You could try to use a WeakHashMap, look for an extended example here.

But, you should know there are caveats:

  • you have zero control or knowledge when dead entries will be removed from the Map

  • this puts additional pressure on the GC, as it needs to do additional work for these types of references (WeakReference under the hood of WeakHashMap)

Eugene
  • 117,005
  • 15
  • 201
  • 306
0

Default methods are not meant to be used like that. They should provide implementations of methods which can have sensible defaults implemented using the other public methods of the interface.

Try composing classes:

interface Cancellable {
  void cancel();
  boolean isCancelled();
}
class CancellableImpl implements Cancellable {
  private boolean cancelled;
  ...
}

class PlayerMovementEvent extends PlayerEvent implements Cancellable {
  private CancellableImpl cancellable = new Cancellable();
  
  public cancel() { cancellable.cancel(); }
  public isCancelled() { return cancellable.isCancelled(); }
  ...
}

Only a few extra lines, but it makes things much easier to understand.

tgdavies
  • 10,307
  • 4
  • 35
  • 40