Substitution for generic type's field
I have generic structure and I need to search using various generic type's attributes.
Let's think of following implementation:
public class Person {
private int id;
private String name;
// + getters & setters
}
Now I have my custom data structure and one of it's method is like:
public T search(T data) { ... }
That is nonsense of course. What I really need in code is something like:
Person p = structure.search(12); // person's id
or
Person p = structure.search("Chuck N."); // person's name
So in pseudoJava (开发者_运维百科:)) the code would be something like this:
public T search(T.field key)
This isn't possible of course :( But how can one deal with this kind of situation? The point is: I don't want to force client's classes (like Person) to implement my own interface or to extend my own class. Is there any workaround?
Looks like you want some kind of intermediary strategy object, that extracts a value, compares a value, perhaps supplies a hash code or a comparison function.
Something like:
interface Matcher<T> {
boolean matches(T obj);
}
or methods such as:
boolean matches(T obj, V value);
V get(T obj);
int hash(T obj);
int compare(T a, T b);
Use is somewhat verbose with the current Java syntax (may change for JDK 8).
You'll end up with something like this:
Person p = structure.search(
new Matcher<Person>() { public boolean matches(Person person) {
return person.getID() == 12;
})
);
or:
Person p = structure.search(
new Matcher<Person,Integer>() {
public boolean matches(Person person, Integer id) {
return person.getID() == id;
}
),
12
);
In JDK8, perhaps something like:
Person p = structure.search(
{ Person person -> person.getID() == 12 }
);
or:
Person p = structure.search(
{ Person person, Integer id -> person.getID() == id },
12
);
or:
Person p = structure.search(
{ Person person -> person.getID() },
12
);
or:
Person p = structure.search(
Person#getID, 12
);
You could make the signature of search contain the type parameter by adding a Class parameter. As an example:
//replace 'id' with whatever your identifier types are
public <T> T search(int id, Class<T> entityClass) { ... }
Clients would then have to use the method like
Person p = foo.search(123, Person.class);
NotAPerson n = foo.search(234, NotAPerson.class);
It might look a little ugly to have to include the class, but when you really think about things - doesn't the client always know what it is searching for? And doesn't the code behind search()
need to know which type to be searching for - what if you have IDs that are shared by different types?
If your IDs are not of a consistent type, you could change the signature to
public <T> T search(Serializable id, Class<T> entityClass) { ... }
I'd suggest you to use Comparable interface.
Person p = structure.searchUnique(new FieldFilter("id", 12)); // searches for person with id 12 Person[] p = structure.searchAll(new FieldFilter("name", "John")); // searches for person named John
Moreover we can implement filter that allows to search over all available fields of the target class:
Person[] p = structure.searchAll(new FieldFilter("John")); // searches for all Johns
How to implement this? Here are some tips.
First what the "structure" is? It is a wrapper over collection that may probably contain some indexing functionality in future. This class should have constructor like
Structure(Collection data);
Its search method iterates over given collection and calls compateTo() method of FieldFilter that implements Comparable:
for (T elem : collection) { if (filter.compareTo(elem)) { result.add(elem); } } return result;
The FieldFilter should look like:
public class FieldFilter implements Comparable { private String fieldName; private V fieldValue; public FieldFilter(String fieldName, V fieldValue) { this.fieldName = fieldName; this.fieldValue = fieldValue; } public boolean compareTo(T elem) { Field field = elem.getClass().getField(fieldName); field.setAccessible(true); return field.getValue().equals(fieldValue); } }
Please note that the code was written directly in answer form, was never compiled, so it cannot be used as is. But I hope it describes the idea.
If you want to enforce a mapping through generics between the type being searched for, and the parameter-type passed to the search() method, you have to specify this somewhere. I don't think there's any way to do this without at least a generic marker interface for the client's classes.
Something like this would work for example:
public interface Searchable<T> { }
public class Person implements Searchable<String> {
// ...
}
public <T extends Searchable<K>, K> T search(K key) {
// ...
}
So now the compiler will allow:
Person p = search("John Doe");
but not:
Person p = search(5);
If even a marker interface is not an option, I'm afraid the best you can hope for is to use specific search methods based on the parameter type, like:
public <T> T searchByInt(int key) {
// ...
}
public <T> T searchByString(String key) {
// ...
}
but of course this will mean invalid cases like
Person p = searchByInt(5);
won't be caught at compile-time, but rather will need to be checked for at runtime instead.
精彩评论