I have class like this:
public enum Type {
ONE, TWO
}
@Data
public class Car {
private String name;
private int year;
private Type type;
}
I have new object:
Car car = new Car();
And I have this data:
Map<String, String> data....
name - BMW
year - 2018
type - TWO
key and value - String
And I need set this values to object(except for reflection, I see no ways)
Field year = car.getClass().getDeclaredField("year");
year.setAccessible(true);
year.set(car, data.get("year"));//2018 as string
I get exception(differently and could not be I know):
Caused by: java.lang.IllegalArgumentException: Can not set int field com.example.mapper.Car.year to java.lang.String
Therefore, the question is - how do I correctly cast the value to the desired type to set in the field?
This is a simple example, because the real task is very long explained. If in short - I get a list of values (they are always a string) and the names of the fields in which they change (also a string) and must update the fields of the object with new values
A valid solution with minimum effort would be using a JSON library as a workaround, since they have already implemented value instantiation from strings for the most common types.
For example, using 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);
Reflection is indeed the way to go. You can get the type using field.getType()
and then check for concrete classes using 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;
}
Of course this can become almost arbitrarily complex, but here's a fully working sample for your provided values. That should give you a gist on how to proceed:
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 + "]";
}
}
}
(See also on ideone )
I'd recommend not to use reflection everywhere it's possible. Like in your exact example.
You can create an enum class wich contains BiConsumers for each of your fields in Car class:
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;
}
}
}
And then use it in the next way:
Map<String, String> data = new HashMap<>();
data.put("name", "BMW");
data.put("year", "2018");
data.put("type", "TWO");
Car.CarEnum.createCar(data);
Java is statically typed. Therefore you need to provide the correct type yourself. Integer.valueOf
takes a String and returns an Integer.
int year = Integer.valueOf("2018");
Converting a String to an Enum works the same.
Type type = Type.valueOf("ONE");
Enum.valueOf
is called in the background.
Of course you also need to add some error checking.
I'd recommand avoiding the use of reflection in such a case. You could use a different approach, eg
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);
}
}
}
You could also add a setData
method to the builder
public Builder setData(Map<String, String> data)
{
this.year = Integer.valueOf(data.get("year"));
this.type = Type.valueOf(data.get("type"));
// etc.
return this;
}
Then create a car with Car c = new Car.Builder().setData(data).build();
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.