Java generators - type selection issue

I have a generic method that takes a class type and fields of that class to update. For example:

class A {
  private int a;
  private int b;
}

class B {
 private int c;
 private int d;
}

      

At runtime, if we pass the class type as "A.class" and fieldstoBeUpdated as "b" then what's the best way to access the field getter / setter of that particular class so that we can modify the fields.

public <T> void find(T clazz, List<String> fieldsToBeUpdated) {
List<T> collectionList = findAll((Class<T>) clazz);
collectionList.parallelStream().forEach(p -> {
        if (clazz instanceof A) {
            fieldsToBeUpdated.parallelStream().forEach(classFieldName -> {
                switch(classFieldName) {
                case "a":((A)p).setA(10);
                break;
                case "b":((A)p).setB(20);
                break;
                }
            });
        }

        if (clazz instanceof B) {
            fieldsToBeUpdated.parallelStream().forEach(classFieldName -> {
                switch(classFieldName) {
                case "c":((B)p).setC(30);
                break;
                case "d":((B)p).setD(40);
                break;
                }
            });
        }
    });
}

      

I wrote the above code to achieve the same.

But the problem is that I have 30 such classes that will be passed as a parameter to this generic method with a list of fields of this class to update / change.

It is not the correct implementation to write 30 such if statements checking the type of a class and then inject an object cast into that class.

Is there a better way to achieve the same?

Thanks in advance.

+3


source to share


3 answers


Using @Mena's suggestion I came up with a solution using the reflection API.

I iterate over the parameter list fieldToBeUpdated and in each iteration check if this field is present in the object (passed as a parameter) using the following piece of string:

This returns a field object if null is still present.



null != clazz.getDeclaredField(field)

      

Below is the entire implementation logic:

public <T> void find(Class clazz, List<String> fieldsToBeUpdated) {
    List<T> collectionList = db.findAll((Class<T>) clazz);
    if (CollectionUtils.isNotEmpty(collectionList)) {
        collectionList.stream().forEach(p -> {
            fieldsToBeUpdated.stream().forEach(field -> {
            Date date = null;
                try {
                 if (null != clazz.getDeclaredField(field)) {
                        Field f = clazz.getDeclaredField(field);
                        f.setAccessible(true);
                        date = (Date) f.get(p);
                    }
                } catch (NoSuchFieldException|SecurityException|IllegalArgumentException|IllegalAccessException e) {
                    e.printStackTrace();
                } 
            });
        });
    }
}

      

0


source


It seems that your classes A

both B

provide methods set/getCreatedTime

and set/getUpdatedTime

.

If the other 28 or so classes provide them (as implied by your question), then just use a common interface containing these methods for all classes.

Then you can bind the generic type of your method to that interface and discard all operators instanceof

and subsequent explicit casting.

The only drawback would be if you have a field name in List

that is not specific to the specific class passed to the method.

If you want to apply this, you can use object reflection to see if a field is present by name. Then you can easily handle any missing field with a logged alert (or whatever mechanism you think is assigned).

Note

As thijs-steel mentioned , if your "time" methods use the same implementation, you can have 30 classes extending the generic abstract parent that only implements "temporary" methods.

Or you can use methods default

since you are explicitly using Java 8.



Example

interface I {
    // assuming parameters and return types here
    public void setCreatedTime(ZonedDateTime z);
    public void setUpdatedTime(ZonedDateTime z);
    public ZonedDateTime getCreatedTime();
    public ZonedDateTime getUpdatedTime();
}

// A, B etc. all implement I

public <T extends I> void find(T object, List<String> fieldsToBeUpdated) {
    fieldsToBeUpdated
    .parallelStream()
    .forEach(
        field -> {
            switch(field) {
                case "a": {
                    try {
                        object.getClass().getDeclaredField("a");
                        // we're good
                        object.setCreatedTime(...);
                    }
                    catch (NoSuchFieldException e) {
                        // TODO something
                    }
                    break;
                }
                // ...
            }
        });
}

      

Update

If your classes have no common fields at all, you can completely change the whole approach altogether.

Instead of using the one-common-all-all-method-logical implementation paradigm, you can use inheritance and have your own method implementation find

in each individual class.

This will allow a smaller instruction switch

to be used in each implementation, with error handling done in case default

.

You can also generalize the behavior by still having a method find

that takes T extends Findable

(where it Findable

declares "time" and now methods find

) and just call find

on the given object T

.

Or even separate issues between Findable

and Timed

, and your classes implement both.

+4


source


I would fetch the interface first:

public interface TimeManipulator {
    public void setCreatedTime(long createdTime);
    public long getCreatedTime();
    public void setUpdatedTime(long createdTime);
    public long getUpdatedTime();
}

      

and apply it to classes:

class A implements TimeManipulator {
    ...
 }

class B implements TimeManipulator {
    ...
 }

      

Then you only need to bind T

to this interface:

public <T extends TimeManipulator> void find(T p, List<String> fieldsToBeUpdated) {
    fieldsToBeUpdated.parallelStream().forEach(field -> {
        switch(field) {
        case "a":
        case "c":
            p.setCreatedTime(TimeUtils.toGMT(p.getCreatedTime(), ZoneOffset.of("+05:30")));
            break;
        case "b":
        case "d":p.setUpdatedTime(TimeUtils.toGMT(p.getUpdatedTime(), ZoneOffset.of("+05:30")));
        break;
        }
    });
}

      

0


source







All Articles