2

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.

slim
  • 40,215
  • 13
  • 94
  • 127
sye
  • 201
  • 3
  • 4
  • 10
  • 1
    [Don't use `==` to compare `String`s (or any object-type, really)](https://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java) – Turing85 Jan 16 '18 at 16:35
  • 1
    There are [reflection APIs](https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary.html) in Java, which do something along those lines, but I would suggest rethinking your approach. Also, see [this question](https://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java). – M. Prokhorov Jan 16 '18 at 16:36
  • You might want to use switch, which then also would have a default action – Lino Jan 16 '18 at 16:41
  • 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. – sye Jan 16 '18 at 16:42
  • 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. – slim Jan 16 '18 at 16:48
  • 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. – slim Jan 16 '18 at 17:24

7 Answers7

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));
            }
        }
    }
}
Spidy
  • 39,723
  • 15
  • 65
  • 83
2

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;
     }
} 
slim
  • 40,215
  • 13
  • 94
  • 127
  • 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 Jan 16 '18 at 23:02
  • @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. – slim Jan 17 '18 at 08:44
0

You can use the getFields() method if the Class, although I recommend you a simpler method if the class have so little properties, you can just use the or operation to achieve this:

for(Employee e: empList)
    if(e.getLocation().equals(Search[1])||e.getName().equals(Search[1]))
      System.out.println(e)

Using the getFields() method:

String searchValue="The string you have to search";
        for(Employee e: empList){
            List<Field> selectedFields=new ArrayList<Field>();
            for (int i = 0; i < e.getClass().getFields().length; i++) {
                if(e.getClass().getFields()[i].getType().getName().equals("String")){
                    selectedFields.add(e.getClass().getFields()[i]);
                }
            }

            for(Field f:selectedFields){
                if(f.get(e).equals(searchValue)){
                     System.out.println(e);
                }
            }

        }
    }
Daniel Diment
  • 110
  • 1
  • 8
  • Can you please provide an example on how to use getField()? I was getting an error when i did e.getField(fieldSearch[0]). – sye Jan 16 '18 at 16:52
  • There you have it, it searches every field in the class that is a string and then searches if the string has it anywhere. – Daniel Diment Jan 16 '18 at 17:13
  • 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 Jan 16 '18 at 23:09
0

This is just pseudocode. But here you are getting each Employee object at index i and getting the name returned in the form of a string from this employee at index i.

for (int i = 0; i < empList.size(); i++)
{
    if (empList.get(i).getId().equals(search[1]) || empList.get(i).getName().equals(search[1]) || empList.get(i).getLocation().equals(search[1]))
        return true;

}

So basically, iterate through your list of objects Employee. At each employee, getName() returns the string value of the name of this Employee.

That is how you access the properties, with your getter method.

public String getName()
    {
    return this.name;
    }
Noel Murphy
  • 1,264
  • 1
  • 8
  • 12
  • 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. – sye Jan 16 '18 at 16:53
  • 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() – Spidy Jan 16 '18 at 17:07
0

As mentioned in other answers: this can be solved with reflection. Another approach with some java8-sugar:

public static void main(String[] args) {
    List<Employee> unfilteredList = new ArrayList<>();
    // ... Add the employees

    String[] fieldAndExpectedValue = new String[] { "location", "Narnia-City" };

    List<Employee> filteredList = unfilteredList.stream().filter(
            employee -> objectHasFieldWithValue(employee, fieldAndExpectedValue[0], fieldAndExpectedValue[1]))
            .collect(Collectors.toList());
    // ...
}

private static <INSTANCE, FIELDVALUE> boolean objectHasFieldWithValue(INSTANCE o,
        String fieldName, FIELDVALUE expectedValue) {
    try {
        Field f = o.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        if (f.getType().isInstance(expectedValue) && expectedValue.equals(f.get(o))) {
            return true;
        }
    } catch (Exception exc) {
        exc.printStackTrace();
    }
    return false;
}
lher
  • 126
  • 7
  • 1
    There strictly is no point in declaring bound types as ``, because just saying `` is equivalent (until and probably even after Java adds value types). – M. Prokhorov Jan 17 '18 at 20:01
0

It's weird that all the answers focus on reflection. Design wise, you should be using getters to solve your problem. It is true that you need to use reflection to retrieve a property without any extra logic in that section of code, but your problem should rely on improving the searching logic rather than exposing fields and breaking SOLID OOP design.

From the looks of it you want a simple solution for searching through an array of objects and checking if the a property matches a certain value.

This would be an answer to that question:

///public class Employee {
public bool doesEmployeeMatch(String property, String value){

  switch(property){

    case "location": return value.equals(this.location);
    break;
    case "name": return value.equals(this.name);
    break;
    default: System.out.println("Invalid parameter");
    break;
  }

}

///where ever you're searching through empList
for(Employee e: empList)
    if(e.doesEmployeeMatch(search[0],search[1])){
      System.out.println(e);
      break;
    }

But that isn't how the question should be formed. Best way of forming the question would be "I need to determine the search parameter, then find the Employees that match the value of my parameter." That means you should have two steps to logically handle this actions. First figure out what field you want to find, then find all the employees that have the expected value on said field.

So what would that look like?

First you'll need some getter functions.

public class Employee {

private String emp_id, location, name;

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

  public String getEmp_id(){
    return this.emp_id;
  }

  public String getLocation(){
    return this.location;
  }

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

Next up you need to add logic for determining which getter to use.

///sorry I just threw a method name out for you
public bool findEmployeeMatch(String[] search){

  switch(search[0]){

    case "location": 
    break;
    case "name": 
    break;
    default: 
    break;
  }

Finally add some lambda expressions to impress the masses.

public bool findEmployeeMatch(String[] search, empList){
///other code maybe?
  switch(search[0]){

    case "location": Arrays.stream(empList).forEach(e)->
          if(e.getLocation().equals(search[1])){ 
             System.out.println(e);
          }
    break;
    case "name": Arrays.stream(empList).forEach(e)->
          if(e.getName().equals(search[1])){ 
            System.out.println(e);
          }
    break;
    case "emp_id": Arrays.stream(empList).forEach(e)->
          if(e.getEmp_List().equals(search[1])){ 
            System.out.println(e);
          }
    break;
    default: System.out.println("Invalid parameter");
    break;
  }

I can't see why there would be a reason to not check for what field they would want, as using reflection is costly and seeing how this is an app that expects users to search for data in a backend, whether for work or school, I do not believe reflection is the thing you want to be using.

Thatalent
  • 404
  • 2
  • 8
-1

I suggest not to use reflection API at all. It is messy and not type safe. Instead use Functional interface Java 8 provides or similar constructs if you are not using Java 8. Here is a cleaner type safe solution using Java 8:

public class Search<T> {

    private T searchValue = null;
    private Function<Employee, T> getter = null;

    public Search(T searchValue, Function<Employee, T> getter) {
        this.searchValue = searchValue;
        this.getter = getter;
    }

    public T getSearchValue() {
        return searchValue;
    }

    public void setSearchValue(T searchValue) {
        this.searchValue = searchValue;
    }

    public Function<Employee, T> getGetter() {
        return getter;
    }

    public void setGetter(Function<Employee, T> getter) {
        this.getter = getter;
    }
}


public Optional<Employee> find(List<Employee> empList, Search<?> search){
    for (Employee e : empList){
        if ( Objects.equals( search.getGetter().apply(e), search.getSearchValue() ) ) return Optional.of(e);
    }
    return Optional.empty();        
}

And you can search like this:

find( empList, new Search<>("Mark",  Employee::getName ) ) //finds Employee with name Mark

find( empList, new Search<>("Toronto",  Employee::getLocation ) ) //finds Employee with location in Toronto

Update:

Here is method that maps user specified field name to actual Search:

public static Search<String> create(String searchValue, String fieldName){
    if ( "name".equalsIgnoreCase(fieldName) ) return new Search<>(searchValue, Employee::getName );
    else if ( "location".equalsIgnoreCase(fieldName) ) return new Search<>(searchValue, Employee::getLocation );
    else throw new IllegalArgumentException("Unsupported fieldName: " + fieldName);
}

find(empList, Search.create("Toronto",  "location" ) )
tsolakp
  • 5,858
  • 1
  • 22
  • 28
  • 1
    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 Jan 16 '18 at 16:59
  • 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. – tsolakp Jan 16 '18 at 17:24
  • Got it. So it isn't an alternative to Reflection. It is just the Functional way of coding – Spidy Jan 16 '18 at 19:03
  • It's alternative to reflection There is no use of Method or Field classes. And is refactor friendly. – tsolakp Jan 16 '18 at 19:28
  • 1
    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 – Spidy Jan 16 '18 at 20:44
  • 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. – tsolakp Jan 16 '18 at 21:02
  • 1
    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. – lher Jan 16 '18 at 22:41
  • 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 Jan 16 '18 at 22:49