3
votes

Java - définissez la valeur du champ sur le type correct à partir de la chaîne (réflexion)

J'ai une classe comme celle-ci:

Caused by: java.lang.IllegalArgumentException: Can not set int field com.example.mapper.Car.year to java.lang.String

J'ai un nouvel objet:

    Field year = car.getClass().getDeclaredField("year");
    year.setAccessible(true);
    year.set(car, data.get("year"));//2018 as string

Et j'ai ces données:

Map<String, String> data....

name - BMW
year - 2018
type - TWO

clé et valeur - Chaîne

Et j'ai besoin de définir cette valeur sur object (sauf pour la réflexion, je ne vois aucun moyen) p>

Car car = new Car();

Je reçois une exception (différemment et je ne pourrais pas être je sais):

public enum  Type {
    ONE, TWO
}

@Data
public class Car {
    private String name;
    private int year;
    private Type type;
}

Par conséquent, la question est - comment puis-je convertir correctement la valeur dans le type souhaité à définir dans le champ?

Ceci est un exemple simple, car la tâche réelle est très longue expliquée. Si en bref - j'obtiens une liste de valeurs (elles sont toujours une chaîne) et les noms des champs dans lesquels elles changent (également une chaîne) et dois mettre à jour les champs de l'objet avec de nouvelles valeurs

p >


2 commentaires

Dans votre exemple simple, vous devez convertir l ' année en un int , en utilisant Integer.valueOf () . Dans un exemple plus complexe, j'écrirais un mappeur qui mappe une String au type correspondant (le type peut être lu par réflexion). Ces mappeurs peuvent être placés dans une carte de type.


Cela ne répond pas à votre question, mais l'évite simplement: new ObjectMapper (). ConvertValue (data, Car.class) (Utilisez la conversion basée sur json de Jackson d'un type à un autre. Il existe des alternatives, bien sûr)


4 Réponses :


-2
votes

Je recommande de ne pas utiliser la réflexion partout où c'est possible. Comme dans votre exemple exact.

Vous pouvez créer une classe enum qui contient des BiConsumers pour chacun de vos champs dans la classe Car:

Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car.CarEnum.createCar(data);

Et puis l'utiliser de la manière suivante:

XXX


0 commentaires

-2
votes

Java est typé statiquement. Par conséquent, vous devez fournir vous-même le type correct. Integer.valueOf prend une chaîne et renvoie un entier.

      public Builder setData(Map<String, String> data)
      {
        this.year = Integer.valueOf(data.get("year"));
        this.type = Type.valueOf(data.get("type"));
        // etc.
        return this;
      }

La conversion d'une chaîne en énumération fonctionne de la même manière.

  class Car
  {
    private final Type type;
    private final String name;
    private final int year;

    private Car(Builder builder)
    {
      this.type = builder.type;
      this.name = builder.name;
      this.year = builder.year;
    }

    static class Builder
    {
      private Type type;
      private String name;
      private int year;

      public Builder setType(String type)
      {
        this.type = Type.valueOf(type);
        return this;
      }

      public Builder setName(String name)
      {
        this.name = name;
        return this;
      }

      public Builder setYear(String year)
      {
        this.year = Integer.valueOf(year);
        return this;
      }

      public Car build()
      {
        return new Car(this);
      }

    }

  }


0 commentaires

3
votes

Une solution valide avec un minimum d'effort consisterait à utiliser une bibliothèque JSON comme solution de contournement, car ils ont déjà implémenté l'instanciation de valeur à partir de chaînes pour les types les plus courants.

Par exemple, en utilisant ObjectMapper:

   Map<String,String> data = new HashMap<>();
   data.put("year","2018");
   data.put("name", "BMW");
   data.put("type", "TWO");
   ObjectMapper mapper = new ObjectMapper();
   Car car = mapper.readValue(mapper.writeValueAsString(data), Car.class);


1 commentaires

Excellente réponse pour ceux qui souhaitent convertir les paramètres URI en objet POJO. Juste une note supplémentaire: si vous n'avez pas à vous soucier de la sensibilité à la casse; vous pouvez simplement envisager d'utiliser: mapper.configure (MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPE‌ RTIES, true);



0
votes

La réflexion est en effet la voie à suivre. Vous pouvez obtenir le type en utilisant field.getType () , puis recherchez les classes concrètes en utilisant Class.isAssignableFrom () :

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

class CarFiller {

    public static void main(String[] args) throws Exception {
        Map<String, String> data = new HashMap<>();
        data.put("name", "BMW");
        data.put("year", "2018");
        data.put("type", "TWO");

        Car car = new Car();
        fillField(car, "name", data);
        fillField(car, "year", data);
        fillField(car, "type", data);

        System.out.println(car);
    }

    private static void fillField(Object instance, String fieldName, Map<String, String> data) throws Exception {
        Field field = instance.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);

        String value = data.get(fieldName);
        Object typedValue = null;
        final Class<?> type = field.getType();
        if (int.class.isAssignableFrom(type)) {
            typedValue = Integer.parseInt(value);
        } else if (type.isEnum()) {
            typedValue = Enum.valueOf((Class<Enum>) type, value);
        } else {
            // Assume String
            typedValue = value;
        }

        field.set(instance, typedValue);
    }

    enum  Type {
        ONE, TWO
    }

    static class Car {
        private String name;
        private int year;
        private Type type;

        public void setName(String name) {
            this.name = name;
        }
        public void setYear(int year) {
            this.year = year;
        }
        public void setType(Type type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return "Car [name=" + name + ", year=" + year + ", type=" + type + "]";
        }       
    }
}

Bien sûr cela peut devenir presque arbitrairement complexe, mais voici un échantillon entièrement fonctionnel pour les valeurs fournies. Cela devrait vous donner un aperçu de la marche à suivre:

final Class<?> type = field.getType();
if (int.class.isAssignableFrom(type)) {
    typedValue = Integer.parseInt(value);
} else if (type.isEnum()) {
    typedValue = Enum.valueOf((Class<Enum>) type, value);
} else {
    // Assume String
    typedValue = value;
}

(Voir aussi sur ideone )


0 commentaires