0

I'm trying to set up a enum where each Enum value has a custom method to be called. However, it tells me that the method must be static. Is there a way to reference a non-static method?

My code looks like this

public class Foo {

    private enum MyEnum {
        TGD410(Foo::doAction);

        private MyLambda myLambda;

        MyEnum(MyLambda myLambda) {
            this.myLambda = myLambda;
        }

        public void execute(String str1, String str2) {
            myLambda.apply(str1, str2);

        }
    }

    public void doAction(String str1, String str2) {

    }

    @FunctionalInterface
    public interface MyLambda{
        void apply(String str1, String str2);
    }
}

enter image description here

Is there some other way to do what I want to do? I think I need to pass in a reference to the Foo object, but not sure how to specify that, since this refers to the Enum

Update

Updating to clarify that I'm using Springboot. Foo is a bean. The method in questions uses some other injected values, which is why it can't be static.

I'm considering just not using a Lambda and instead putting my method in another POJO (which implements some common interface), which can be instantiated

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
Peter Kronenberg
  • 878
  • 10
  • 32
  • @user16320675 This discussion seem to be fruitless, we both know the topic. I don't recall that anywhere in [**JLS**](https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.13) anywhere in the left part of the *method reference* has been referred as an *argument*. Firstly, it might create a confusion with *type arguments* which can be provided in square brackets `` in front of the right part. – Alexander Ivanchenko Aug 19 '22 at 23:43
  • @user16320675 Secondly, even we decide to call it argument it doesn't seem to intuitive to mix it with arguments of the Functional interface, since they should be provided at different stages, declaration of the *method reference* and *method reference* is being used. Does it make sense? – Alexander Ivanchenko Aug 19 '22 at 23:43

1 Answers1

2

You either have to provide an instance of Foo or make method doAction() to be static, otherwise you can't access it. Precisely as the error message tells you.

So you have two options:

  • Provide an instance (for instance via constructor of enum), which judging by the signature of doAction() seem to be unnecessary because there's nothing that points that this method depends somehow on the state of Foo instance (unless you're not going to use some properties of Foo in the body of the method which is omitted).
  • Add a static modifier to the declaration of doAction().

The later fix is trivial, here's a way to apply the first one:

private enum MyEnum {
    TGD410(new Foo()); // instance of Foo is provided while initializing enum-member
    
    private MyLambda myLambda;
    
    MyEnum(Foo foo) {
        this.myLambda = foo::doAction;
    }
    
    public void execute(String str1, String str2) {
        myLambda.apply(str1, str2);
    }
}

Update

Since clarification that Foo is actually a Bean in the Spring Context changes the question drastically, the answer requires an addition.

In not possible to inject Foo into the enum directly. There's a dirty and convoluted workaround with a static nested class annotated with @Component, having a method annotated with @PostConstruct responsible for initialization of the enum-constants (for more detail see this question).

From the perspective of clean coding, it's better to keep enums free from heavy logic and avoid making them dependent on the state of other objects.

I'm still suggesting making referenced behavior static, if it's not feasible, here's another option on how to avoid tight dependency between enum-members and Foo. We can achieve it by introducing an intermediate map Map<MyEnum,MyLambda> and extracting the method execute() outside of the enum:

@Component
public class Foo {
    
    private enum MyEnum {
        TGD410
    }
    
    private final Map<MyEnum, MyLambda> enumToLambda = Map.of(
        MyEnum.TGD410, this::doAction
    );
    
    public void execute(MyEnum myEnum, String str1, String str2) {
        enumToLambda.get(myEnum).apply(str1, str2);
    }
    
    public void doAction(String str1, String str2) {
    
    }
    
    @FunctionalInterface
    public interface MyLambda{
        void apply(String str1, String str2);
    }
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • The problem is that Foo is a singleton. I don't want to instantiate another instance. I'm doing this in Springboot, so it's actually a bean. – Peter Kronenberg Aug 19 '22 at 22:01
  • @PeterKronenberg Since enum constants are being initialized all together while enum is being loaded into memory, and its members under the hood are public static final fields (although these modifiers are not allowed with enum constants) there's a problem with injecting an instance of `Foo` from the context. It doesn't seem to be a viable option. So the only solution left is to make the behavior you're referencing `static`. – Alexander Ivanchenko Aug 19 '22 at 22:24
  • @Pshemo It seems like you're confusing the *Singleton design pattern* and *Singleton scope* of the Bean in the Spring Context. See https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch04s04.html – Alexander Ivanchenko Aug 19 '22 at 23:16
  • The problem is that as a bean, Foo has other Spring things that are being injected into it, which the method needs access to – Peter Kronenberg Aug 19 '22 at 23:24
  • I actually ended a creating a Map similar to what you have. At that point, I'm not sure if I really need the enum – Peter Kronenberg Aug 21 '22 at 13:43
  • 2
    @PeterKronenberg Whether to use this enum or not, that's up to you. Even lean enums are fine, they still have extensive language support: might be used with `switch` and in custom annotation, ability to access all members, or particular group of members via `values()` and `EnumSet` methods, etc. – Alexander Ivanchenko Aug 21 '22 at 19:07