这个答案https://stackoverflow.com/a/41717310/280393解释了如何创建一个包含引用的不可变对象列表;但是,所提供的解决方案需要事先知道对象。 如何在按需创建对象时实现此目的?
case class PersonA(id: Int, name: String, friends: Set[Int])
val john = PersonA(0, "john", Set(1,2))
val maria = PersonA(1, "maria", Set(0))
val georges = PersonA(2, "georges", Set(1))
val peopleA = Set(john, maria, georges)
case class PersonB(id: Int, name: String, friends: Set[PersonB])
// case class PersonB(id: Int, name: String, friends: () => Set[PersonB])
def convert(peopleA: Set[PersonA]): Set[PersonB] = ???
val peopleB = convert(peopleA)
println(peopleB)
println(peopleB.toList.map(_.friends.size))
peopleB.toList.map {
case PersonB(id, name, friends) => friends.size
}.foreach(println)
那么,如果不修改case class PersonA
和val peopleA
的实施,如何实施convert
?
假设两个PersonB
实例相等,如果id
相等,
一个解决方案是这样的:
class PersonB(val id: Int, val name: String) {
var friends0: Set[PersonB] = _
def setFriends(friends: Set[PersonB]) {
require(friends0 == null)
friends0 = friends
}
def friends: Set[PersonB] = {
require(friends0 != null)
friends0
}
override def equals(that: Any): Boolean = that match {
case t: PersonB => t.id == id
case _ => false
}
override def hashCode(): Int = id.hashCode
override def toString = s"PersonB($id, $name, List(${friends.map(_.id).mkString(", ")}))"
}
object PersonB {
def apply(id: Int, name: String) = new PersonB(id, name)
def apply(id: Int, name: String, friends: Set[PersonB]): PersonB = {
val p = new PersonB(id, name)
p.setFriends(friends)
p
}
def unapply(p: PersonB): Option[(Int, String, Set[PersonB])] =
Some((p.id, p.name, p.friends))
}
def convert(peopleA: Set[PersonA]): Set[PersonB] = {
val peopleB = peopleA.map(p => new PersonB(p.id, p.name))
val peopleBMap = peopleB.map(p => (p.id, p)).toMap
peopleA.foreach(p =>
peopleBMap(p.id).setFriends(p.friends.map(peopleBMap))
)
peopleB
}
有更简单的方法吗?
Udate 基于@sjrd回答的解决方案:
class PersonB(val id: Int, val name: String, friends0: => Set[PersonB]) {
lazy val friends: Set[PersonB] = friends0
override def equals(that: Any): Boolean = that match {
case t: PersonB => t.id == id
case _ => false
}
override def hashCode(): Int = id.hashCode
override def toString = s"PersonB($id, $name, List(${friends.map(_.id).mkString(", ")}))"
}
object PersonB {
def apply(id: Int, name: String, friends: => Set[PersonB]): PersonB =
new PersonB(id, name, friends)
def unapply(p: PersonB): Option[(Int, String, Set[PersonB])] =
Some((p.id, p.name, p.friends))
}
def convert(peopleA: Set[PersonA]): Set[PersonB] = {
lazy val peopleB: Map[Int, PersonB] =
(for (PersonA(id, name, friendsIDs) <- peopleA)
yield (id, PersonB(id, name, friendsIDs.map(peopleB)))).toMap
peopleB.values.toSet
}
答案 0 :(得分:2)
如果要创建不可变数据,则需要使用DAG /有向非循环图来创建对象的创建顺序。
我认为你不能这样做,因为前两个记录只有一个周期:
val john = PersonB(0, "john", Set(maria, ...))
val maria = PersonB(1, "maria", Set(john))
约翰在约翰身上依赖玛丽亚和玛丽亚。
因此,在约翰之后创造玛丽亚和玛丽亚之后,必须创造约翰。
所以你可能不得不与你的不可变数据结构妥协,而不是使用case类,但可能是一次赋值。
答案 1 :(得分:0)
使用PersonB
:
case class PersonB(id: Int, name: String, friends: () => Set[PersonB])
你可以写:
def convert(peopleA: Set[PersonA]): Set[PersonB] = {
lazy val peopleB: Map[Int, PersonB] =
(for (PersonA(id, name, friendsIDs) <- peopleA)
yield PersonB(id, name, () => friendsIDs.map(peopleB)).toMap
peopleB.values.toSet
}
未经测试,但与此相关的内容应该有效。