复制Groovy类属性

19

我想以通用的方式将对象属性复制到另一个对象中(如果目标对象上存在该属性,则从源对象复制它)。

使用ExpandoMetaClass,我的代码可以正常运行,但我不喜欢这种解决方法。是否有其他方法可以实现此功能?

class User {
    String name = 'Arturo'
    String city = 'Madrid'
    Integer age = 27
}

class AdminUser {
    String name
    String city
    Integer age
}

def copyProperties(source, target) {
    target.properties.each { key, value ->
        if (source.metaClass.hasProperty(source, key) && key != 'class' && key != 'metaClass') {
            target.setProperty(key, source.metaClass.getProperty(source, key))
        }
    }
}

def (user, adminUser) = [new User(), new AdminUser()]
assert adminUser.name == null
assert adminUser.city == null
assert adminUser.age == null

copyProperties(user, adminUser)
assert adminUser.name == 'Arturo'
assert adminUser.city == 'Madrid'
assert adminUser.age == 27

你可以随时使用BeanUtils。 - Dave Newton
@DaveNewton 不确定BeanUtils是否可行,因为源和目标是不同的类... - tim_yates
使用AutoClone注释怎么样?http://groovy.codehaus.org/gapi/groovy/transform/AutoClone.html - user3581645
4个回答

34
我认为最好而且清晰的方法是使用InvokerHelper.setProperties()方法。
例子:
import groovy.transform.ToString
import org.codehaus.groovy.runtime.InvokerHelper

@ToString
class User {
    String name = 'Arturo'
    String city = 'Madrid'
    Integer age = 27
}

@ToString
class AdminUser {
    String name
    String city
    Integer age
}

def user = new User()
def adminUser = new AdminUser()

println "before: $user $adminUser"
InvokerHelper.setProperties(adminUser, user.properties)
println "after : $user $adminUser"

输出:

before: User(Arturo, Madrid, 27) AdminUser(null, null, null)
after : User(Arturo, Madrid, 27) AdminUser(Arturo, Madrid, 27)

注意:如果您想要更好的可读性,您可以使用类别。

use(InvokerHelper) {
    adminUser.setProperties(user.properties) 
}

感谢InvokerHelper,我为这个问题苦苦挣扎了一段时间。 - dbrin
我认为这是一个非常好的想法,在DomainUnitTest案例中它也起作用了,但是在服务集成测试中却没有多少警告地失败了!真让人恼火...为什么! - Brent Fisher
好的,事实证明我把它们搞反了,我的测试只是测试了两个是否相等。复制操作将它们都变成了 null! - Brent Fisher

31

我认为你的解决方案相当不错,而且走在了正确的轨道上。至少我觉得它很容易理解。

一个更简洁的解决方案可能是...

def copyProperties(source, target) {
    source.properties.each { key, value ->
        if (target.hasProperty(key) && !(key in ['class', 'metaClass'])) 
            target[key] = value
    }
}

...但它并没有根本上的不同。我正在迭代源属性,以便可以使用这些值来分配到目标对象中 :). 不过,与您原始的解决方案相比,它可能不太健壮,因为我认为如果目标对象定义了getAt(String)方法,它将会出现故障。

如果你想要更高级一些,你可以像这样做:

def copyProperties(source, target) {
    def (sProps, tProps) = [source, target]*.properties*.keySet()
    def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
    commonProps.each { target[it] = source[it] }
}

基本上,它首先计算两个对象之间的公共属性,然后将它们复制。这也有效,但我认为第一个更直观、更易于理解 :)

有时候,简单就是美。


1
对于那些喜欢一行代码的人,我认为像这样应该行得通:[源, 目标]*.properties*.keySet().grep { it != 'class' && it != 'metaClass' }.each { 目标[it] = 源[it] } - Yannick Mauray
可能还需要将目标赋值放在try catch中,以防类型无法强制转换。 - GameSalutes
在Idea中调试时可以工作,但在部署的jar上调用时会抛出异常:“java.util.ArrayList.keySet()方法的签名不适用于参数类型:()值:[]\n可能的解决方案:toSet()、toSet()、set(int, java.lang.Object)、set(int, java.lang.Object)、get(int)、get(int)” - yuranos
当参数具有名为properties的属性或名为getProperties()且没有参数的方法时,此操作将失败。这些情况的答案可以在此处找到:https://dev59.com/Cqbja4cB1Zd3GeqPdkSt#46979194 - Mene

3

另一种方法是这样做:

def copyProperties( source, target ) {
  [source,target]*.getClass().declaredFields*.grep { !it.synthetic }.name.with { a, b ->
    a.intersect( b ).each {
      target."$it" = source."$it"
    }
  }
}

此方法会获取公共属性(不包括合成字段),然后将其分配给目标对象。


你也可以使用这种方法来实现:

def user = new User()

def propCopy( src, clazz ) {
  [src.getClass(), clazz].declaredFields*.grep { !it.synthetic }.name.with { a, b ->
    clazz.newInstance().with { tgt ->
      a.intersect( b ).each {
        tgt[ it ] = src[ it ]
      }
      tgt
    }
  }
}


def admin = propCopy( user, AdminUser )
assert admin.name == 'Arturo'
assert admin.city == 'Madrid'
assert admin.age == 27

所以,您将一个对象传递给该方法来复制属性,并传递要返回的对象的类。然后,该方法创建该类的新实例(假设存在无参数构造函数),设置属性并返回它。

编辑2

假设这些是Groovy类,则可以调用Map构造函数并设置所有常见属性,如下所示:
def propCopy( src, clazz ) {
  [src.getClass(), clazz].declaredFields*.grep { !it.synthetic }.name.with { a, b ->
    clazz.metaClass.invokeConstructor( a.intersect( b ).collectEntries { [ (it):src[ it ] ] } )
  }
}

1
除了使用Map构造函数外,还可以使用MetaClass.setProperties将属性分配为Map到现有实例中:https://dev59.com/N2oy5IYBdhLWcg3wguSS#8507884 - ataylor

1

请注意,BeanUtils.copyProperties 不会进行深拷贝,请参考:https://dev59.com/a2Up5IYBdhLWcg3wEEXd#15591144。 - Lifeweaver

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