Скопируйте свойства класса Groovy - PullRequest
15 голосов
/ 31 января 2012

Я хочу скопировать свойства объекта в другой объект общим способом (если свойство существует для целевого объекта, я копирую его из исходного объекта).

Мой код работает нормальноиспользуя 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

Ответы [ 4 ]

28 голосов
/ 31 января 2012

Я думаю, что ваше решение довольно хорошее и находится на правильном пути.По крайней мере, я нахожу это вполне понятным.

Более краткая версия этого решения может быть ...

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] }
}

По сути, он сначала вычисляет общие свойства между двумя объектами, а затем копирует их.Это также работает, но я думаю, что первый более прост и понятен:)

Иногда меньше - больше.

27 голосов
/ 10 мая 2014

Я думаю, лучший и прозрачный способ заключается в использовании 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) 
}
3 голосов
/ 31 января 2012

Другим способом является:

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

Таким образом, вы передаете методу объект, из которого копируются свойства, и класс возвращаемого объекта.Затем метод создает новый экземпляр этого класса (в предположении конструктора без аргументов), устанавливает свойства и возвращает его.


Edit 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 ] ] } )
  }
}
0 голосов
/ 22 апреля 2015

Spring BeanUtils.copyProperties будет работать, даже если исходные / целевые классы имеют разные типы.http://docs.spring.io/autorepo/docs/spring/3.2.3.RELEASE/javadoc-api/org/springframework/beans/BeanUtils.html

...