TL; DR:
如何减少冗余(任何可行的方法都可以帮助您)?
if (personModification.firstName != null) {person.firstName = personModification.firstName}
if (personModification.lastName != null) {person.lastName = personModification.lastName}
if (personModification.job != null) {person.job = personModification.job}
长版:我有一个简单的问题。我有一堂课Person
:
class Person (val firstName: String?,
val lastName: String?,
val job: String?)
我有一个叫PersonModification
的班级:
class PersonModification(val firstName: String?,
val lastName: String?,
val job: String?)
如果Person
属性不是PersonModification
,则任务是用PersonModification
值覆盖任何null
属性值。如果您愿意的话,其背后的业务逻辑是API端点,该端点修改Person
并接受PersonModification
作为参数(但可以更改所有或任何属性,因此我们不希望用空值覆盖有效的旧值)。解决方案如下所示。
if (personModification.firstName != null) {person.firstName = personModification.firstName}
if (personModification.lastName != null) {person.lastName = personModification.lastName}
if (personModification.job != null) {person.job = personModification.job}
有人告诉我这是多余的(我同意)。解决方案伪代码如下所示:
foreach(propName in personProps){
if (personModification["propName"] != null) {person["propName"] = personModification["propName"]}
}
当然,这不是JavaScript,所以并不是那么容易。我的反射解决方案在下面,但是imo,最好具有冗余性而不是在这里进行反射。除冗余之外,我还有哪些其他选择?
反思:
package kotlin.reflect;
class Person (val firstName: String?,
val lastName: String?,
val job: String?)
class PersonModification(val firstName: String?,
val lastName: String?,
val job: String?)
// Reflection - a bad solution. Impossible without it.
//https://stackoverflow.com/questions/35525122/kotlin-data-class-how-to-read-the-value-of-property-if-i-dont-know-its-name-at
inline fun <reified T : Any> Any.getThroughReflection(propertyName: String): T? {
val getterName = "get" + propertyName.capitalize()
return try {
javaClass.getMethod(getterName).invoke(this) as? T
} catch (e: NoSuchMethodException) {
null
}
}
fun main(args: Array<String>) {
var person: Person = Person("Bob","Dylan","Artist")
val personModification: PersonModification = PersonModification("Jane","Smith","Placeholder")
val personClassPropertyNames = listOf("firstName", "lastName", "job")
for(properyName in personClassPropertyNames) {
println(properyName)
val currentValue = person.getThroughReflection<String>(properyName)
val modifiedValue = personModification.getThroughReflection<String>(properyName)
println(currentValue)
if(modifiedValue != null){
//Some packages or imports are missing for "output" and "it"
val property = outputs::class.memberProperties.find { it.name == "firstName" }
if (property is KMutableProperty<*>) {
property.setter.call(person, "123")
}
}
})
}
您可以在此处复制并粘贴以运行它:https://try.kotlinlang.org/
答案 0 :(得分:7)
编写一个5行的助手来做到这一点应该非常简单,该助手甚至支持复制每个匹配的属性或仅复制某些属性。
尽管如果您正在编写Kotlin代码并大量利用数据类和val
(不可变属性),它可能没有用。检查一下:
fun <T : Any, R : Any> T.copyPropsFrom(fromObject: R, skipNulls: Boolean = true, vararg props: KProperty<*>) {
// only consider mutable properties
val mutableProps = this::class.memberProperties.filterIsInstance<KMutableProperty<*>>()
// if source list is provided use that otherwise use all available properties
val sourceProps = if (props.isEmpty()) fromObject::class.memberProperties else props.toList()
// copy all matching
mutableProps.forEach { targetProp ->
sourceProps.find {
// make sure properties have same name and compatible types
it.name == targetProp.name && targetProp.returnType.isSupertypeOf(it.returnType)
}?.let { matchingProp ->
val copyValue = matchingProp.getter.call(fromObject);
if (!skipNulls || (skipNulls && copyValue != null)) {
targetProp.setter.call(this, copyValue)
}
}
}
}
此方法使用反射,但是使用非常轻巧的Kotlin反射。我还没有计时,但是它应该以与手动复制属性几乎相同的速度运行。
此外,它使用KProperty
而不是字符串来定义属性的子集(如果您不希望复制所有属性),因此它具有完整的重构支持,因此,如果在类上重命名属性,您将获得不必寻找要重命名的字符串引用。
默认情况下它将跳过空值,或者您可以将skipNulls
参数切换为false(默认为true)。
现在提供2个课程:
data class DataOne(val propA: String, val propB: String)
data class DataTwo(var propA: String = "", var propB: String = "")
您可以执行以下操作:
var data2 = DataTwo()
var data1 = DataOne("a", "b")
println("Before")
println(data1)
println(data2)
// this copies all matching properties
data2.copyPropsFrom(data1)
println("After")
println(data1)
println(data2)
data2 = DataTwo()
data1 = DataOne("a", "b")
println("Before")
println(data1)
println(data2)
// this copies only matching properties from the provided list
// with complete refactoring and completion support
data2.copyPropsFrom(data1, DataOne::propA)
println("After")
println(data1)
println(data2)
输出将是:
Before
DataOne(propA=a, propB=b)
DataTwo(propA=, propB=)
After
DataOne(propA=a, propB=b)
DataTwo(propA=a, propB=b)
Before
DataOne(propA=a, propB=b)
DataTwo(propA=, propB=)
After
DataOne(propA=a, propB=b)
DataTwo(propA=a, propB=)
答案 1 :(得分:2)
使用委托的属性可以解决此问题,而无需进行反思。参见:https://kotlinlang.org/docs/reference/delegated-properties.html
class Person(firstName: String?,
lastName: String?,
job: String?) {
val map = mutableMapOf<String, Any?>()
var firstName: String? by map
var lastName: String? by map
var job: String? by map
init {
this.firstName = firstName
this.lastName = lastName
this.job = job
}
}
class PersonModification(firstName: String?,
lastName: String?,
job: String?) {
val map = mutableMapOf<String, Any?>()
var firstName: String? by map
var lastName: String? by map
var job: String? by map
init {
this.firstName = firstName
this.lastName = lastName
this.job = job
}
}
fun main(args: Array<String>) {
val person = Person("Bob", "Dylan", "Artist")
val personModification1 = PersonModification("Jane", "Smith", "Placeholder")
val personModification2 = PersonModification(null, "Mueller", null)
println("Person: firstName: ${person.firstName}, lastName: ${person.lastName}, job: ${person.job}")
personModification1.map.entries.forEach { entry -> if (entry.value != null) person.map[entry.key] = entry.value }
println("Person: firstName: ${person.firstName}, lastName: ${person.lastName}, job: ${person.job}")
personModification2.map.entries.forEach { entry -> if (entry.value != null) person.map[entry.key] = entry.value }
println("Person: firstName: ${person.firstName}, lastName: ${person.lastName}, job: ${person.job}")
}
答案 2 :(得分:2)
您可以为此创建一个不错的 trait ,您将可以申请可能具有的任何修改类:
interface Updatable<T : Any> {
fun updateFrom(model: T) {
model::class.java.declaredFields.forEach { modelField ->
this::class.java.declaredFields
.filter { it.name == modelField.name && it.type == modelField.type }
.forEach { field ->
field.isAccessible = true
modelField.isAccessible = true
modelField.get(model)?.let { value ->
field.set(this, value)
}
}
}
}
}
用法:
data class Person(val firstName: String?,
val lastName: String?,
val job: String?) : Updatable<PersonModification>
data class PersonModification(val firstName: String?,
val lastName: String?,
val job: String?)
然后您可以尝试一下:
fun main(args: Array<String>) {
val person = Person(null, null, null)
val mod0 = PersonModification("John", null, null)
val mod1 = PersonModification(null, "Doe", null)
val mod2 = PersonModification(null, null, "Unemployed")
person.updateFrom(mod0)
println(person)
person.updateFrom(mod1)
println(person)
person.updateFrom(mod2)
println(person)
}
这将打印:
Person(firstName=John, lastName=null, job=null)
Person(firstName=John, lastName=Doe, job=null)
Person(firstName=John, lastName=Doe, job=Unemployed)
答案 3 :(得分:2)
您还可以使用许多模型映射实用程序之一,例如http://www.baeldung.com/java-performance-mapping-frameworks中列出的模型实用程序(至少您已经看到了有关不同类型的模型映射程序的一些性能基准)。
请注意,如果您没有彻底测试它,我真的不建议您编写自己的映射实用程序。自定义映射实用程序不断增长的例子已经出现,后来由于不考虑某些极端情况而导致奇怪的行为。
!= null
否则,如果您不太懒惰,我宁愿推荐类似的东西:
personModification.firstName?.also { person.firstName = it }
它不需要任何反射,很简单并且仍然可读...至少在某种程度上;-)
我想到的另一件事是delegated properties(以某种方式与您的Javascript方法匹配)(仅当支持的Map
是适合您的模型时,我才建议这样做;实际上,我在下面显示的是我无法真正推荐使用HashMap的委托人地图,但这是获得Javascript外观的一种非常简单而有用的方法;我不推荐它的原因是:Person
a {{1 }}?;-))。
Map
然后class Person() : MutableMap<String, String?> by HashMap() { // alternatively use class Person(val personProps : MutableMap<String, String?> = HashMap()) instead and replace `this` below with personProps
var firstName by this
var lastName by this
var job by this
constructor(firstName : String?, lastName : String?, job : String?) : this() {
this.firstName = firstName
this.lastName = lastName
this.job = job
}
}
类看起来基本相同。然后应用映射将如下所示:
PersonModification
如果您不需要二级构造函数就更好了,则该类看起来几乎和以前一样,并且可以像以前一样设置和获取属性。
考虑一下,您实际上并不需要辅助构造函数,因为您仍然可以使用val person = Person("first", "last", null)
val personMod = PersonModification("new first", null, "new job")
personMod.filterValues { it != null }
.forEach { key, value -> person[key] = value } // here the benefit of extending the Map becomes visible: person[key] instead of person.personProps[key], but then again: person.personProps[key] is cleaner
,然后只需添加您感兴趣的变量(几乎称为命名参数)即可。然后,该类将类似于:
apply
并将其实例化如下:
class PersonModification : MutableMap<String, String?> by HashMap() { // or again simply: class PersonModification(props : MutableMap<String, String?> = HashMap()) and replacing `this` with props below
var firstName by this
var lastName by this
var job by this
}
映射仍然相同。
答案 4 :(得分:0)
已经有很多人提供了他们的解决方案。但我想再提供一个:
jackson
中有一个有趣的功能,您可以尝试合并json。因此,您可以将src对象与反序列化版本的PersonModification
使用它,可以执行以下操作:
class ModificationTest {
@Test
fun test() {
val objectMapper = jacksonObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
fun Person.merge(personModification: PersonModification): Person = run {
val temp = objectMapper.writeValueAsString(personModification)
objectMapper.readerForUpdating(this).readValue(temp)
}
val simplePerson = Person("firstName", "lastName", "job")
val modification = PersonModification(firstName = "one_modified")
val modification2 = PersonModification(lastName = "lastName_modified")
val personAfterModification1: Person = simplePerson.merge(modification)
//Person(firstName=one_modified, lastName=lastName, job=job)
println(personAfterModification1)
val personAfterModification2: Person = personAfterModification1.merge(modification2)
//Person(firstName=one_modified, lastName=lastName_modified, job=job)
println(personAfterModification2)
}
}
希望这对您有帮助!
答案 5 :(得分:0)
为Person创建扩展功能:
fun Person.modify(pm: PersonModification) {
pm.firstName?.let { firstName = it }
pm.lastName?.let { lastName = it }
pm.job?.let { job = it }
}
fun Person.println() {
println("firstName=$firstName, lastName=$lastName, job=$job")
}
并像这样使用它:
fun main(args: Array <String> ) {
val p = Person("Nick", "Doe", "Cartoonist")
print("Person before: ")
p.println()
val pm = PersonModification("Maria", null, "Actress")
p.modify(pm)
print("Person after: ")
p.println()
}
或选择以下选项之一:
fun Person.println() {
println("firstName=$firstName, lastName=$lastName, job=$job")
}
fun main(args: Array <String> ) {
val p = Person("Nick", "Doe", "Cartoonist")
print("Person before: ")
p.println()
val pm = PersonModification("John", null, null)
pm.firstName?.run { p.firstName = this }.also { pm.lastName?.run { p.lastName = this } }.also { pm.job?.run { p.job = this } }
// or
pm.firstName?.also { p.firstName = it }.also { pm.lastName?.also { p.lastName = it } }.also { pm.job?.also { p.job = it } }
// or
with (pm) {
firstName?.run { p.firstName = this }
lastName?.run { p.lastName= this }
job?.run { p.job= this }
}
print("Person after: ")
p.println()
}
答案 6 :(得分:0)
这没什么花哨的,但它掩盖了从外部世界对Person
进行突变的复杂性。
class Person(
var firstName: String?,
var lastName: String?,
var job: String?
) {
fun modify(p: PersonModification){
p.firstName?.let { firstName = it }
p.lastName?.let { lastName = it }
p.job?.let { job = it }
}
}
class PersonModification(/* ... */)