2017-05-29 129 views
3

我有一个通用的方法接受类类型和该类的字段进行更新。 对于前:Java泛型 - 类型铸造问题

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

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

在运行时,如果我们通过类类型为“的A.class”和fieldstoBeUpdated为“B”,什么是访问特定类领域的getter/setter方法的最佳途径,以便我们可以修改这些字段。

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

我已经写上面的代码来实现相同。

但问题是我有30个这样的类作为参数传递给这个通用方法,并且该类的字段列表要更新/修改。

它不是正确的实现来编写30个这样的if语句来检查类的类型,然后将类型转换为该类的对象。

有没有更好的方法来实现?

在此先感谢。

+0

作为一个侧面说明,这看起来并不像一个良好的使用parallelStream'的'。你没有那么多的字段需要更新,所以性能可能会更差,而你的'forEach' lambda [修改共享状态时没有任何线程安全性](https://docs.oracle.com/javase/tutorial/必需/并发/ memconsist.html)。 – Radiodef

回答

0

使用@Mena给出的建议,我想出了一个使用反射API的解决方案。

我通过fieldsToBeUpdated参数的列表,并在每一次迭代中(作为参数传递)我检查如果该字段是存在于对象使用下面的片线的迭代:

这将返回字段对象如果存在,否则为空。

null != clazz.getDeclaredField(field) 

下面是对整个执行逻辑:

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

您的AB类似乎都提供了set/getCreatedTimeset/getUpdatedTime方法。

如果其他28个左右的类提供了这些(就像你的问题所暗示的那样),那么只需要有一个以这些方法为特征的通用接口,以供所有类实现。

然后,您可以将方法的通用类型绑定到该接口,并放弃所有instanceof语句和随后的显式转换。

唯一的缺点是如果在List中有一个字段名称与传递给该方法的具体类不相关。

如果要强制执行此操作,可以对对象使用反射来发现字段是否按名称存在。然后,您可以轻松处理任何遗漏的警告字段(或您认为适用的任何机制)。

注意

正如thijs-steel提到的,如果你的“时间”的方法共享相同的实现,你可以有你的30个班扩展了一个公共抽象父,只有实现了“时间”的方法。

或者,你因为你清楚地使用Java 8

实例可以使用default方法

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

更新

如果你的类不共享同一领域的所有,你可能想要完全改变整个方法。

您可能想要使用继承并在每个类中都有自己的find方法实现,而不是具有“一个通用方法适用所有”逻辑实现范例。

这将允许在每个实现中使用较小的switch语句,并在default的情况下执行错误处理。

你也仍然还是有find方法以推广行为的T extends Findable(其中Findable宣布了“时间”的方法现在find方法),并简单地调用指定的T对象find

甚至在FindableTimed之间分开关注,并让你的类同时实现。

+0

而不是一个接口,实际的类继承可能会更好 –

+0

@ThijsSteel确实如果“时间”方法有一个共同的实现。 – Mena

+0

这30个班都没有类似的领域。如示例中所述,这些类将具有不同的字段。 –

0

我想提取接口第一:

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

并将其应用到的类:

class A implements TimeManipulator { 
    ... 
} 

class B implements TimeManipulator { 
    ... 
} 

然后,所有你需要做的就是将T绑定到这个接口:

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