tags:

views:

48

answers:

4

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?

+1  A: 

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) { ... }
matt b
I don't really know, what will "id" be. It might be int, string, date, whatever :> Only thing I know, its T's attribute. Does this help? :/
Xorty
Well, what are you searching by - an "identifier" of T, or any arbitrary attribute of T? It actually doesn't matter for this example, the first parameter can be interpreted by your `search()` method to be anything - the real part of my answer is adding `Class<T>` as a parameter.
matt b
Would you mind editing yout answer for example as I wrote? I am not quite getting your point. I have homogeneous structure. What I know, that there are only Ts in this structure. In our example - I have structure full of Persons. Nothing else gets there. What I don't know, is what "key" should I specify in my search method. In other words, what would you use instead of your "int id" ? And how would you access T's field?
Xorty
If you want to be able to "search" for any arbitrary field of T, then it sounds like you really want `search(Object, Class<T>)`. There is no way to enforce what types of fields T may have, so you can't declare this as a type parameter of the `search()` method. Do you have an idea of how to implement `search()`, or are you asking how to implement it? Perhaps I've misunderstood the real question here
matt b
I don't know how to declare nor define such method, that will work as mentioned. It shouldn't touch client classes and it can assume, that structure is homogenous. It also can assume, that search will be based only on generic type's T field. It sounds somewhat impossible, I know, so I am looking for workaround. So that I can use "search" method as in example (id,string,date,whatever)
Xorty
+2  A: 

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
);
Tom Hawtin - tackline
But Person class is forced to implement Matcher, correct?
Xorty
No. I'll add some more.
Tom Hawtin - tackline
Please do so :)
Xorty
I tried, it worked. Brilliant I think. If nobody comes with nothing better, I'll use this as solution.
Xorty
A: 

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.

AlexR
I was also thinking of using reflection, but I think this isn't very optimal. Since programmers - as API clients - aren't very well aware of java.lang.reflect and it also brings potentional problems (we all know which).
Xorty
A: 

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.

Luke Hutteman
yayx, this is fine and readable, but I can't reimplement original classes now :) I appreciate your advice, but matcher pattern (Tom Hawtin wrote) suits better for me now.
Xorty
ok; I thought you were looking for a method signature like search(K), with K somehow mapped to the Person class. But if passing in an anonymous interface implementation with some matching logic is ok as well, then yes I would agree that Tom Hawtin's solution is probably the best one for you.
Luke Hutteman