1

I am working with some code where the user sends in an object which can have upwards of 100 values to it. (Let's use a car for example).

updateDatabasePrep(carObject);

The car object COULD have things like name, milage, vin, color, etc... but say I only set the color and then pass the object.

In my method:

public void updateDatabasePrep(Car carObject){
    NewObject myObject = newObject(); //Initialized, but empty
    String x1 = carObject.getMilage();
    String x2 = carObject.getColor();
    String x3 = carObject.getName();
    //These will hold null values no problem

    //Once the rest of the data has been collected:

    //These, however, will error out when I try to set the null values.
    myObject.setMilage(x1);
    myObject.setColor(x2);
    myObject.setName(x3);
}

It uses accessor methods which will pull the data from the passed object and then tries to set said values. In the above situation, it will throw a null pointer error as the milage and name are null; only the color is not.

My goal is to update the database with ONLY the values that have been set. In this case, I COULD write a ton of if/ else or try/ catch statements for every single one, that would work, but I would prefer not to.

Is there a shorter way to do this in Java? Is there a method where you can ONLY set data if it is not-null other than if/ else, try/catch, and going through every single setter method and telling it to skip null values?

EDIT: Just to clarify, the Nullpointerexception will get thrown on when it tries to set a null value. IE

myObject.setMilage(x1);

since people we asking.

PGMacDesign
  • 6,092
  • 8
  • 41
  • 78
  • With a strongly typed language, you should get your hands dirty sometimes. – mtyurt Apr 22 '15 at 20:02
  • 1
    The very first line of the updateDatabasePrep method is not complete, "NewObject myObject...". Can you please update the sample code to show how exactly myObject is created? Because, unless you have some special error checking in the setter methods of the NewObject class, you should not be getting any errors from the code where you set the values on myObject. You WILL, however, get a NullPointerException if you never initialized myObject. – Matej Apr 22 '15 at 20:03
  • 1
    You have to use reflection to solve this. – Luiggi Mendoza Apr 22 '15 at 20:03
  • Ok, I will have to look into reflection, I have not used it before – PGMacDesign Apr 22 '15 at 20:08
  • OK, I see you updated the sample code by adding a semicolon after myObject, BUT, this would never actually compile because you are attempting to set something on myObject while it was never initialized inthe first place. Don't know how close this is to your actual code, but at some point before calling the setters on myObject, you have to assign a new instance of NewObject to it, like "NewObject myObject = new NewObject();". I assume you know this already? – Matej Apr 22 '15 at 20:08
  • @Matej I fixed it, just an example – PGMacDesign Apr 22 '15 at 20:08
  • I think there is a google library that helps you do that in java. They have convenience methods like setIfNotNull or something like that. – lupus137 Apr 22 '15 at 20:11
  • @lupus137 you mean [`Optional`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html) that is in [Java 8](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) as well. – Luiggi Mendoza Apr 22 '15 at 20:14
  • OK, great. Now, what exception are you actually getting there? What does the stack trace say? – Matej Apr 22 '15 at 20:14
  • @LuiggiMendoza Nice. That seems like the tool for the job. – lupus137 Apr 22 '15 at 20:17
  • @lupus137 I would say that's only part of the job, but still won't be sure. – Luiggi Mendoza Apr 22 '15 at 20:17
  • Of course you could use something like Optional, as Luiggi suggests, but the question is, do you really need it. At least from the code sample you posted in the question, it is not really clear where or why you would be getting an exception. Do you somehow inside the NewObject class require the values that you set via the setters to be non-null? Is it really the case for all of them? If so, why even do it via the setter, and not straight via the constructor, to make your intent clearer. And finally, if you can really have 100 fields in this class, it would be good to re-think the architecture. – Matej Apr 22 '15 at 20:23
  • If you really need lots of dependencies which you pass in via a constructor or setter, and which must not be undefined, of course you will have to check them at some point. If they are absolutely necessary for the object to even make sense, then you would check in the constructor and throw an exception with a meaningful message so you know what was not passed in correctly. This is boilerplate code, but like @mtyurt said, at some point you'll have to write it. And if the values are optional, do a check before using them, or use Optional as Luiggi Mendoza said. – Matej Apr 22 '15 at 20:31

4 Answers4

2

Assuming myObject is always going to be the same type, you could use reflection to do a mapping phase that puts the getter and setter methods together in a hashmap like so:

    private HashMap<Method, Method> methodMap;

    private void doReflection()
    {
      Method[] carMethods = carObject.getClass().getDeclaredMethods();
      ArrayList<Method> getters = new ArrayList<>();

      for(int i = 0; i < carMethods.size(); i++)
        if(carMethods[i].getName().startsWith("get")) getters.add(carMethods[i]);

      methodMap = new HashMap<>();
      for(Method m : getters)
      {
        String fieldName = m.getName().substring(3);
        Method setter = myObject.class.getMethod("set" + fieldName, m.getParameterTypes());

        if(setter != null) methodMap.put(m, setter);
      }
    }

Now you can iterate the HashMap to do assignments like so:

for(MapEntry<Method, Method> entry : methodMap.entrySet())
{
  Method getter = entry.getKey();
  Method setter = entry.getValue();

  Object o = getter.invoke(carObject, null);
  if(o != null) setter.invoke(myObject, getter.invoke(carObject, null));
}
1

I am not sure how you are updating DB, using procedure or prepared call. However, below might be helpful to you.

You can get list of fields having null value -

public List<String> getNullFields(Car car) {
    Field[] fields = car.getClass().getDeclaredFields();
    List<String> result = new ArrayList<String>();
    for (Field field : fields) {
        try {
            field.setAccessible(true);
            Object value = field.get(car);
            if(value == null) {
                result.add(field.getName());
            }
        } catch (IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    return result;
}

Or else, you can get fields having not null value as well (exact values as well) using similar approach.

Then, you can construct your prepared statement dynamically based on this result. Good luck!

Kartic
  • 2,935
  • 5
  • 22
  • 43
1

Reflection is your answer. You may also try using some libraries making it a little more convenient (e.g. commons-beanutils).

So you may do something like:

private void copyIfSpecified(final Car from, final NewObject to, final String propName)
        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    final Object value = PropertyUtils.getSimpleProperty(from, propName);
    if (value != null) {
        PropertyUtils.setSimpleProperty(to, propName, value);
    }
}

and call it from your method:

public void updateDatabasePrep(Car carObject){
    NewObject myObject = new NewObject(); //Initialized, but empty

    try {
        copyIfSpecified(carObject, myObject, "milage");
        copyIfSpecified(carObject, myObject, "color");
        copyIfSpecified(carObject, myObject, "name");
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
        throw new RuntimeException("Error while populating fields", ex);
    }
}

This way you explicitly specify the properties to copy but avoid excessive if statements. In case you don't have to make sure all the fields you want are always copied (e.g. the fieldset is fixed and don't tend to be changed over time) you can do the whole copy thing via reflection (i.e. get all fields of the source object and copy non-null values to the destination object's fields - see PropertyUtils.copyProperties(..) implementation).

ps: note that using this method applies additional restrictions to your classes (Car and NewObject) - they should be java beans: see What is a JavaBean exactly?

pps: and also note that using reflection takes A LOT more time than plain if-statements boilerplate - if you need performance you should think twice.

Community
  • 1
  • 1
FlasH from Ru
  • 1,165
  • 2
  • 13
  • 19
  • Awesome, thank you. Also, Bonus points for mentioning in your post scripts that it does slow down the code and that I should keep an eye out for that. – PGMacDesign Apr 22 '15 at 21:51
0

working & optimized code for only getters & setters (including "is" for boolean types)

public void copyCabInfo(CabInfo pCabInfo){

        for(Map.Entry<Method, Method> entry : doReflection().entrySet())
        {
            Method getter = entry.getKey();
            Method setter = entry.getValue();

            Object o=null;
            try {
                o = getter.invoke(pCabInfo, null);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
                e1.printStackTrace();
            }
            if(o != null)
                try {
                    setter.invoke(this, getter.invoke(pCabInfo, null));
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                    e.printStackTrace();
                }
        }

    }

    private static HashMap<Method, Method> methodMap;

    private static HashMap<Method, Method> doReflection()
    {
        if(methodMap!=null && !methodMap.isEmpty())
            return methodMap;

        Method[] carMethods = CabInfo.class.getDeclaredMethods();
        ArrayList<Method> getters = new ArrayList<>();

        for(int i = 0; i < carMethods.length; i++)
            if(carMethods[i].getName().startsWith("get") || carMethods[i].getName().startsWith("is") ) getters.add(carMethods[i]);

        methodMap = new HashMap<>();
        for(Method m : getters)
        {
            String methodName=m.getName();
            String fieldName = methodName.startsWith("is")?methodName.substring(2):methodName.substring(3);
            Method setter=null;
            try {
                setter = CabInfo.class.getMethod("set" + fieldName, m.getReturnType());
            } catch (NoSuchMethodException | SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            if(setter != null) methodMap.put(m, setter);
        }

        return methodMap;
    }