3

I have to call a particular method through Java reflection. Instead of passing hardcoded method name, is it possible to pass the method name as a string?

For example

 public String getAttribute(Object object1, Object2, String className, String methodName){
     Class<?> clazz = Class.forName(className);
     Method method = clazz.getMethod(methodName);
     return ObjectUtils.firstNonNull(null == object1 ? null: method.invoke(object1),null == object2 ? null: method.invoke(object2); }

Let us say I have a class

 @Getter
 @Setter 
 Class Student{
   String studentName;
   String address;
   int rollNumber;
 }

Lets say, we have caller code

Student student1 = new Student();// Student record from School 1
Student student2 = new Student(); // Student record from School 2
student2.setAddress("ABC");
System.out.println(getAttribute(student1, student2, Student.class.name(), "getAddress"));

Instead of passing hardcoded method name as parameter to getAttribute() method, is there a way that I can use a method name that is not hardcoded?

For example, getAttribute(student, Student.class.name(), Student.class.getStudentName.getName()) so that we can easily make the changes to methods and variable of the student class when required without worrying on hardcoded method name constants.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • `Student.class.getStudentName.getName()` is not possible. I think this answer pretty much answers your question too: [https://stackoverflow.com/questions/3023354/how-to-get-string-name-of-a-method-in-java](https://stackoverflow.com/questions/3023354/how-to-get-string-name-of-a-method-in-java) – killexe Aug 07 '18 at 09:28
  • How would `Student.class.getStudentName.getName()` be better than `"getStudentName"`? I understand that you could then use refactoring tools more easily if you needed to change the name in the future, but is there another reason you want to avoid hard coding the name? – FThompson Aug 07 '18 at 09:29
  • Yes, I would like to use refactoring easily. – Gangadhar Enagandula Aug 07 '18 at 10:00
  • What are the `@Getter` and `@Setter` annotations from? Lombok? – FThompson Aug 07 '18 at 10:05
  • Yes, they are from Lombok – Gangadhar Enagandula Aug 07 '18 at 10:06
  • From [my 5 minute understanding of Lombok](http://vogella.com/tutorials/Lombok/article.html), you should be able to just do `student.getStudentName()`, yeah? I don't think you need reflection to begin with. – FThompson Aug 07 '18 at 10:13
  • @killexe: From the post that you gave, we can get all the methods declared in a class and can access through index, but how can we get a particular method name. – Gangadhar Enagandula Aug 07 '18 at 10:15
  • @Vulcan: My use case is I need to call each get method on 3 of the objects(let's say student) and choose one based on some criteria - first not null in order. there are multiple getters, out of them I need to call few of them. To reduce code repetition, I went for the reflection. Now, am able to call a given method on given class with getAttribute(), but It needs the hardcoded string method name that i am trying to avoid. I am editing the question to better describe the problem – Gangadhar Enagandula Aug 07 '18 at 10:23
  • Use the beandescriptor. – Grim Aug 07 '18 at 10:31
  • 1
    @GangadharEnagandula Could the problem be reduced to *For a given collection of objects, you want to find the first non-null result of a given getter*? – FThompson Aug 07 '18 at 10:57
  • @GangadharEnagandula you could either use Annotations (like the previous comments recommends) to find the methods you want to call or if you know that you will only want to call methods that start with a certain naming pattern you could also search for this pattern. How do you decide which methods you want/need to call? Also could you give a concrete example of your problem, there is probably a better solution which avoids using reflection at all. – killexe Aug 07 '18 at 11:12
  • @Vulcan: Yes, we can reduce the problem to - _For a given collection of objects, you want to find the first non-null result of a given getter_ – Gangadhar Enagandula Aug 07 '18 at 11:17
  • @GangadharEnagandula See my updated answer. – FThompson Aug 07 '18 at 11:24
  • @Vulcan: Thanks, this solved problem. – Gangadhar Enagandula Aug 07 '18 at 14:57

3 Answers3

5

To find the first non-null result of a given getter of the objects in a collection, you could utilize streams, method references, and optionals, while avoiding reflection entirely.

public static <T, R> Optional<R> findFirstNonNull(Collection<T> objects, 
                                                  Function<T, R> getter) {
    return objects.stream()
            .filter(Objects::nonNull)
            .map(getter)
            .filter(Objects::nonNull)
            .findFirst();
}

Example usage: Optional<String> found = findFirstNonNull(fooList, Foo::getName);

public class Foo {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static void main(String[] args) {
        Foo foo1 = null;
        Foo foo2 = new Foo();
        Foo foo3 = new Foo();
        foo3.setName("foo3");
        Foo foo4 = new Foo();
        foo4.setName("foo4");
        List<Foo> fooList = Arrays.asList(foo1, foo2, foo3, foo4);
        Optional<String> found = findFirstNonNull(fooList, Foo::getName);
        System.out.println(found); // Optional[foo3]
    }
}

Note: these are Java 8 features.

FThompson
  • 28,352
  • 13
  • 60
  • 93
0

you can access annotations at runtime. By marking the method that you want to use with reflection with an annotation it's possible to get all methods and then run the one with an annotation.

Here is a good example of that: java-seek-a-method-with-specific-annotation-and-its-annotation-element

Student student1 = new Student();// Student record from School 1
Student student2 = new Student(); // Student record from School 2
student2.setAddress("ABC");
try {
    System.out.println(getAttribute(student1));
} catch (Exception e) {
    System.out.println("Some error");
}

public static String getAttribute(Object object) throws Exception{
    Method method = getAnnotatedMethod(object.getClass());
    return (String) method.invoke(object);
}

public static Method getAnnotatedMethod(final Class<?> type) {
    final List<Method> allMethods = new ArrayList<Method>(Arrays.asList(type.getMethods()));
    for (final Method method : allMethods) {
        if (method.isAnnotationPresent(RunWithReflection.class)) {
            return method; 
        } 
    }
    return null;
}

Then you need the annotation:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class anno {

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RunWithReflection {
    }
}

And then you just annotate the function you want to run with @RunWithReflection and it works.

JohanLarsson
  • 195
  • 1
  • 7
  • @Vulcan and Thomas: Unfortunatly I can not comment your answers, but you are not getting what he asked for. The method name may change, thus he cannot use the method name at all. Thomas, you use getAddress() and others, Vulcan you use a method reference ::getName. Neither can be used. Wish I had more reputation :( – JohanLarsson Aug 07 '18 at 13:40
0

Another possibility is to create an enum with accessor to each attribute, for example:

public enum StudentAttribute {
    NAME,
    ADDRESS,
    ROLLNUMBER,
    ;

    public Object get(Student s) {
        switch(this) {
            case NAME: return s.getName();
            case ADDRESS: return s.getAddress();
            case ROLLNUMBER: return s.getRollNumber();
        }
    }
}

...

public Object getAttribute(StudentAttribute attr, Student... students) {
    if(students==null) return null;
    return Arrays.stream(students) //Java 8 Stream
        .map(attr::get) //convert into the corresponding attribute value
        .filter(o -> o!=null) //we're only interested in non-null values
        .findFirst() //specifically the first such non-null value
        .orElse(null) //otherwise null
        ;
}


//access:
Student student1 = new Student();// Student record from School 1
Student student2 = new Student(); // Student record from School 2
student2.setAddress("ABC");
System.out.println(getAttribute(StudentAttribute.ADDRESS, student1, student2));

If passing attribute (method to call) into the main method as a string, you can, for example, pass "ADDRESS" (exact naming as per enum constant) and execute:

public static void main(String[] args) {
    Student s1 = ...
    Student s2 = ...
    ...
    StudentAttribute attr = StudentAttribute.valueOf(args[0]); //this will be StudentAttribute.ADDRESS
    System.out.println(getAttribute(attr, student1, student2));

There aren't any hardcoded Strings here and no reflection, so this will survive any type of refactoring that you can think of. However, like other solutions this is unnecessarily verbose in that you are more or less duplicating your code for the sake of being able to refactor it later. I'd argue that the completely unnecessary repetition is more of a code smell than having to type "getAddress" more than once and already requires immediate refactoring into something simpler.

Perhaps using Streams, varargs, etc can give you better options for implementing your solution. Time-wise, would you have finished by now if you'd sat down to type everything out, as tedious as that might have been? Food for thought...

Thomas Timbul
  • 1,634
  • 6
  • 14