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.

14
  • 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 Commented Aug 7, 2018 at 9: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? Commented Aug 7, 2018 at 9:29
  • Yes, I would like to use refactoring easily. Commented Aug 7, 2018 at 10:00
  • What are the @Getter and @Setter annotations from? Lombok? Commented Aug 7, 2018 at 10:05
  • 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? Commented Aug 7, 2018 at 10:57

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

In my case, I would like to call a given method on a object, that method name is not given initially, that will be provided during run time.
@GangadharEnagandula Put the method references in a map, or use an enum.
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.

1 Comment

@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 :(
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...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.