Java - установить значение поля на правильный тип из строки (отражение) - PullRequest
0 голосов
/ 08 февраля 2019

У меня есть такой класс:

public enum  Type {
    ONE, TWO
}

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

У меня есть новый объект:

Car car = new Car();

И у меня есть эти данные:

Map<String, String> data....

name - BMW
year - 2018
type - TWO

ключ и значение - Строка

И мне нужно установить это значение для объекта (кроме отражения, я не вижу путей)

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

Я получаю исключение (иначе и не могло бытьЯ знаю):

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

Поэтому возникает вопрос - как правильно привести значение к нужному типу для установки в поле?

Этопростой пример, потому что реальная задача очень долго объясняется.Если вкратце - я получаю список значений (они всегда строки) и имена полей, в которых они изменяются (также строки) и должен обновить поля объекта новыми значениями

Ответы [ 4 ]

0 голосов
/ 09 февраля 2019

Отражение - действительно путь.Вы можете получить тип, используя field.getType(), а затем проверить конкретные классы, используя Class.isAssignableFrom():

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

Конечно, это может стать почти произвольно сложным,но вот полностью рабочий образец для предоставленных вами значений.Это должно дать вам представление о том, как действовать:

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 + "]";
        }       
    }
}

(см. Также по ideone )

0 голосов
/ 08 февраля 2019

Java статически типизирована.Поэтому вам нужно предоставить правильный тип самостоятельно.Integer.valueOf принимает строку и возвращает целое число.

int year = Integer.valueOf("2018");

Преобразование строки в перечисление работает так же.

Type  type = Type.valueOf("ONE");

Enum.valueOf вызывается в фоновом режиме.

Конечно, вам также необходимо добавить некоторую проверку ошибок.


Я бы рекомендовал избегать использования отражения в таком случае.Вы можете использовать другой подход, например,

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

    }

  }

Вы также можете добавить метод setData к строителю

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

Затем создать автомобиль с Car c = new Car.Builder().setData(data).build();.

0 голосов
/ 08 февраля 2019

Допустимым решением с минимальными усилиями будет использование библиотеки JSON в качестве обходного пути, поскольку в них уже реализовано создание значений из строк для наиболее распространенных типов.

Например, используя 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);
0 голосов
/ 08 февраля 2019

Я бы рекомендовал не использовать отражение везде, где это возможно.Как в вашем конкретном примере.

Вы можете создать класс enum, содержащий BiConsumers для каждого из ваших полей в классе Car:

import java.util.Map;
import java.util.function.BiConsumer;

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

    static enum CarEnum {
        name((car, value) -> car.setName(value)),
        year((car, value) -> car.setYear(Integer.parseInt(value))),
        type((car, value) -> car.setType(Ttc.Type.valueOf(value)));
        private BiConsumer<Car, String> setValueConsumer;

        CarEnum(BiConsumer<Car, String> setValueConsumer) {
            this.setValueConsumer = setValueConsumer;
        }

        static Car createCar(Map<String, String> data) {
            Car car = new Car();
            data.forEach((key, value) -> valueOf(key).setValueConsumer.accept(car, value));
            return car;
        }
    }
}

И затем использовать его следующим образом:

Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car.CarEnum.createCar(data);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...