Java泛型 - 类型转换问题

3

我有一个通用方法,可以接受类类型和需要更新的该类字段。

例如:
class A {
  private int a;
  private int b;
}

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

在运行时,如果我们传递类类型为"A.class"和要更新的字段为"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 语句检查类类型,然后将对象强制转换为该类并不是正确的实现方式。
有没有更好的方法来实现相同的功能?
提前致谢。

顺便提一下,这似乎不是使用parallelStream的好方法。您没有那么多字段需要更新,因此性能可能更差,并且您的forEach lambda[修改共享状态而没有任何线程安全性](https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html)。 - Radiodef
3个回答

4
您的 AB 类似乎都提供了 set/getCreatedTimeset/getUpdatedTime 方法。
如果其他28个左右的类也提供了这些方法(根据您的问题所暗示的),那么只需要有一个通用的接口来包含这些方法,让所有的类都实现即可。
然后,您可以将方法的泛型类型绑定到该接口,并避免所有的 instanceof 语句和随后的显式转换。
唯一的缺点是,如果您的 List 中有一个字段名称与传递给方法的具体类无关,则您需要使用对象反射来发现该字段是否存在。然后,您可以使用记录日志警告(或您认为适当的任何机制)来轻松处理任何缺失的字段。 注意 正如 thijs-steel 所提到的,如果您的“时间”方法共享相同的实现,则可以使您的30个类扩展一个共同的抽象父类,该父类仅实现“时间”方法。
或者,你可以使用默认方法,因为你显然使用的是Java 8。 示例
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 案例上执行错误处理。

你还可以通过仍然拥有以 T extends Findable(其中Findable 声明了“时间”方法和 find 方法)为参数的 find 方法,并在给定的 T 对象上简单地调用 find 方法来概括行为。

甚至可以将 FindableTimed 的关注点分开,并让你的类实现两者。


不妨采用实际的类继承,而非接口。 - Thijs Steel
@ThijsSteel 的确,如果“时间”方法有一个共同的实现。 - Mena
这30个类中没有任何一个具有相似的字段。如示例所述,这些类将拥有不同的字段。 - Sindhura Gudarada
@SindhuraGudarada 我理解了,之前可能没有表达清楚。可以看一下我的编辑,提供了另一种方法。 - Mena

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

0

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

我正在迭代fieldsToBeUpdated参数列表,并在每次迭代中使用以下代码检查对象(作为参数传递)中是否存在该字段:

如果存在,则返回该字段对象,否则返回null。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接