2012-12-19 10 views
18

Ich habe ein java.lang.reflect.InvocationHandler und ich brauche die Methode invoke()Wie von String in einem primitiven Typ oder Standard-Java-Wrapper-Typen konvertieren

ich einen Wert vom Typ habe zu implementieren java.lang.String von meiner Ausarbeitung und ich brauche zu konvertieren Dieser Wert entspricht dem von der Methode erwarteten returnType (es kann ein Primitiv wie int, boolean, double oder Wrapper-Klassen wie Boolean, Integer, Double, Float usw. sein).

Beispiel:

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? 
} 

Ich erwarte nicht einfach eine automatische Konvertierung zwischen komplexen Objekten zu implementieren, aber ich erwarte, dass eine einfache Art und Weise von String zu dem Standard-Java-Typ zu konvertieren.

Ich habe (zu) gesehen viele Male Sachen wie diese, aber es scheint mir nicht angemessen:

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; 
} 

und der oben ist nicht einmal das Schlimmste, das ich sah, so weit :)

Hat jemand hier einen geheimen Trick?

+1

Sagen Sie uns: 1. Warum Ihr erstes Beispiel nicht funktioniert, und 2. Was "nicht angemessen" bedeutet. –

+1

1. Das erste Beispiel funktioniert nicht, weil in der Convert-Methode die Implementierung fehlt (was das Thema für diese Frage ist) 2. scheint nicht angemessen bedeutet, dass diese Implementierung meiner Meinung nach ziemlich hässlich ist die vielen Wenns, und ich nehme an, es gibt einen besseren Weg, diesen Job zu machen. –

+1

Da das zweite Beispiel funktioniert * (aber es ist nur hässlich), http://codereview.stackexchange.com könnte eine bessere Option sein. – luiscubal

Antwort

22

Soweit ich weiß, gibt es keine echte Alternative zu der von Ihnen vorgestellten Version. Sie können es ein wenig vereinfachen (da die Wrapper-Typen alle final sind), aber Sie müssen im Wesentlichen if oder switch oder Hashing verwenden, um die Klasse einzuschalten.

Mein Rat ist es, es wie das oben zu kodieren. Hässlicher Code ist nur ein Problem per se wenn Sie es betrachten müssen. Setzen Sie es also in eine Hilfsmethode und schauen Sie es nicht noch einmal an.


FWIW - das ist, wie ich das Verfahren vereinfachen würde:

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; 
} 

Das ist einfacher und effizienter zu gestalten. Und es entspricht der ursprünglichen Version, da die Klassen alle final sind und weil die Spezifikationen angeben, dass die Gleichheit für Class Objekte Objektidentität ist.

Wahrscheinlich sollten wir die <wrapper>.valueOf(String) Methoden verwenden, die die Wrapper-Objekte direkt zurückgeben.

Ich behaupte nicht, dass dies weniger hässlich ist ... aber "Schönheit" ist kein nützliches Maß für die Codequalität, weil es subjektiv ist und nicht sagt, ob der Code einfach zu verstehen ist und/oder pflegen.

UPDATE

Um auch primitive Typen zu unterstützen, fügen Sie die entsprechenden Klassen zu den if Bedingungen; z.B.

if (Boolean.class == clazz || Boolean.TYPE == clazz) { 
     return Boolean.parseBoolean(value); 
    } 

Es ist nun an dem Punkt bekommen kann, wo durch das Tun einen String-Schalter auf den Namen des Typs ist effizienter, obwohl es einige leicht knorrigen Fragen der Art Identität sind, die müssen zu denken. (In der Theorie können Sie mehrere Typen mit demselben vollständigen Namen haben, die von verschiedenen Klassenladern geladen wurden. Ich denke, Sie müssten "schnell und locker" in einem Klassenlader spielen, um das mit den primitiven Wrapper-Klassen zu tun ... aber Ich denke, es könnte noch möglich sein.)

+3

+1 - Einverstanden, das ist ungefähr so ​​einfach, wie ich es mir vorstellen kann. Persönlich würde ich lieber all die "zusätzliche" Arbeit ausgeben, um Generics und eine Map zu verwenden, um den eigentlichen Typ von der Konvertierung zurückzuholen, anstatt eine Objektreferenz (die später eventuell eine weitere Konvertierung erfordert). –

+1

'aber" Schönheit "ist kein nützliches Maß für die Codequalität" Code ist Poesie, hässlicher Code ist ein Fehler. nur scherzen :) –

+1

@ LuigiR.Viggiano - Sie sollten nicht vergessen, dass die Leute, die für den Code bezahlen, den Sie schreiben wollen, Zeug, das funktioniert ... nicht Poesie. –

3

Ich schlage vor, diese:

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 
    } 
} 

ich es Ihnen überlassen werden, ob diese Reiniger oder hässlicher aussieht.

+1

Eine andere Option, die ich denke, um Reflexion zu vermeiden, wäre, eine Karte , Converter> zu erstellen, um Reflexion zu vermeiden. Converter-Schnittstelle könnte eine einzige Methode haben 'Objekt konvertieren (String)', dann Boolean.class BooleanConverter Mapping; Integer.class zu IntegerConverter und so weiter ... zumindest wird das nicht so hässlich wie mehrere Wenns. –

+1

Das funktioniert nicht mit primitiven Typen arbeiten: int.class.getMethod („valueOf“, neue Klasse [] {} String.class) wirft NoSuchMethodException –

+1

@ LuigiR.Viggiano - Es sollte darauf hingewiesen werden, dass der Code, den Sie versuchen, Ersetzen funktioniert auch nicht mit Primitiven. –

20

Ich glaube, ich etwas gefunden

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(); 
} 

Ich denke, dass diese drei Zeilen Code sind besser als die mehreren ifs, und ich vermied externe Bibliothek Abhängigkeiten hinzuzufügen, da java.beans Paket innerhalb der Java-Standardbibliotheken ist (Javadocs: PropertyEditorManager).

Ich finde es durchaus akzeptabel; Meine einzige Perplexität ist, dass PropertyEditor in java.beans Paket enthalten ist und ich hätte gerne etwas verfügbar in java.util oder java.lang.reflect Paket, da dieser Code hat eigentlich nichts mit java.beans zu tun.

Der obige Code hat auch den Vorteil, dass Sie zusätzliche PropertyEditor Instanzen registrieren können, um komplexe Objekte zu übersetzen, BTW. Das ist aber keine schlechte Sache.

Ich denke, es ist besser als eine Liste von ifs, in Schönheit, sondern auch in Qualität.

+1

Das funktioniert gut. – NBW

+1

Ich denke, es ist der einzige, der ALSO mit primitiven Typen arbeitet. Aber es funktioniert nicht auf Android: http: //stackoverflow.com/questions/22988376/missing-runtime-dependencies-with-owner-gradle-for-android –

4

Es gibt eine leichtgewichtige Bibliothek, die Strings nach Java-Typen parst, die das tun, was Sie wollen. Es heißt Typ-Parser und Sie können es auf Github here finden.

Ihr oben Code könnte dann wie folgt aussehen:

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()); 
} 
4

in jdk8, haben Sie jetzt etwas tun könnte, wie so O (1) Lookup-Zeit ohne if-Anweisungen ...

Eine bessere Version verarbeitet nun, dass NULL-Werte korrekt ist hier

https://github.com/deanhiller/webpieces/blob/master/webserver/http-router/src/main/java/org/webpieces/router/impl/params/ObjectTranslator.java

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); 
} 

, so dass Sie dann

primitiveTranslator.getConverter(Integer.TYPE).apply(stringToConvert); 
Verwandte Themen