2010-10-26 7 views
0

J'ai une structure générique et je dois effectuer une recherche en utilisant divers attributs de type générique.Substitution pour le champ de type générique

songeons à la mise en œuvre suivante:

public class Person { 
    private int id; 
    private String name; 
    // + getters & setters 
} 

Maintenant, j'ai ma structure de données personnalisées et l'un de ses méthode est comme:

public T search(T data) { ... } 

C'est un non-sens bien sûr. Ce que j'ai vraiment besoin dans le code est quelque chose comme:

Person p = structure.search(12); // person's id 

ou

Person p = structure.search("Chuck N."); // person's name 

Donc pseudoJava (:)) le code serait quelque chose comme ceci:

public T search(T.field key) 

Ce n » Bien sûr, mais comment faire face à ce genre de situation? Le fait est que je ne veux pas forcer les classes du client (comme Person) à implémenter ma propre interface ou à étendre ma propre classe. ?

Répondre

2

On dirait que vous voulez une sorte d'objet de stratégie intermédiaire, qui extrait une valeur, compare une valeur, fournit peut-être un code de hachage ou une fonction de comparaison.

Quelque chose comme:

interface Matcher<T> { 
    boolean matches(T obj); 
} 

ou des méthodes telles que:

boolean matches(T obj, V value); 

    V get(T obj); 

    int hash(T obj); 

    int compare(T a, T b); 

utilisation est un peu bavarde avec la syntaxe actuelle de Java (peut changer pour JDK 8).

Vous finirez avec quelque chose comme ceci:

Person p = structure.search(
    new Matcher<Person>() { public boolean matches(Person person) { 
     return person.getID() == 12; 
    }) 
); 

ou:

Person p = structure.search(
    new Matcher<Person,Integer>() { 
     public boolean matches(Person person, Integer id) { 
      return person.getID() == id; 
     } 
    ), 
    12 
); 

En JDK8, peut-être quelque chose comme:

Person p = structure.search(
    { Person person -> person.getID() == 12 } 
); 

ou:

Person p = structure.search(
    { Person person, Integer id -> person.getID() == id }, 
    12 
); 

ou:

Person p = structure.search(
    { Person person -> person.getID() }, 
    12 
); 

ou:

Person p = structure.search(
    Person#getID, 12 
); 
+0

Mais la classe Person est forcée d'implémenter Matcher, correct? – Xorty

+0

Non. J'en ajouterai d'autres. –

+0

S'il vous plaît le faire :) – Xorty

2

Vous pouvez faire en sorte que la signature de la recherche contienne le paramètre type en ajoutant un paramètre Class. A titre d'exemple:

//replace 'id' with whatever your identifier types are 
public <T> T search(int id, Class<T> entityClass) { ... } 

Les clients doivent alors utiliser la méthode comme

Person p = foo.search(123, Person.class); 
NotAPerson n = foo.search(234, NotAPerson.class); 

Il peut sembler un peu laid devoir inclure la classe, mais quand vous pensez vraiment des choses - n » t le client sait-il toujours ce qu'il recherche? Et le code derrière search() n'a pas besoin de savoir quel type de recherche - et si vous avez des ID partagés par différents types?

Si vos identifiants ne sont pas d'un type cohérent, vous pouvez changer la signature à

public <T> T search(Serializable id, Class<T> entityClass) { ... } 
+0

Je ne sais pas vraiment ce que sera "id". Ça peut être int, string, date, peu importe:> Seule chose que je connaisse, son attribut T's. est-ce que cela aide? :/ – Xorty

+0

Eh bien, que cherchez-vous par - un "identifiant" de T, ou tout attribut arbitraire de T? Cela n'a pas d'importance pour cet exemple, le premier paramètre peut être interprété par votre méthode 'search()' comme étant quelque chose - la partie réelle de ma réponse est l'ajout de 'Class ' en tant que paramètre. –

+0

Pourriez-vous éditer votre réponse par exemple comme j'ai écrit? Je ne comprends pas très bien votre argument. J'ai une structure homogène. Ce que je sais, c'est qu'il n'y a que des Ts dans cette structure. Dans notre exemple, j'ai une structure pleine de personnes. Rien d'autre n'y arrive. Ce que je ne sais pas, c'est quelle "clé" dois-je spécifier dans ma méthode de recherche. En d'autres termes, qu'utiliseriez-vous à la place de votre "int id"? Et comment auriez-vous accès au champ de T? – Xorty

0

Je vous suggère d'utiliser l'interface Comparable.

Personne p = structure.searchUnique (nouveau FieldFilter ("id", 12)); // recherche une personne avec l'ID 12 Personne [] p = structure.searchAll (nouveau FieldFilter ("nom", "John")); // recherches pour personne nommée John

De plus, nous pouvons mettre en œuvre filtre qui permet de rechercher sur tous les champs disponibles de la classe cible:

personne [] p = structure.searchAll (nouveau FieldFilter (« John »)) ; // cherche tous les Johns

Comment implémenter ceci? Voici quelques conseils.

D'abord qu'est-ce que la "structure"? Il s'agit d'un wrapper over collection qui peut probablement contenir des fonctionnalités d'indexation à l'avenir. Cette classe devrait avoir un constructeur comme

Structure (données de collection);

Sa méthode de recherche itère collection donnée et appelle la méthode compateTo() de FieldFilter qui implémente Comparable:

pour (T élém: collection) { if (filter.compareTo (élém)) { result.add (elem); } } renvoyer le résultat;

Le FieldFilter devrait ressembler à:

FieldFilter public class implémente Comparable {private String fieldName; private V fieldValue; public FieldFilter (String fieldName, V fieldValue) { this.fieldName = nomdechamp; this.fieldValue = fieldValue; } public booléen compareTo (T elem) { Champ de champ = elem.getClass(). GetField (fieldName); field.setAccessible (true); return field.getValue(). Est égal à (fieldValue); } }

Veuillez noter que le code a été écrit directement sous forme de réponse, n'a jamais été compilé, donc il ne peut pas être utilisé tel quel. Mais j'espère que cela décrit l'idée.

+0

Je pensais aussi à utiliser la réflexion, mais je pense que ce n'est pas très optimal. Puisque les programmeurs - en tant que clients API - ne sont pas très conscients de java.lang.reflect et cela apporte aussi des problèmes potentiels (nous savons tous lesquels). – Xorty

0

Si vous souhaitez appliquer un mappage via des génériques entre le type recherché et le type de paramètre transmis à la méthode search(), vous devez le spécifier quelque part. Je ne pense pas qu'il existe un moyen de le faire sans au moins une interface de marqueur générique pour les classes du client.

Quelque chose comme cela fonctionnerait par exemple:

public interface Searchable<T> { } 

public class Person implements Searchable<String> { 
    // ... 
} 

public <T extends Searchable<K>, K> T search(K key) { 
    // ... 
} 

Alors maintenant, le compilateur permettra:

Person p = search("John Doe"); 

mais pas:

Person p = search(5); 

Si même une interface marqueur est pas une option, je crains que le mieux que vous pouvez espérer est d'utiliser des méthodes de recherche spécifiques basées sur le type de paramètre, comme:

public <T> T searchByInt(int key) { 
    // ... 
} 

public <T> T searchByString(String key) { 
    // ... 
} 

mais bien sûr, cela signifie des cas invalides comme

Person p = searchByInt(5); 

ne seront pas pris au moment de la compilation, mais devront être vérifiés pour la place lors de l'exécution.

+0

yayx, c'est bien et lisible, mais je ne peux pas réimplémenter les classes originales maintenant :) J'apprécie vos conseils, mais le modèle de matcher (écrit Tom Hawtin) convient mieux pour moi maintenant. – Xorty

+0

ok; Je pensais que vous cherchiez une signature de méthode comme search (K), avec K en quelque sorte mappé à la classe Person. Mais si passer à une implémentation d'interface anonyme avec une logique de correspondance est également acceptable, alors oui je suis d'accord que la solution de Tom Hawtin est probablement la meilleure pour vous. –

Questions connexes