【问题标题】:Java - Accessing the properties of an object without using the direct field nameJava - 在不使用直接字段名称的情况下访问对象的属性
【发布时间】:2018-06-25 10:19:38
【问题描述】:

所以对于下面的问题,我尝试在网上搜索,但找不到答案。我正在使用 Java 语言工作。

所以我现在有一个课,可以说:

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

我创建了多个 Employee 对象,并将其保存在 arrayList 中。现在,我的用户将询问哪些员工位于纽约,或者他们可以询问哪些员工名为 John。

这样他们就可以输入位置纽约。我需要阅读用户的请求,首先确定他们要搜索的内容,然后查看数组中是否有任何匹配的员工。

我已读入该命令,并将其保存在名为 Search 的字符串数组中。第一个索引保存对象的字段/属性的名称,第二个索引保存用户实际想要检查的内容。

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

我正在考虑这样做:

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

但是,我不能这样做,因为search[0] 不是Employee 对象的属性名称。我收到此错误:错误:找不到符号。

有没有办法让我在没有实际名称的情况下访问对象属性,这意味着名称保存在另一个 String 变量中?

请告诉我。感谢您的帮助。

谢谢。

【问题讨论】:

  • Java 中有 reflection APIs,它们可以按照这些思路进行操作,但我建议您重新考虑您的方法。另请参阅this question
  • 你可能想使用 switch,它也会有一个默认操作
  • 是的,我确实在我的实际代码中使用了 equals。我忘了在这里做同样的事情。但我在这里的问题是访问属性。您能否为我的方法提供一些建议或替代方案?我有点困惑,并坚持在哪里继续。
  • 我已编辑以澄清问题,并将search 全部小写。在 Java 中,只使用 CaptializedWords 作为类名是一种很好的风格。

标签: java arrays class oop search


【解决方案1】:

您正在寻找的是反射 API。这是一个简单的示例,说明如何实现所需的目标。请注意,我们可以查询该类的字段和方法。然后我们可以检查字段类型或方法返回类型。反射不适合胆小的人,但它可以为您提供一些非常动态的代码。

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));
            }
        }
    }
}

【讨论】:

  • 我根本看不到您的f 变量在哪里使用?
  • 我问的原因是该变量不用于使用反射的解决方案,而只是创建和打印它的值会使代码混乱。
  • 这是一个如何使用反射的例子,而不是直接的解决方案
【解决方案2】:

如果是Class,你可以使用getFields()方法,虽然我推荐你一个更简单的方法,如果类的属性太少,你可以使用or操作来实现:

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

使用 getFields() 方法:

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);
                }
            }

        }
    }

【讨论】:

  • 您能否提供一个如何使用 getField() 的示例?我在执行 e.getField(fieldSearch[0]) 时遇到错误。
  • 你有它,它搜索类中作为字符串的每个字段,然后搜索字符串是否在任何地方都有它。
  • 通过其字段访问 JavaBean 属性是不好的做法。如果 Employee 没有 get 方法,那么它应该有它们。反射或非反射代码应始终尝试使用get 方法访问它们。
【解决方案3】:

这只是伪代码。但是在这里,您在索引 i 处获取每个 Employee 对象,并从索引 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;

}

所以基本上,遍历您的 Employee 对象列表。在每个员工处,getName() 返回此员工姓名的字符串值。

这就是您使用 getter 方法访问属性的方式。

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

【讨论】:

  • 是的,但这是假设我知道用户想要搜索 Name 属性对吗?在我的场景中,用户可能正在搜索 emp_id、位置或名称。我想搜索列表,而不必查看他们要求的字段。
  • 这只有在 OP 有一个 switch case 或者 if...else if 块检查用户输入的已知方法或字段范围时才有效。例如: if( input.equals("id") ) return empList.get(i).getId() else if( input.equals("name") return empList.get(i).getName()
【解决方案4】:

我建议完全不要使用反射 API。它很混乱,而且不安全。 如果您不使用 Java 8,请改用 Java 8 提供的功能接口或类似结构。这是使用 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();        
}

你可以这样搜索:

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

更新:

这是将用户指定的字段名称映射到实际搜索的方法:

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" ) )

【讨论】:

  • 整洁。我还没有使用过 Java 8 功能接口。一个问题,当 OP 请求时,如何将 Employee::getName 替换为用户查询的内容?我可以在其中放置一个带有我要查找的属性名称的字符串吗?
  • 这应该很容易。我只会硬编码字段名称和实际方法之间的映射逻辑。但是 OP 也只能在这里使用反射。查看我的更新。
  • 知道了。所以它不是反射的替代品。这只是函数式编码方式
  • 这不是替代方案,因为它不支持运行时的方法或字段发现。它要求所有的方法调用和字段访问在编译时已经存在
  • 访问“对象的字段/属性”“不使用直接字段名称”听起来像动态字段发现。是的,应该有一种方法可以提供对键值存储的访问。这个问题暗示了一个误解,因为该类是已知的,因此所有字段名称都是已知的,但它仍然需要反思。
【解决方案5】:

您可以通过包装Map(或Properties,如果您愿意:

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);
    }
}

如果愿意,您可以将字段公开为 getter:

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

当然,相反的方法是显式编写getProperty(String) 来访问字段:

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
      }
 }

这可能看起来冗长,但它非常简单有效。


您还可以使用 Reflection 在运行时处理类。不建议新程序员这样做 - 不是因为它本身很难,而是因为通常有一种更清洁的方法。而且它颠覆了Java的访问控制特性(例如它可以读取私有字段)。

反射包括诸如Class&lt;?&gt; c = e1.getClass(); Field f = c.getField("name"); 之类的技术——在编译时没有检查e1 有一个名为name 的字段。它只会在运行时失败。


如果您愿意使用 Bean 方法命名约定——主要是 getName() 是一个名为 name 的字段的访问器——那么你可以使用 Apache BeanUtils 来处理对象。这也是反射,但它封装在一个更加以任务为中心的 API 中。

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

...这将:

  • 如果存在getName(),则调用getName()并返回结果
  • 如果没有getName()方法,则抛出NoSuchMethodException
  • 其他失败的其他异常(参见 JavaDoc)

所以你可以写:

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;
     }
} 

【讨论】:

  • 通过在 Employee 类中扩展 Properties ,您将丢失类型信息,因为查看该类的人将不知道 Employee 可以和不可以拥有哪些字段。任何人都可以使用继承自PropertiessetProperty 方法将任意不相关的字段放入映射中。按照这个逻辑,我们应该让所有的 Java Bean 都做同样的事情吗?
  • @tsolakp 对不起,是的。 extends Properties 不应该还在那里。它是我决定不使用的另一个示例遗留下来的。现在走了。
【解决方案6】:

正如其他答案中提到的:这可以通过反射来解决。使用一些 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;
}

【讨论】:

  • 严格来说,将绑定类型声明为&lt;T extends Object&gt; 是没有意义的,因为只是说&lt;T&gt; 是等价的(直到甚至可能在Java 添加值类型之后)。
【解决方案7】:

奇怪的是,所有的答案都集中在反思上。设计明智,你应该使用吸气剂来解决你的问题。确实,您需要使用反射来检索该部分代码中没有任何额外逻辑的属性,但您的问题应该依赖于改进搜索逻辑而不是暴露字段和破坏 SOLID OOP 设计。

从外观上看,您需要一个简单的解决方案来搜索对象数组并检查 a 属性是否与某个值匹配。

这将是那个问题的答案:

///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;
    }

但这不是问题的形成方式。形成问题的最佳方式是“我需要确定搜索参数,然后找到与我的参数值匹配的员工”。这意味着您应该有两个步骤来逻辑地处理此操作。首先弄清楚您要查找的字段,然后查找在该字段上具有预期值的所有员工。

那会是什么样子呢?

首先你需要一些 getter 函数。

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;
  }
}

接下来,您需要添加逻辑以确定使用哪个 getter。

///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;
  }

最后添加一些 lambda 表达式来打动大众。

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;
  }

我不明白为什么有理由不检查他们想要的字段,因为使用反射的成本很高,并且看到这是一个期望用户在后端搜索数据的应用程序,无论是工作或学校,我不相信反射是你想要使用的东西。

【讨论】:

    猜你喜欢
    • 2013-05-10
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 2011-06-29
    • 2012-01-20
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    相关资源
    最近更新 更多