2012-12-19 2 views
18

Ho un java.lang.reflect.InvocationHandler e ho bisogno di implementare il metodo invoke()Come convertire da stringa a un tipo primitivo o tipi wrapper Java standard

ho un valore di tipo java.lang.String dalla mia elaborazione e ho bisogno di convertire questo valore al returnType appropriato previsto dal metodo (può essere primitivo come int, boolean, double o wrapper come Boolean, Integer, Double, Float, ecc.).

Esempio:

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

Non mi aspetto di implementare semplicemente una conversione automatica tra oggetti complessi, ma mi aspetto un modo semplice per convertire da stringa ai tipi standard di Java.

che ho visto (troppo) molte volte cose come questa, ma non sembra appropriato per me:

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

e quanto sopra non è nemmeno il peggiore che ho visto, finora :)

Qualcuno ha un trucco segreto qui?

+1

Dicci: 1. Perché il tuo primo esempio non funziona, e 2. Che cosa "non sembra appropriato" significa. –

+1

1. il primo esempio non funziona perché nel metodo di conversione manca l'implementazione (che è l'argomento soggetto per questa domanda) 2. non sembra appropriato significa che a mio parere questa implementazione è piuttosto brutta a causa di i molti se, e suppongo che ci sia un modo migliore per fare questo lavoro. –

+1

Poiché il secondo esempio * funziona * (ma è solo brutto), http://codereview.stackexchange.com potrebbe essere un'opzione migliore. – luiscubal

risposta

22

Per quanto ne so, non esiste una vera alternativa alla versione che hai presentato. È possibile semplificarlo un po '(poiché i tipi di wrapper sono tutti final), ma in pratica è necessario utilizzare if o switch o hashing per attivare la classe.

Il mio consiglio è di codificarlo come sopra. Il brutto codice è solo un problema di per sé se devi guardarlo. Quindi inseritelo in un metodo di utilità e non guardatelo di nuovo.


FWIW - questo è come mi piacerebbe semplificare il metodo:

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

Questo è più semplice e più efficiente. Ed è equivalente alla versione originale perché le classi sono tutte final e poiché le specifiche indicano che l'uguaglianza per gli oggetti Class è l'identità dell'oggetto.

Probabilmente, dovremmo utilizzare i metodi <wrapper>.valueOf(String) che restituiscono direttamente gli oggetti wrapper.

Non pretendo che questo sia meno brutto ... ma "bellezza" non è una misura utile della qualità del codice, perché è soggettiva e perché non ti dice se il codice è facile da capire e/o mantenere.

UPDATE

per supportare i tipi primitivi pure, aggiungere le classi corrispondenti alle if condizioni; per esempio.

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

Può ora essere sempre al punto in cui fare un interruttore String sul nome del tipo è più efficiente, anche se ci sono alcuni problemi leggermente nodosi di tipo identità che hanno bisogno di essere pensato attraverso. (In teoria, puoi avere più tipi con lo stesso nome completo che sono stati caricati da diversi classloader. Penso che avresti bisogno di "giocare veloce e libero" in un classloader per farlo con le primitive classi wrapper ... ma Penso che potrebbe essere ancora possibile.)

+3

+1 - D'accordo, questo è più semplice di quanto possa pensare anch'io. Personalmente, preferirei spendere tutto il lavoro "extra" per usare i generici e una mappa per recuperare il tipo effettivo dalla conversione, anziché un riferimento a un oggetto (che potrebbe richiedere un'ulteriore conversione in seguito per l'uso!). –

+1

'ma" bellezza "non è una misura utile del codice di qualità codice' è poesia, il codice brutto è un bug. sto scherzando :) –

+1

@ LuigiR.Viggiano - Non dovresti dimenticare che le persone che pagano il codice che scrivi vogliono roba che funzioni ... non poesia. –

3

propongo questo:

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

Lascio a voi decidere se questo sembra più pulito e più brutto.

+1

Un'altra opzione che posso pensare per evitare la riflessione, sarebbe quella di creare una mappa , convertitore> per evitare riflessioni. L'interfaccia del convertitore potrebbe avere un singolo metodo 'Object conversion (String)', quindi mappare Boolean.class in BooleanConverter; Integer.class in IntegerConverter e così via ... almeno, non sarà così brutto come multiplo se. –

+1

Questo non funziona con i tipi primitivi: int.class.getMethod ("valueOf", new Class [] {String.class}) genera NoSuchMethodException –

+1

@ LuigiR.Viggiano - Va sottolineato che il codice che stai provando a la sostituzione non funziona nemmeno con i primitivi. –

20

Penso di aver trovato qualcosa di

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

Penso che quei 3 righe di codice sono meglio le molteplici IFS, e ho evitato di aggiungere dipendenze di librerie esterne, dal momento che java.beans pacchetto è all'interno delle librerie standard di Java (javadocs: PropertyEditorManager).

Lo trovo abbastanza accettabile; la mia unica perplessità è che PropertyEditor è contenuto nel pacchetto java.beans e avrei preferito qualcosa disponibile nel pacchetto java.util o java.lang.reflect, poiché questo codice non ha nulla a che fare con lo java.beans.

Il codice sopra ha anche il vantaggio che è possibile registrare ulteriori istanze PropertyEditor per tradurre oggetti complessi, btw. Non è una brutta cosa avere però.

Penso che sia meglio di un elenco di ifs, in bellezza, ma anche in qualità.

+1

Funziona bene. – NBW

+1

Penso che sia l'unico che funziona ANCHE con i tipi primitivi. Ma non funziona su Android: http: //stackoverflow.com/questions/22988376/missing-runtime-dependencies-with-owner-gradle-for-android –

4

C'è una libreria leggera che analizza le stringhe in tipi di Java che fa quello che vuoi. Si chiama type-parser e puoi trovarlo su github here.

Il tuo codice di cui sopra potrebbe quindi essere simile a questa:

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, si potrebbe ora fare qualcosa di simile in modo da O (1) ricerca volta senza if ...

Una migliore versione ora in grado di gestire i null corrette è qui

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

tale che si può quindi chiamare

primitiveTranslator.getConverter(Integer.TYPE).apply(stringToConvert); 
Problemi correlati