Michal Lefler
Michal Lefler

Reputation: 71

Combining Java 8 Lambda with annotations

Considering the following code:

public class MyTest {
public void sayHello(Supplier<String> myGetter) {
    System.out.println("Hello " + myGetter.get());
}

public static void main(String[] args) {
    Person person = new Person("Michal", "Lefler");
    MyTest myTest = new MyTest();

    List<Supplier<String>> suppliers = new ArrayList<>();
    suppliers.add(person::getName);
    suppliers.add(person::getLastName);

    suppliers.forEach(myTest::sayHello);
}

static class Person {
    private String name;
    private String lastName;

    public Person(String name, String lastName) {
       this.name = name;
        this.lastName = lastName;
    }

    @NameMethod
    public String getName() {
        return name;
    }

    @NameMethod
    public String getLastName() {
        return lastName;
    }

    public String getBlahBlah() {
        return "BlahBlah";
    }

    public String getPakaPaka() {
        return "PakaPaka";
    }
}

@interface NameMethod {}
}

My question is: Instead of filling the suppliers list explicitly:

    suppliers.add(person::getName);
    suppliers.add(person::getLastName);

I would like to fill the suppliers list automatically, using the "@NameMethod" annotation. I mean that I would like to scan all the "@NameMethod" annotated methods, using reflection, and then to somehow add these methods to the suppliers list. Is there a way I can do it? How? Thanks.

Upvotes: 4

Views: 2532

Answers (3)

Michal Lefler
Michal Lefler

Reputation: 71

Thanks! Both answers were very helpful. I would like to suggest my solution, based on yours, but avoid using reflection for the routine method invocation (we have performance restrictions). Instead, I'm using "MethodHandle" objects.

public class MyTest {
public void sayHello(Supplier<String> myGetter) {
    System.out.println("Hello " + myGetter.get());
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException {
    Person person = new Person("Michal", "Lefler");
    MyTest myTest = new MyTest();

    List<Supplier<String>> suppliers = new ArrayList<>();
    Method[] methods = Person.class.getMethods();
    for (Method method : methods) {
        if (!method.isAnnotationPresent(NameMethod.class))
            continue;
        MethodType desc = MethodType.methodType(String.class);
        MethodHandle methodHandle = MethodHandles.lookup().findVirtual(
                Person.class, method.getName(), desc);
        suppliers.add(new NameSupplier<>(methodHandle, person));
    }

    suppliers.forEach(myTest::sayHello);
}

static class Person {
    private String name;
    private String lastName;

    public Person(String name, String lastName) {
       this.name = name;
        this.lastName = lastName;
    }

    @NameMethod
    public String getName() {
        return name;
    }

    @NameMethod
    public String getLastName() {
        return lastName;
    }

    public String getBlahBlah() {
        return "BlahBlah";
    }

    public String getPakaPaka() {
        return "PakaPaka";
    }
}

@Retention(value= RetentionPolicy.RUNTIME)
@interface NameMethod {}

public static class NameSupplier<T> implements Supplier<String> {

    private final MethodHandle methodHandle;
    private final T object;

    public NameSupplier(MethodHandle methodHandle, T object) {
        this.methodHandle = methodHandle;
        this.object = object;
    }

    @Override
    public String get() {
        try {
            return (String) methodHandle.invoke(object);
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }
}
} 

Upvotes: 1

Sven Olderaan
Sven Olderaan

Reputation: 70

A Supplier is just an interface that you can implement.

public static class NameSupplier implements Supplier<String> {

    private final Method method;
    private final Object object;

    public NameSupplier(Method method, Object object) {
        this.method = method;
        this.object = object;
    }

    @Override
    public String get() {
        try {
            return (String) method.invoke(object);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

If you use reflection, you can get all methods that are annotated with @NameMethod

public static void main(String[] args) {
    Person person = new Person("Michal", "Lefler");
    MyTest myTest = new MyTest();

    List<Supplier<String>> suppliers = new ArrayList<>();
    Method[] methods = Person.class.getMethods();
    for (Method method : methods) {
        if (!method.isAnnotationPresent(NameMethod.class))
            continue;
        suppliers.add(new NameSupplier(method, person));
    }

    suppliers.forEach(myTest::sayHello);
}

Upvotes: 1

Peter Lawrey
Peter Lawrey

Reputation: 533530

You can use reflection as you suggest and check for the annotation and create a supplier for each method.

Person p = ...
for (Method m : Person.class.getMethods()) 
    if (m.getAnnotation(NameMethod.class) != null)
        suppliers.add(() -> {
            try {
                 return m.invoke(p);
            } catch (Exception e) {
                 throw new AssertionError(e);
            }
        });

Upvotes: 2

Related Questions