J'ai un Java.lang.reflect.InvocationHandler
et je dois implémenter la méthode invoke ()
J'ai une valeur de type Java.lang.String
de mon élaboration et j'ai besoin de convertir cette valeur en returnType approprié attendu par la méthode (il peut s'agir d'une primitive comme int, booléenne, double ou wrapper comme Boolean, Integer, Double, Float, etc.).
Exemple:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String computedValue = compute(...);
return convert(method.getReturnType(), computedValue);
}
private Object convert(Class<?> returnType, String stringValue) {
return ...; // what's the simplest way?
}
Je ne m'attends pas à simplement implémenter une conversion automatique entre des objets complexes, mais je m'attends à un moyen simple de convertir de String aux types standard Java.
J'ai vu (trop) de fois des trucs comme ça, mais ça ne me semble pas approprié:
public static Object toObject( Class clazz, String value ) {
if( Boolean.class.isAssignableFrom( clazz ) ) return Boolean.parseBoolean( value );
if( Byte.class.isAssignableFrom( clazz ) ) return Byte.parseByte( value );
if( Short.class.isAssignableFrom( clazz ) ) return Short.parseShort( value );
if( Integer.class.isAssignableFrom( clazz ) ) return Integer.parseInteger( value );
if( Long.class.isAssignableFrom( clazz ) ) return Long.parseLong( value );
if( Float.class.isAssignableFrom( clazz ) ) return Float.parseFloat( value );
if( Double.class.isAssignableFrom( clazz ) ) return Double.parseDouble( value );
return value;
}
et ce qui précède n'est même pas le pire que j'ai vu jusqu'à présent :)
Quelqu'un a-t-il un truc secret ici?
Pour autant que je sache, il n'y a pas de véritable alternative à la version que vous avez présentée. Vous pouvez le simplifier un peu (puisque les types d'encapsuleurs sont tous final
), mais vous devez essentiellement utiliser if
ou switch
ou un hachage pour activer la classe.
Mon conseil est de le coder comme ci-dessus. Le code laid n'est qu'un problème en soi si vous devez le regarder. Mettez-le donc dans une méthode utilitaire et ne le regardez plus.
FWIW - voici comment je simplifierais la méthode:
public static Object toObject( Class clazz, String value ) {
if( Boolean.class == clazz ) return Boolean.parseBoolean( value );
if( Byte.class == clazz ) return Byte.parseByte( value );
if( Short.class == clazz ) return Short.parseShort( value );
if( Integer.class == clazz ) return Integer.parseInt( value );
if( Long.class == clazz ) return Long.parseLong( value );
if( Float.class == clazz ) return Float.parseFloat( value );
if( Double.class == clazz ) return Double.parseDouble( value );
return value;
}
C'est plus simple et plus efficace. Et elle est équivalente à la version d'origine car les classes sont toutes final
et parce que les spécifications indiquent que l'égalité pour les objets Class
est l'identité de l'objet.
On devrait peut-être utiliser les méthodes <wrapper>.valueOf(String)
qui retournent directement les objets wrapper.
Je ne prétends pas que c'est moins moche ... mais la "beauté" n'est pas une mesure utile de la qualité du code, car elle est subjective et parce qu'elle ne vous dit pas si le code est facile à comprendre et/ou à maintenir.
MISE À JOUR
Pour prendre également en charge les types primitifs, ajoutez les classes correspondantes aux conditions if
; par exemple.
if (Boolean.class == clazz || Boolean.TYPE == clazz) {
return Boolean.parseBoolean(value);
}
Il peut maintenant arriver au point où faire un changement de chaîne sur le nom du type est plus efficace, bien qu'il y ait quelques problèmes légèrement noueux d'identité de type qui doivent être réfléchis. (En théorie, vous pouvez avoir plusieurs types avec le même nom complet qui ont été chargés par différents chargeurs de classe. Je pense que vous auriez besoin de "jouer rapidement et librement" dans un chargeur de classe pour le faire avec les classes de wrapper primitif ... mais Je pense que cela pourrait encore être possible.)
Je pense avoir trouvé quelque chose
import Java.beans.PropertyEditor;
import Java.beans.PropertyEditorManager;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String returnValue = ...
return convert(method.getReturnType(), returnValue);
}
private Object convert(Class<?> targetType, String text) {
PropertyEditor editor = PropertyEditorManager.findEditor(targetType);
editor.setAsText(text);
return editor.getValue();
}
Je pense que ces 3 lignes de code sont meilleures que les if multiples, et j'ai évité d'ajouter des dépendances de bibliothèque externes, puisque Java.beans
le package est à l'intérieur des bibliothèques standard Java Java (javadocs: PropertyEditorManager
).
Je trouve cela tout à fait acceptable; ma seule perplexité est que PropertyEditor
est contenu dans Java.beans
package et j'aurais préféré quelque chose de disponible dans Java.util
ou Java.lang.reflect
package, car ce code n'a rien à voir avec Java.beans
réellement.
Le code ci-dessus présente également l'avantage de pouvoir enregistrer des instances PropertyEditor
supplémentaires pour traduire des objets complexes, btw. Ce n'est pas une mauvaise chose à avoir cependant.
Je pense que c'est mieux qu'une liste d'if, en beauté, mais aussi en qualité.
Probablement org.Apache.commons.beanutils.ConvertUtils peut aider?
import org.Apache.commons.beanutils.ConvertUtils;
// ...
final Object v = ConvertUtils.convert("42", Integer.class);
dans jdk8, vous pouvez maintenant faire quelque chose comme ça O(1) temps de recherche sans instructions if ...
Une meilleure version maintenant qui gère correctement les valeurs NULL est ici
private Map<Class<?>, Function<String, Object>> classToUnmarshaller = new HashMap<>();
private Map<Class<?>, Function<Object, String>> classToMarshaller = new HashMap<>();
public ObjectTranslator() {
classToUnmarshaller.put(Boolean.class, s -> s == null ? null : Boolean.parseBoolean(s));
classToUnmarshaller.put(Boolean.TYPE, s -> Boolean.parseBoolean(s));
classToUnmarshaller.put(Byte.class, s -> s == null ? null : Byte.parseByte(s));
classToUnmarshaller.put(Byte.TYPE, s -> Byte.parseByte(s));
classToUnmarshaller.put(Short.class, s -> s == null ? null : Short.parseShort(s));
classToUnmarshaller.put(Short.TYPE, s -> Short.parseShort(s));
classToUnmarshaller.put(Integer.class, s -> s == null ? null : Integer.parseInt(s));
classToUnmarshaller.put(Integer.TYPE, s -> Integer.parseInt(s));
classToUnmarshaller.put(Long.class, s -> s == null ? null : Long.parseLong(s));
classToUnmarshaller.put(Long.TYPE, s -> Long.parseLong(s));
classToUnmarshaller.put(Float.class, s -> s == null ? null : Float.parseFloat(s));
classToUnmarshaller.put(Float.TYPE, s -> Float.parseFloat(s));
classToUnmarshaller.put(Double.class, s -> s == null ? null : Double.parseDouble(s));
classToUnmarshaller.put(Double.TYPE, s -> Double.parseDouble(s));
classToUnmarshaller.put(String.class, s -> s);
classToMarshaller.put(Boolean.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Boolean.TYPE, s -> s.toString());
classToMarshaller.put(Byte.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Byte.TYPE, s -> s.toString());
classToMarshaller.put(Short.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Short.TYPE, s -> s.toString());
classToMarshaller.put(Integer.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Integer.TYPE, s -> s.toString());
classToMarshaller.put(Long.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Long.TYPE, s -> s.toString());
classToMarshaller.put(Float.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Float.TYPE, s -> s.toString());
classToMarshaller.put(Double.class, s -> s == null ? null : s.toString());
classToMarshaller.put(Double.TYPE, s -> s.toString());
classToMarshaller.put(String.class, s -> s == null ? null : s.toString());
}
public Function<String, Object> getUnmarshaller(Class<?> paramTypeToCreate) {
return classToUnmarshaller.get(paramTypeToCreate);
}
public Function<Object, String> getMarshaller(Class<?> type) {
return classToMarshaller.get(type);
}
de telle sorte que vous puissiez ensuite appeler
primitiveTranslator.getConverter(Integer.TYPE).apply(stringToConvert);
Il existe une bibliothèque légère qui analyse les chaînes en types Java qui fait ce que vous voulez. Elle s'appelle analyseur de type et vous pouvez la trouver sur github ici .
Votre code ci-dessus pourrait alors ressembler à ceci:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TypeParser parser = TypeParser.newBuilder().build();
String computedValue = compute(...);
return parser.parseType(computedValue, method.getGenericReturnType());
}
Je propose ceci:
List<Class<?>> clsList = new ArrayList<Class<?>>();
clsList.add(Boolean.class);
clsList.add(Integer.class);
//etc.
for (Class<?> cls : clsList) {
if (cls.isAssignableFrom(clazz)) {
return cls.getMethod("valueOf", new Class[] { String.class }).invoke(null, new Object[] { value });
//Missing in this example: Handle a few exceptions
}
}
Je vous laisse le soin de savoir si cela semble plus propre ou plus laid.