web-dev-qa-db-fra.com

Méthodes génériques renvoyant des types d'objets dynamiques

Peut-être une question qui a déjà été posée, mais comme d'habitude la seconde vous mentionnez le mot générique vous obtenez mille réponses expliquant l'effacement du type. J'ai traversé cette phase il y a longtemps et j'en sais maintenant beaucoup sur les génériques et leur utilisation, mais cette situation est un peu plus subtile.

J'ai un conteneur représentant une cellule de données dans une feuille de calcul, qui stocke en fait les données dans deux formats: sous forme de chaîne à afficher, mais également dans un autre format, en fonction des données (stockées en tant qu'objet). La cellule contient également un transformateur qui convertit entre les types, et effectue également des vérifications de validité pour le type (par exemple, un IntegerTransformer vérifie si la chaîne est un entier valide, et si elle retourne un entier à stocker et vice versa).

La cellule elle-même n'est pas dactylographiée car je veux pouvoir changer le format (par exemple changer le format secondaire pour flotter au lieu d'entier, ou en chaîne brute) sans avoir à reconstruire l'objet cellule avec un nouveau type. une tentative précédente a utilisé des types génériques mais n'a pas pu changer le type une fois défini, le codage est devenu très volumineux avec beaucoup de réflexion.

La question est: comment puis-je extraire les données de ma cellule d'une manière typée? J'ai expérimenté et trouvé que l'utilisation d'un type générique pouvait se faire avec une méthode même si aucune contrainte n'était définie

public class Cell {
    private String stringVal;
    private Object valVal;
    private Transformer<?> trans;
    private Class<?> valClass;

    public String getStringVal(){
        return stringVal;
    }

    public boolean setStringVal(){
        //this not only set the value, but checks it with the transformer that it meets constraints and updates valVal too
    }

    public <T> T getValVal(){
        return (T) valVal;
        //This works, but I don't understand why
    }
}

Le bit qui me décourage est: c'est? il ne peut rien couler, il n'y a aucune entrée de type T qui le contraint à correspondre à quoi que ce soit, en fait il ne dit rien vraiment nulle part. Avoir un type d'objet de retour ne fait que compliquer le lancer partout.

Dans mon test, j'ai défini une valeur Double, il a stocké le Double (en tant qu'objet), et quand je l'ai fait Double testdou = testCell.getValVal (); cela a fonctionné instantanément, sans même un avertissement de casting non contrôlé. cependant, quand j'ai fait String teststr = testCell.getValVal (), j'ai eu une ClassCastException. Sans surprise vraiment.

Il y a deux points de vue que je vois à ce sujet:

Un: utiliser un Cast non défini ne semble être rien de plus qu'un moyen de contourner pour placer le cast à l'intérieur de la méthode plutôt qu'à l'extérieur après son retour. C'est très soigné du point de vue de l'utilisateur, mais l'utilisateur de la méthode doit se soucier d'utiliser les bons appels: tout ce que cela fait, c'est cacher des avertissements et des vérifications complexes jusqu'à l'exécution, mais semble fonctionner.

Le deuxième point de vue est le suivant: je n'aime pas ce code: il n'est pas propre, ce n'est pas le genre de qualité de code dont je suis normalement fier par écrit. Le code doit être correct, pas seulement fonctionnel. Les erreurs doivent être détectées et gérées, et anticipées, les interfaces doivent être infaillibles même si le seul utilisateur est moi-même, et je préfère toujours une technique générique et réutilisable flexible à une technique maladroite. Le problème est: existe-t-il un moyen normal de procéder? Est-ce un moyen sournois d'atteindre le ArrayList sans type, qui accepte tous ce que vous voulez sans lancer? ou y a-t-il quelque chose qui me manque ici. Quelque chose me dit que je ne devrais pas faire confiance à ce code!

peut-être plus d'une question philosophique que je ne le pensais, mais je suppose que c'est ce que je demande.

modifier: tests supplémentaires.

J'ai essayé les deux extraits intéressants suivants:

public <T> T getTypedElem() {
    T output = (T) this.typedElem;
    System.out.println(output.getClass());
    return output;
}

public <T> T getTypedElem() {
    T output = null;
    try {
        output = (T) this.typedElem;
        System.out.println(output.getClass());
    } catch (ClassCastException e) {
        System.out.println("class cast caught");
        return null;
    }
    return output;
}

Lorsque j'attribue un double à typedElem et que j'essaye de le mettre dans une chaîne, j'obtiens une exception NON sur le cast, mais sur le retour, et le deuxième extrait ne protège pas. La sortie de la getClass est Java.lang.Double, ce qui suggère que cela est déduit dynamiquement de typedElem, mais que les vérifications de type au niveau du compilateur sont juste forcées à l'écart.

Comme note pour le débat: il y a aussi une fonction pour obtenir la valClass, ce qui signifie qu'il est possible de faire un contrôle d'assignation lors de l'exécution.

Edit2: résultat

Après avoir réfléchi aux options, j'ai opté pour deux solutions: l'une la solution légère, mais annoté la fonction comme @depreciated, et deuxièmement la solution où vous lui passez la classe dans laquelle vous souhaitez essayer de la convertir. de cette façon, c'est un choix en fonction de la situation.

25
K.Barad

Vous pouvez essayer de taper des jetons:

public <T> T getValue(Class<T> cls) {
    if (valVal == null) return null;
    else {
        if (cls.isInstance(valVal)) return cls.cast(valVal);
        return null;
    }
}

Notez que cela n'effectue aucune conversion (c'est-à-dire que vous ne pouvez pas utiliser cette méthode pour extraire un Double, si valVal est une instance de Float ou Integer).

Vous devriez obtenir, btw., Un avertissement du compilateur concernant votre définition de getValVal. C'est parce que le cast ne peut pas être vérifié au moment de l'exécution (les génériques Java fonctionnent par "effacement", ce qui signifie essentiellement que les paramètres de type générique sont oubliés après la compilation), donc le code généré ressemble plus à:

public Object getValVal() {
    return valVal;
}
20
Dirk

Comme vous le découvrez, il y a une limite à ce qui peut être exprimé en utilisant le système de type Java, même avec des génériques. Parfois, il existe des relations entre les types de certaines valeurs que vous souhaitez affirmer à l'aide de déclarations de type, mais vous ne pouvez pas (ou peut-être vous pouvez, au prix d'une complexité excessive et d'un code long et prolixe). Je pense que l'exemple de code dans ce post (questions et réponses) en est une bonne illustration.

Dans ce cas, le compilateur Java pourrait faire plus de vérification de type si vous avez stocké la représentation objet/chaîne à l'intérieur du "transformateur". (Peut-être devrez-vous repenser ce que c'est: peut-être que ce n'est pas juste un "transformateur".) Mettez une borne générique sur votre base Transformer classe, et faites de cette même borne le type de l '"objet".

En ce qui concerne l'obtention de la valeur out de la cellule, il n'y a aucun moyen que la vérification du type de compilateur vous y aide, car la valeur peut être de différents types (et vous ne savez pas au moment de la compilation quel type de l'objet sera stocké dans une cellule donnée).

Je pense que vous pourriez également faire quelque chose de similaire à:

public <T> void setObject(Transformer<T> transformer, T object) {}

Si la seule façon de définir le transformateur et l'objet consiste à utiliser cette méthode, la vérification du type de compilateur sur les arguments empêchera une paire transformateur/objet incompatible d'entrer dans une cellule.

Si je comprends ce que vous faites, le type de Transformer que vous utilisez est déterminé uniquement par le type d'objet que contient la cellule, n'est-ce pas? Si c'est le cas, plutôt que de définir le transformateur/objet ensemble, je fournirais un setter pour l'objet uniquement et ferais une recherche de hachage pour trouver le transformateur approprié (en utilisant le type d'objet comme clé). La recherche de hachage peut être effectuée à chaque fois que la valeur est définie ou lorsqu'elle est convertie en chaîne. Dans les deux cas, cela fonctionnerait.

Cela rendrait naturellement impossible la transmission du mauvais type de Transformer.

3
Alex D

Je pense que vous êtes un gars de type statique, mais laissez-moi essayer: avez-vous pensé à utiliser un langage dynamique comme groovy pour cette partie?

D'après votre description, il me semble que les types gênent plus que d'aider quoi que ce soit.

Dans groovy, vous pouvez laisser le Cell.valVal Être de type dynamique et obtenir une transformation facile autour:

class Cell {
  String val
  def valVal
}

def cell = new Cell(val:"10.0")
cell.valVal = cell.val as BigDecimal
BigDecimal valVal = cell.valVal
assert valVal.class == BigDecimal
assert valVal == 10.0

cell.val = "20"
cell.valVal = cell.val as Integer
Integer valVal2 = cell.valVal
assert valVal2.class == Integer
assert valVal2 == 20

as c'est tout ce dont vous avez besoin pour les transformations les plus courantes. Vous pouvez également ajouter le vôtre.

Si vous devez transformer d'autres blocs de code, notez que la syntaxe de Java est une syntaxe groovy valide, à l'exception du bloc do { ... } while()

1
Will