简体   繁体   中英

Get parameter name of class to pass it to other method in java

I would like to pass parameter name as a parameter to other method, fe:

I have class:

public class Foo() {
    public Bar bar;
    public Bar anotherBar;
    public Bar yetAnotherBar;

    public void doSomethingWithBar() {
        common.doingSomething(
            getMostImportantBarParameterName()
        );
    }
}

And in this class I would to have method:

public String getMostImportantBarParameterName() {
    return Foo.bar;
}

but instead of returning value of bar, I would like to get a name of parameter bar, so it should just return "bar" .

For now I have to do this that way:

public String getMostImportantBarParameterName() {
    return "bar";
}

Why I wanna achieve something like that? I am trying as much I can to avoid using strings in my code, cause in refactorization process I will bypass (skip) it accidentally.

But if I will have "hard coded" parameters that way, when I will later rename this parameter it will be automatically replaced in all instances by Eclipse IDE (Using LALT+LSHIFT+R)

Also my method: common.doingSomething() use parameter in runtime, So I won't get compilation error, which it makes hard to maintain this method.

I don't write unit test, cause I can't yet.

Please give me some help on this. Thanks

----------------- EDIT ------------------------

Real life usage.

I would like to have method to access database records in generic way. Common database operation in my application is:

Getting records from TableName where Parameter = SomeValue

So I would like to have generic method for that in generic entity listed below:

@MappedSuperclass
public abstract class GenericModel<T extends GenericModel> {
    @Transient protected Class<T> entityClass;
    private List<T> getByParameterAndValue(String parameter, String value) {
        List<T> entities = new ArrayList<T>();
        String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ parameter + " = :value";
        TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
        try {
            entities = query.getResultList();
        } catch (NoResultException e1) {
            entities = null;
        } catch (Exception e) {
            Index.toLog("error","Unsupported error in Generic model class in " + entityClass);
        }
        return entities;
    }

which is extended by real entities fe:

public class User extends GenericModel<User> {
    public String name;
    public String email;
    public String date;
    public String department;

    public List<User> getUsersByDepartments(String dep) {
        return getByParameterAndValue("department", dep);
    }
}

The problem is that in JPA TypedQuery :

TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.department = :department", User.class);
return query.setParameter("department", department).getSingleResult();

First of all, I think you should reconsider your approach. Using field names like this (either by reflection or hard coded Strings) is not very robust. In general, reflection should be avoided if possible.

What are you trying to achieve? What will common.doingSomething be doing with the field name?

It might be better to model the importance explicitly with an accessor:

class Foo {

    private Bar bar;
    private Bar anotherBar;
    private Bar yetAnotherBar;

    public Bar getMostImportantBar() {
        return bar;
    }
}

To answer your question about generics. You can either select the field by its index or by its name. Both are not robust, for when you change the field name, the String used to get it via reflection will not change with it, and if you change the order of the fields, the index will be wrong.

Here's how to do it:

Class foo = Foo.class;
Field[] fields = foo.getFields();

// get by index
Field firstField = fields[0];
String firstFieldName = firstField.getName();

// get by name
Field barField = foo.getField("bar");   
String barFieldName = barField.getName();

EDIT (after reading updated question):

In any Object Relational Mapping solution there is a boundary where the object-oriented realm ends and the relational realm begins. With your solution you are pulling that boundary a bit further into your code, in order to gain ease of use for your specific model classes and queries. The consequence of that is that you get more 'boiler plate' style code as part of your application (the GenericModel class) and that the boundary becomes more visible (the reference to a field by index or name using reflection). This type of code is generally harder to understand, test and maintain. On the other hand, once you get it right it doesn't change that often (if your assumption about the query type you usually need turns out to be valid).

So I think this is not a ridiculous use case for reflection, even though I myself would probably still stick to JPA and accept the similarity of the queries. With a good JPA framework, expressing these queries does not incur a lot of code.

About the hard-coded field names vs indexes, I advise you to go with the field names because they are easier to understand and debug for your successors. I would make sure the field name is expressed in the model class where the field resides, to make it as clear as possible that the two belong together, similar to the example you gave:

public class User extends GenericModel<User> {

    public static final String FIELD_NAME = "name";
    public static final String FIELD_EMAIL = "email";
    public static final String FIELD_DATE = "date";
    public static final String FIELD_DEPARTMENT = "department";

    private String name;
    private String email;
    private String date;
    private String department;

    // the byXXX naming scheme is a quite common shorthand for lookups
    public List<User> byDepartment(String department) {
        return getByParameterAndValue(FIELD_DEPARTMENT, department);
    }

BTW I think getByParameterAndValue cannot be private (must be at least default). Also I don't think you should initialize List<T> entities = new ArrayList<T>() at the start. You can do that in the catch(Exception e) to avoid unnecessary initialization if the query succeeds or returns no results. An your fields should be private (shown above).

Of course, this approach still results in one lookup method for each field. A different solution is to create a service for this and leave the model objects aenemic (without behavior):

public class DaoService {

    public <T extends GenericModel> List<T> get(Class<T> entityClass, String fieldName, String value) {
        List<entityClass> entities;
        String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ fieldName+ " = :value";
        TypedQuery<T> query = JPA.em().createQuery(sqlString,     entityClass).setParameter("value", value);
        try {
            entities = query.getResultList();
        } catch (NoResultException e) {
            entities = null;
        } catch (Exception e) {
            entities = new ArrayList<T>()            
        }
        return entities;
    }
}

Usage:

List<User> = daoService.get(User.class, User.FIELD_DEPARTMENT, value);

Here's another (slightly wild) idea I just had. Each model class is also a query template:

public abstract class ModelQuery<T extends ModelQuery> {

    // TODO set from constructor
    private Class<T> entityClass;
    private Field[] allFields = entityClass.getFields();

    private List<T> getByTemplate() {
        List<Field> queryFields = new ArrayList<Field>();
        String sql = selectFieldsAndCreateSql(queryFields);
        TypedQuery<T> query = setQueryParameters(queryFields, sql);
        return executeQuery(query);
    }

    private String selectFieldsAndCreateSql(List<Field> queryFields) throws IllegalAccessException {
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT e FROM ")
                .append(entityClass.getSimpleName())
                .append("e WHERE ");
        for (Field field : allFields) {
            if (field.get(this) != null) {
                sql.append("e.")
                        .append(field.getName())
                        .append(" = :")
                        .append(field.getName());
                // keep track of the fields used in the query
                queryFields.add(field);
            }
        }
        return sql.toString();
    }

    private TypedQuery<T> setQueryParameters(List<Field> queryFields, String sql) throws IllegalAccessException {
        TypedQuery<T> query = JPA.em().createQuery(sql, entityClass);
        for (Field field : queryFields) {
            query.setParameter(field.getName(), field.get(this));
        }
        return query;
    }

    private List<T> executeQuery(TypedQuery<T> query) {
        List<T> entities;
        try {
            entities = query.getResultList();
        } catch (NoResultException e1) {
            entities = null;
        } catch (Exception e) {
            entities = new ArrayList<T>();
        }
        return entities;
    }

}

Usage:

User userQuery = new User();
userQuery.setDepartment("finance");
List<User> results = userQuery.getByTemplate();

I guess there are more ways to skin this cat. Good luck with finding your optimal solution!

To get private field names

use foo.getDeclaredFields(); instead of foo.getFields();

Here are also you have some minor issue

fields[0] means, the first declared field, in which 0 is again hard coded

If you change the order of declaration then again it could be a trouble for you, which will never get refracted

I would recommend using

1.) The Class.forName() SPI logic where you can inject the expected business logic on the fly. 
2.) The Spring DI with interfaces and implementations using auto wiring 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM