Java - Accessing the properties of an object without using the direct field name

11,942

Solution 1

You could write your class to allow querying in this way, by wrapping a Map (or a Properties if you prefer:

public class Employee {
    private Map<String,String> properties = new HashMap<>();
    public Employee(String emp_id, String location, String name) {
        properties.put("emp_id", empt_id);
        properties.put("location", location);
        properties.put("name", name);
    }

    public String getProperty(String key) {
        return properties.get(key);
    }
}

You can expose the fields as getters if you like:

    public String getName() {
         return this.getProperty("name");
    }

The opposite way around, of course, is to explicitly write a getProperty(String) to access fields:

public String getProperty(String key) {
     switch(key) {
         case "name":
             return this.name;
         case "empId":
             return this.empId;
         case "location":
             return this.location;
         default:
             throw new NoSuchFieldException; // or return null, or whatever
      }
 }

This may seem long-winded, but it's pretty easy and effective.


You can also use Reflection to work with the class at runtime. This is not recommended for new programmers - not because it is difficult as such, but because usually there's a cleaner way. And it subverts Java's access control features (e.g. it can read private fields).

Reflection includes techniques such as Class<?> c = e1.getClass(); Field f = c.getField("name"); -- there are no checks at compile time that e1 has a field called name. It will only fail at runtime.


If you're willing to use the Bean method naming conventions -- mostly simply that getName() is an accessor for a field called name -- then you could use Apache BeanUtils to work with the object. This is also Reflection, but it's wrapped in a more task-centric API.

String name = PropertyUtils.getProperty("name");

... this will:

  • call getName() and return the result, if getName() exists
  • throw NoSuchMethodException if there is no getName() method
  • other exceptions for other failures (see the JavaDoc)

So you could write:

public boolean isMatch(Employee employee, String[] search) {
     String key = search[0];
     String expectedValue = search[1];
     try {
         String actual = PropertyUtils.getProperty(key);
         return(Objects.equals(actual,expected)); // Objects.equals is null-safe
     } catch (NoSuchMethodException e) {
         return false;
     }
} 

Solution 2

What you are looking for is the Reflection API. Here's a simple example of how you might achieve what you need. Notice that we can query the class for its Fields and Methods. We can then do checks on Field types or Method return types. Reflection is not for the faint of heart but it can give you some extremely dynamic code.

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Employee {
    public String name;
    public int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public static void main(String[] args) throws Exception {
        Employee e1 = new Employee("Nick", 30);

        Class<?> c = e1.getClass();
        Field f = c.getField("name");

        System.out.print("Type: ");
        System.out.println(f.getType());
        System.out.print("Object: ");
        System.out.println(f.get(e1));
        System.out.println();

        System.out.println("Methods: ");
        Method[] methods = c.getMethods();
        for(int i = 0; i < methods.length; i++) {
            System.out.print("Name: ");
            System.out.println(methods[i].getName());
            System.out.print("Return type: ");
            System.out.println(methods[i].getReturnType());

            // imagine this value was set by user input
            String property = "name";
            if( methods[i].getName().toLowerCase().equals("get" + property) ) {
                System.out.print("Value of " + property + " is: ");
                System.out.println(methods[i].invoke(e1));
            }
        }
    }
}
Share:
11,942

Related videos on Youtube

sye
Author by

sye

Updated on June 04, 2022

Comments

  • sye
    sye almost 2 years

    So for the below question, I tried to search online but I couldn't find the answer to it. I am working in Java language.

    So I currently have a class, lets say:

    public Employee(String emp_id, String location, String name)
        {
            this.emp_id = emp_id;
            this.location = location;
            this.name = name;
        }
    

    I have created multiple objects of Employee, and I have saved it in an arrayList. Now, I the user will ask which employees are located in New York, or they can ask which employees are named John.

    So they can enter location New York. I need to read in the user's request, first identify what they are trying to search, and then see if there are any matching Employees in the array.

    I have read in the command, and saved it in an array of strings called Search. The first index holds the name of the field/property of the object, and the second index will hold what the user actually wants to check.

    String[] search = new String[] { "location", "New York" }
    

    I was thinking for doing this:

    for(Employee e: empList)
        if(e.search[0].equals(search[1]))
          System.out.println(e)
    

    However, I am not able to do this, since search[0] is not a property name for the Employee object. I am getting this error: error: cannot find symbol.

    Is there a way for me to access the object property without the actual name, meaning the name is saved in another String variable?

    Please let me know. Appreciate your help.

    Thank you.

    • Turing85
      Turing85 over 6 years
    • M. Prokhorov
      M. Prokhorov over 6 years
      There are reflection APIs in Java, which do something along those lines, but I would suggest rethinking your approach. Also, see this question.
    • Lino
      Lino over 6 years
      You might want to use switch, which then also would have a default action
    • sye
      sye over 6 years
      Yes, I actually did use equals in my actual code. I forgot to do the same here. But my issue here is accessing the properties. Can you please provide some suggestion or alternatives to my approach? I am kind of confused and stuck on where to proceed.
    • Tomato
      Tomato over 6 years
      I've edited to clarify the question, and also made search all lower-case. In Java it's good style to only use CaptializedWords for class names.
    • Tomato
      Tomato over 6 years
      There are hints here that your fields are public. Don't do this (I assume if you've not been taught it already, you will be soon). So if you insist on using reflection, use it to locate and invoke methods, not read fields.
  • sye
    sye over 6 years
    Can you please provide an example on how to use getField()? I was getting an error when i did e.getField(fieldSearch[0]).
  • sye
    sye over 6 years
    Yes, but this is assuming that I know the user wants to search for the Name property right? In my scenario, the user could be searching for the emp_id, location or the name. I want to search the list without having to check to see what field they are asking for.
  • Spidy
    Spidy over 6 years
    Neat. I haven't used the Java 8 Functional interfaces yet. One issue, how can the Employee::getName be replaced with something queried from the user as the OP is requesting? Can I just place a string in there with the property name I'm looking for?
  • Spidy
    Spidy over 6 years
    This would only work if the OP had a switch case or if...else if block that checked the user's input for the range of known methods or fields. For example: if( input.equals("id") ) return empList.get(i).getId() else if( input.equals("name") return empList.get(i).getName()
  • Daniel Diment
    Daniel Diment over 6 years
    There you have it, it searches every field in the class that is a string and then searches if the string has it anywhere.
  • tsolakp
    tsolakp over 6 years
    That should be easy. I would just hardcode the logic of mapping between field name and actual method. But OP can also use reflection in here only. See my update.
  • Spidy
    Spidy over 6 years
    Got it. So it isn't an alternative to Reflection. It is just the Functional way of coding
  • tsolakp
    tsolakp over 6 years
    It's alternative to reflection There is no use of Method or Field classes. And is refactor friendly.
  • Spidy
    Spidy over 6 years
    It is not an alternative because it doesn't support method or field discovery at runtime. It requires that all the method calls and field access already exist at compile time
  • tsolakp
    tsolakp over 6 years
    There was no mention about dynamic field discovery at runtime in original post. Use of reflection is always a code smell. It should only be used as last resort and abstracted away as much as possible.
  • lher
    lher over 6 years
    Accessing a "field/property of an object" "without using the direct field name" sounds like dynamic field discovery. Yes, there should be an method which provides access to a keyvalue store instead. The question hints a misconception, because the class is known and therefore all field names, but it still requires reflection.
  • tsolakp
    tsolakp over 6 years
    I dont think so. You access field via getter and map to user specified field name "saved in another String variable" described in create method. @lher. Again there is no reason to use reflection. If user specifies non existing Employee field name then it is error situation and dynamic dispatch wont help. If you are adding new field to Employee then you can add mapping logic for it in create method. Just like you do in case of equal or hasCode overridden methods.
  • tsolakp
    tsolakp over 6 years
    By extending Properties in your Employee class you are losing the type information because someone looking at that class will have no idea what fields Employee can and cannot have. Anyone can put arbitrary non related fields into the map by using setProperty method inherited from Properties. By following this logic we should make all Java Beans do the same?
  • tsolakp
    tsolakp over 6 years
    I dont see where your f variables is used at all?
  • tsolakp
    tsolakp over 6 years
    Accessing JavaBean property via its fields is bad practice. If Employee does not have get methods then it should have them. Reflection or non reflection code should always try to access them using get methods.
  • tsolakp
    tsolakp over 6 years
    The reason I asked is that the variable is not used for solution using reflection and just creating and printing it's value clutters the code.
  • Spidy
    Spidy over 6 years
    It's an example of how to use reflection, not a direct solution
  • Tomato
    Tomato over 6 years
    @tsolakp sorry, yes. extends Properties shouldn't still have been there. It was left over from another example that I decided not to use. Gone now.
  • M. Prokhorov
    M. Prokhorov over 6 years
    There strictly is no point in declaring bound types as <T extends Object>, because just saying <T> is equivalent (until and probably even after Java adds value types).