将List列表映射到内部case类给出错误Object不带参数
我的列表数据:
val lstEmpAddrRec = List( List("John", "Developer", List("123 Main St", "Chicago")),
List("Kevin", "Architect", List("444 Madrid Ln", "Los Angeles")))
我将列表映射为2个案例类:
case class clsAddr(addr1: String, city: String)
case class clsEmp(name: String, jobDesc: String, addr: clsAddr)
lstEmpAddrRec.map(x => clsEmp(x(0).toString, x(1).toString, clsAddr(x(2)(0), x(2)(1))
)).foreach(println)
我收到以下错误:
error: Object does not take parameters
lstEmpAddrRec.map(x => clsEmp(x(0).toString, x(1).toString, clsAddr(x(2)(0), x(2)(1))
外部对象(clsEmp)映射看起来不错,但内部对象(clsAddr)不接受转换。
实现这一目标的最佳方法是什么?
答案 0 :(得分:3)
这里有很多问题......
首先,虽然我有点迂腐,但你没有内部或外部类。 内部类是定义为另一个外部类的成员,如下所示:
class Outer {
class Inner {
// ...
}
}
这不是你在这里所拥有的。
其次,如果您查看lstEmpAddrRec
的类型,它是List
个异质对象的List
(String
,String
和List[String]
)。因此,其类型为:List[List[java.io.Serializable]]
(因为java.io.Serializable
是String
和List[String]
共有的唯一特征。通常,创建此类对象的列表可能并不是您想要的。
第三,当您将map
应用于lstEmpAddrRec
时,x
将具有List[java.io.Serializable]
类型,该类型的成员与您的案例类所需的参数类型不匹配或允许您使用x(2)(0)
等表达式,因为没有此类方法属于Serializable
列表。
我认为您想要的更像是这样(我已经从Vector
切换到List
,因为.appy(Int)
的{{1}}方法不是&{ #39;特别有效率):
List
此处,case class clsAddr(addr1: String, city: String)
case class clsEmp(name: String, jobDesc: String, addr: clsAddr)
case class Data(name: String, function: String, address: Vector[String])
val lstEmpAddrRec = List(Data("John", "Developer", Vector("123 Main St", "Chicago")),
Data("Kevin", "Architect", Vector("444 Madrid Ln", "Los Angeles")))
val listEmp = lstEmpAddrRec.map {
x =>
clsEmp(x.name, x.function, clsAddr(x.address(0), x.address(1)))
}
是lstEmpAddrRec
,List[Data]
是x
实例,可以让事情变得简单明了。
或者,您可以使用元组而不是Data
案例类,如下所示:
Data
在这种情况下,case class clsAddr(addr1: String, city: String)
case class clsEmp(name: String, jobDesc: String, addr: clsAddr)
val lstEmpAddrRec = List(("John", "Developer", Vector("123 Main St", "Chicago")),
("Kevin", "Architect", Vector("444 Madrid Ln", "Los Angeles")))
val listEmp = lstEmpAddrRec.map {
x =>
clsEmp(x._1, x._2, clsAddr(x._3(0), x._3(1)))
}
是lstEmpAddrRec
,List[(String, String, Vector[String])]
是x
(这是一个元组,如表达式两端的括号所示)
更新:根据@ KiranM的评论添加了以下说明。
问题不在于列表列表。主要问题是您在其中一个列表中有两种不同类型的值。拿这个数据记录:
(String, String, Vector[String])
(第一)列表的成员是:List("John", "Developer", List("123 Main St", "Chicago"))
(String
);另一个"John"
(String
);和"Developer"
(List[String]
)。
那么这个清单的类型是什么?要回答这个问题, Scala 编译器需要考虑成员在类型方面的共同点。那么List["123 Main St", "Chicago"]
和String
有什么共同之处呢?好吧,他们(最终)都来自List[String]
。但是,它们还实现了AnyRef
特征/接口(允许将实例序列化和反序列化到数据流中/从数据流中反序列化)。因此, Scala 编译器会认为这是java.io.Serializable
的列表。即Serializable
。您可以通过打开 Scala REPL 并在其中键入此表达式来验证这一点,如下所示:
List[Serializable]
因此,当您尝试将此记录转换为$ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151).
Type in expressions for evaluation. Or try :help.
scala> List("John", "Developer", List("123 Main St", "Chicago"))
res0: List[java.io.Serializable] = List(John, Developer, List(123 Main St, Chicago))
函数中的clsEmp
实例时,您会在代码中使用map
(推断类型List[Serializable]
)有三名成员并试图按如下方式使用它:
x
所以让我们打破这个。
clsEmp(x(0).toString, x(1).toString, clsAddr(x(2)(0), x(2)(1)))
构造函数的第一个参数是clsEmp
,并且必须具有类型x(0).toString
。 String
是x
,因此List[Serializable]
是列表中的第一个元素,并且(编译器可以告诉它)它的类型x(0)
的值为{{ 1}}。 (虽然Serializable
的实际类型为"John"
,但编译器还不够智能。)然后,您可以通过{{1}将此值转换为x(0)
} 方法。由于String
实际上是String
,因此其.toString
方法只会将值x(0)
作为String
返回。所以我们很好。
请注意,如果您没有添加toString
转换,则会收到参数类型错误,因为您应该将"John"
分配给String
。您可以在 REPL 中对此进行验证,如下所示:
.toString
以完全相同的方式,构造函数的第二个参数Serializable
将String
scala> val x = List("John", "Developer", List("123 Main St", "Chicago"))
x: List[java.io.Serializable] = List(John, Developer, List(123 Main St, Chicago))
scala> x(0)
res0: java.io.Serializable = John
scala> val s: String = x(0)
<console>:12: error: type mismatch;
found : java.io.Serializable
required: String
val s: String = x(0)
^
scala> val s: String = x(0).toString
s: String = John
分配给x(1).toString
成员String
。
我在这里只提一下,如果你知道前两个参数的类型为"Developer"
,那么如果你明确地将值转换为clsEmp
s会更安全,那么你就得到了如果值实际上不是该类型,则为运行时强制转换异常。这是因为字面上一切都有一个有效的jobDesc
方法,它可以掩盖类型错误。使用显式强制转换,代码现在看起来像这样:
String
但我们还没有处理创建String
实例的错误。现在,鸡真的开始回家了......
因此,让我们只关注.toString
构造函数调用:
clsEmp(x(0).asInstanceOf[String], x(1).asInstanceOf[String], clsAddr(x(2)(0), x(2)(1)))
所以,第一个参数必须是clsAddr
,但我们已经通过它clsAddr
。当 Scala 对此进行求值时,它会处理第一组括号,即表达式clsAddr(x(2)(0), x(2)(1))
。到目前为止,这很好,这是一个String
。它现在尝试将第二组括号应用于x(2)(0)
实例。但是x(2)
没有Serializable
方法,范围中也没有任何函数可以隐式地将其转换为具有的函数。同样,您可以在 REPL 中验证这一点:
Serializable
第二个Serializable
构造函数参数出现完全相同的问题。
我们能解决这个问题吗?当然:在应用第二组括号之前,我们可以将.apply(Int)
表达式显式转换为scala> val x = List("John", "Developer", List("123 Main St", "Chicago"))
x: List[java.io.Serializable] = List(John, Developer, List(123 Main St, Chicago))
scala> x(2)
res0: java.io.Serializable = List(123 Main St, Chicago)
scala> x(2)(0)
<console>:13: error: java.io.Serializable does not take parameters
x(2)(0)
^
,如下所示:
clsAddr
您也可以使用 REPL 进行验证:
x(2)
因此,我们可以通过将列表的元素显式转换为适当的类型来使原始代码工作,如以下 Scala REPL 会话所示:
List[String]
好的,我们已经让它发挥作用了。但我认为你会同意它看起来非常难看。通常,只要您必须显式地转换值,代码中就会出现难闻气味。根本原因是您已将基本上不同类型的值(clsAddr(x(2).asInstanceOf[List[String]](0), x(2).asInstanceOf[List[String]](1))
和scala> case class clsAddr(addr1: String, city: String)
defined class clsAddr
scala> val x = List("John", "Developer", List("123 Main St", "Chicago"))
x: List[java.io.Serializable] = List(John, Developer, List(123 Main St, Chicago))
scala> clsAddr(x(2).asInstanceOf[List[String]](0), x(2).asInstanceOf[List[String]](1))
res0: clsAddr = clsAddr(123 Main St,Chicago)
)放入同一scala> case class clsAddr(addr1: String, city: String)
defined class clsAddr
scala> case class clsEmp(name: String, jobDesc: String, addr: clsAddr)
defined class clsEmp
scala> val lstEmpAddrRec = List( List("John", "Developer", List("123 Main St", "Chicago")),
| List("Kevin", "Architect", List("444 Madrid Ln", "Los Angeles")))
lstEmpAddrRec: List[List[java.io.Serializable]] = List(List(John, Developer, List(123 Main St, Chicago)), List(Kevin, Architect, List(444 Madrid Ln, Los Angeles)))
scala> lstEmpAddrRec.map(x => clsEmp(x(0).asInstanceOf[String], x(1).asInstanceOf[String],
| clsAddr(x(2).asInstanceOf[List[String]](0), x(2).asInstanceOf[List[String]](1)))).foreach(println)
clsEmp(John,Developer,clsAddr(123 Main St,Chicago))
clsEmp(Kevin,Architect,clsAddr(444 Madrid Ln,Los Angeles))
。因此,我建议通过使用元组或案例类来更改数据的结构。
您可能考虑的另一种选择是使用平面列表来表示每条记录,如下所示:
String
这里,不需要显式转换,表达式符合您的预期。这是因为List[String]
现在只是常规List
(而scala> case class clsAddr(addr1: String, city: String)
defined class clsAddr
scala> case class clsEmp(name: String, jobDesc: String, addr: clsAddr)
defined class clsEmp
scala> val lstEmpAddrRec = List( List("John", "Developer", "123 Main St", "Chicago"),
| List("Kevin", "Architect", "444 Madrid Ln", "Los Angeles"))
lstEmpAddrRec: List[List[String]] = List(List(John, Developer, 123 Main St, Chicago), List(Kevin, Architect, 444 Madrid Ln, Los Angeles))
scala> lstEmpAddrRec.map(x => clsEmp(x(0), x(1), clsAddr(x(2), x(3)))).foreach(println)
clsEmp(John,Developer,clsAddr(123 Main St,Chicago))
clsEmp(Kevin,Architect,clsAddr(444 Madrid Ln,Los Angeles))
现在是x
)。
关于效果的最后一点说明:如果您要使用List[String]
方法从集合中检索值,请记住,对于lstEmpAddrRec
,它必须处理每个值在列表中寻找索引。相比之下,List[List[String]]
(或.apply(Int)
)被组织为在恒定时间内执行List
。