我目前正在教授“ 应用文本分析”课程,并且正在使用Scala作为该课程中教授和使用的编程语言。 我没有创造更多的教程,而是想着当他回答学生问题时,我会从Brian Dunning的剧本中摘录他的Skeptoid播客(强烈推荐)。 因此,根据目前为止的阅读和作业,我让课程中的学生提交了有关Scala的问题。 这篇文章涵盖了其中一半以上的内容,其余的将在后续文章中介绍。
我从一些更基本的问题开始,然后这些问题和/或答案逐渐进入更多的中级主题。 欢迎改善任何答案的建议和意见!
基本问题
问:关于变量的寻址部分:要寻址列表的各个部分,项目的编号为(列表0、1,2等)。也就是说,第一个元素称为“ 0”。 对于数组和映射来说似乎是相同的,但是对于元组来说却不一样,要获得元组的第一个元素,我需要使用元组_1。 这是为什么?
答:这只是一个惯例问题—元组在Haskell等其他语言中使用了基于1的索引,而且Scala似乎采用了相同的惯例/传统。 看到: 基于http://stackoverflow.com/questions/6241464/why-are-the-indexes-of-scala-tuples-1-based
问:似乎Scala无法将“ b”边界字符识别为正则表达式。 Scala中有类似的东西吗?
答:Scala确实可以识别边界字符。 例如,以下REPL会话声明了一个正则表达式,该正则表达式查找带有边界的“ the”,并成功检索了示例句子中“ the”的三个标记。
scala> val TheRE = """\bthe\b""".r
TheRE: scala.util.matching.Regex = \bthe\b
scala> val sentence = "She think the man is a stick-in-the-mud, but the man disagrees."
sentence: java.lang.String = She think the man is a stick-in-the-mud, but the man disagrees.
scala> TheRE.findAllIn(sentence).toList
res1: List[String] = List(the, the, the)
问:为什么“ split”方法对args不起作用? 示例:val arg = args.split(“”)。 Args是正确的字符串,所以split应该起作用吗?
答: args变量是一个数组,因此split对它们不起作用。 实际上,数组已被拆分。
问: foo.mapValues(x => x.length)和foo.map(x => x.length)之间的主要区别是什么? 有些地方一个有效,而一个无效。
答: map函数适用于所有序列类型,包括Seqs和Maps(请注意,Map可以看作是Tuple2s的序列)。 但是, mapValues函数仅适用于地图。 它本质上是一种便利功能。 例如,让我们从一个简单的从Ints到Ints的Map开始。
scala> val foo = List((1,2),(3,4)).toMap
foo: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 4)
现在考虑将Map中的每个值加2的任务。 可以使用以下map函数完成此操作。
scala> foo.map { case(key,value) => (key,value+2) }
res5: scala.collection.immutable.Map[Int,Int] = Map(1 -> 4, 3 -> 6)
因此,映射函数遍历键/值对。 我们需要将它们都匹配,然后输出键和更改后的值以创建新的Map。 mapValues函数使此操作变得容易得多 。
scala> foo.mapValues(2+)
res6: scala.collection.immutable.Map[Int,Int] = Map(1 -> 4, 3 -> 6)
回到有关使用mapValues或map计算长度的问题-然后,只是要转换哪些值的问题,如以下示例所示。
scala> val sentence = "here is a sentence with some words".split(" ").toList
sentence: List[java.lang.String] = List(here, is, a, sentence, with, some, words)
scala> sentence.map(_.length)
res7: List[Int] = List(4, 2, 1, 8, 4, 4, 5)
scala> val firstCharTokens = sentence.groupBy(x=>x(0))
firstCharTokens: scala.collection.immutable.Map[Char,List[java.lang.String]] = Map(s -> List(sentence, some), a -> List(a), i -> List(is), h -> List(here), w -> List(with, words))
scala> firstCharTokens.mapValues(_.length)
res9: scala.collection.immutable.Map[Char,Int] = Map(s -> 2, a -> 1, i -> 1, h -> 1, w -> 2)
问:是否有任何功能可以将列表分为两个列表,且元素位于原始列表的交替位置? 例如,
主列表=(1,2,3,4,5,6)
清单1 =(1,3,5) 清单2 =(2,4,6) 答:给定您提供的确切主列表,您可以使用分区函数并使用模运算来查看该值是否能被2整除。
scala> val mainList = List(1,2,3,4,5,6)
mainList: List[Int] = List(1, 2, 3, 4, 5, 6)
scala> mainList.partition(_ % 2 == 0)
res0: (List[Int], List[Int]) = (List(2, 4, 6),List(1, 3, 5))
因此,分区返回一对列表。 第一个具有与条件匹配的所有元素,第二个具有与条件不匹配的所有元素。
当然,这对于具有字符串或没有顺序的Ints的List通常不起作用。但是,List的索引总是以这种方式表现良好,因此我们只需要通过压缩每个元素的索引,然后根据索引进行分区,需要做更多的工作。
scala> val unordered = List("b","2","a","4","z","8")
unordered: List[java.lang.String] = List(b, 2, a, 4, z, 8)
scala> unordered.zipWithIndex
res1: List[(java.lang.String, Int)] = List((b,0), (2,1), (a,2), (4,3), (z,4), (8,5))
scala> val (evens, odds) = unordered.zipWithIndex.partition(_._2 % 2 == 0)
evens: List[(java.lang.String, Int)] = List((b,0), (a,2), (z,4))
odds: List[(java.lang.String, Int)] = List((2,1), (4,3), (8,5))
scala> evens.map(_._1)
res2: List[java.lang.String] = List(b, a, z)
scala> odds.map(_._1)
res3: List[java.lang.String] = List(2, 4, 8)
基于此,您当然可以编写一个针对任意列表执行此操作的函数。
问:如何将列表转换为向量,反之亦然?
A.使用toIndexSeq和toList 。
scala> val foo = List(1,2,3,4)
foo: List[Int] = List(1, 2, 3, 4)
scala> val bar = foo.toIndexedSeq
bar: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4)
scala> val baz = bar.toList
baz: List[Int] = List(1, 2, 3, 4)
scala> foo == baz
res0: Boolean = true
问:向量相对于列表的优势是恒定的时间查找。 在向量上使用列表的好处是什么?
答:List在序列的开头(前端)操作要快一些,因此,如果您要做的是遍历(例如,在映射时按顺序访问每个元素),那么List就足够了,可能会更多高效。 对于case语句,它们还具有一些不错的模式匹配行为。 但是,通常的智慧似乎是您应该默认使用Vector。 请参阅Daniel Spiewak关于Stackoverflow的一个不错的答案: http://stackoverflow.com/questions/6928327/when-should-i-choose-vector-in-scala
问:使用分割字符串时,holmes.split(“ \\ s”)– \ n和\ t只需要一个'\'即可识别其特殊功能,但是为什么空白字符需要两个'\'呢?
答:这是因为\ n和\ t实际上意味着字符串中的某些内容。
scala> println("Here is a line with a tab\tor\ttwo, followed by\na new line.")
Here is a line with a tab or two, followed by
a new line.
scala> println("This will break\s.")
<console>:1: error: invalid escape character
println("This will break\s.")
因此,您将提供一个String参数进行拆分,并使用该参数构造一个正则表达式。 鉴于\ s不是字符串字符,而是正则表达式元字符,您需要对其进行转义。 您当然可以使用split(“”” \ s“””) ,尽管在这种情况下并不能更好。
问:我长期从事C ++和Java编程。 因此,我不知不觉地将分号放在行尾。 似乎Scala的标准编码风格不建议使用分号。 但是,在上一堂课中,我看到有些情况下需要分号。 分号在Scala中失去作用有什么具体原因吗?
答:主要原因是提高可读性,因为在编辑器中编写标准代码时很少需要使用分号(与REPL中的一个衬里相对)。 但是,当您想在一行中执行某项操作(例如处理多个案例)时,则需要使用分号。
scala> val foo = List("a",1,"b",2)
foo: List[Any] = List(a, 1, b, 2)
scala> foo.map { case(x: String) => x; case(x: Int) => x.toString }
res5: List[String] = List(a, 1, b, 2)
但是,总的来说,最好将这些案例分解成任何实际代码中的多行。
问:没有办法在类似地图的方法中使用_来组成由对组成的集合? 例如, List((1,1),(2,2))。map(e => e._1 + e._2)有效,但是List((1,1),(2,2))。map (_._ 1 + _._ 2)不起作用。
答:_保持明确的范围超出了它的第一次调用,因此您只能使用一次。 无论如何,最好使用一个case语句,以使该对对的成员清楚。
scala> List((1,1),(2,2)).map { case(num1, num2) => num1+num2 }
res6: List[Int] = List(2, 4)
问:我不确定“ =>”和“->”之间的确切含义以及区别。 它们看起来都像是“将X应用于Y”之类的东西,我看到它们都是在特定的上下文中使用的,但是其背后的逻辑是什么?
答:->的使用只是构造一个Tuple2,如以下代码片段所示。
scala> val foo = (1,2)
foo: (Int, Int) = (1,2)
scala> val bar = 1->2
bar: (Int, Int) = (1,2)
scala> foo == bar
res11: Boolean = true
首先,它是语法糖,它为创建aa Map的元素提供了直观的符号。 比较以下两种声明同一Map的方式。
scala> Map(("a",1),("b",2))
res9: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2)
scala> Map("a"->1,"b"->2)
res10: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2)
第二个对我来说似乎更易读。
=>的使用表示您正在定义一个函数。 基本形式为ARGUMENTS => RESULT。
scala> val addOne = (x: Int) => x+1
addOne: Int => Int = <function1>
scala> addOne(2)
res7: Int = 3
scala> val addTwoNumbers = (num1: Int, num2: Int) => num1+num2
addTwoNumbers: (Int, Int) => Int = <function2>
scala> addTwoNumbers(3,5)
res8: Int = 8
通常,您可以使用它在将匿名函数定义为map , filter等函数的参数时。
问:在RegExes中,是否有更方便的方式将元音表示为[AEIOUaeiou],将辅音表示为[BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz]?
答:您可以在定义正则表达式时使用字符串,因此您可以为元音设置一个变量,为辅音设置一个变量。
scala> val vowel = "[AEIOUaeiou]"
vowel: java.lang.String = [AEIOUaeiou]
scala> val consonant = "[BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz]"
consonant: java.lang.String = [BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz]
scala> val MyRE = ("("+vowel+")("+consonant+")("+vowel+")").r
MyRE: scala.util.matching.Regex = ([AEIOUaeiou])([BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz])([AEIOUaeiou])
scala> val MyRE(x,y,z) = "aJE"
x: String = a
y: String = J
z: String = E
问:RegExes中的“ \ b”标记边界,对吗? 因此,它还会捕获“-”。 但是,如果我只有一个字符串“ sdnfeorgn”,那么它就不能捕捉到它的界限,对吗? 如果是这样,为什么不呢?
答:因为该字符串没有边界!
中级问题
问:flatMap函数获取列表列表并将其合并为单个列表。 但是在示例中:
scala> (1 to 10).toList.map(x=>squareOddNumber(x))
res16: List[Option[Int]] = List(Some(1), None, Some(9), None, Some(25), None, Some(49), None, Some(81), None)
scala> (1 to 10).toList.flatMap(x=>squareOddNumber(x))
res17: List[Int] = List(1, 9, 25, 49, 81)
这里不是列表列表,而是列表。 在这种情况下,它期望列表为选项列表。
我尝试使用仅返回数字或无的函数运行代码。 显示错误。 因此,有没有方法可以使用不带选项列表和仅列表的平面地图。 例如, List(1,None,9,None,25)应该作为List(1,9,25)返回 。
答:不能,因为List(1,None,9,9,None,25)会将Options与Ints混合使用,因此无法使用。
scala> val mixedup = List(1, None, 9, None, 25)
mixedup: List[Any] = List(1, None, 9, None, 25)
因此,您应该让函数返回Option ,这意味着返回Somes或Nones 。 然后flatMap会愉快地工作。
对选项的一种思考方式是,它们就像具有零个或一个元素的列表一样,如下面的代码片段中的相似之处所示。
scala> val foo = List(List(1),Nil,List(3),List(6),Nil)
foo: List[List[Int]] = List(List(1), List(), List(3), List(6), List())
scala> foo.flatten
res12: List[Int] = List(1, 3, 6)
scala> val bar = List(Option(1),None,Option(3),Option(6),None)
bar: List[Option[Int]] = List(Some(1), None, Some(3), Some(6), None)
scala> bar.flatten
res13: List[Int] = List(1, 3, 6)
问:scala是否具有通用模板(例如C ++,Java)? 例如。 在C ++中,我们可以使用vector <int>,vector <string>等。在scala中可以吗? 如果是这样,怎么办?
答:是的,每个集合类型都已参数化。 请注意,以下每个变量都是通过其初始化元素的类型进行参数化的。
scala> val foo = List(1,2,3)
foo: List[Int] = List(1, 2, 3)
scala> val bar = List("a","b","c")
bar: List[java.lang.String] = List(a, b, c)
scala> val baz = List(true, false, true)
baz: List[Boolean] = List(true, false, true)
您可以直接创建自己的参数化类。
scala> class Flexible[T] (val data: T)
defined class Flexible
scala> val foo = new Flexible(1)
foo: Flexible[Int] = Flexible@7cd0570e
scala> val bar = new Flexible("a")
bar: Flexible[java.lang.String] = Flexible@31b6956f
scala> val baz = new Flexible(true)
baz: Flexible[Boolean] = Flexible@5b58539f
scala> foo.data
res0: Int = 1
scala> bar.data
res1: java.lang.String = a
scala> baz.data
res2: Boolean = true
问:我们如何轻松地创建,初始化和使用多维数组(和字典)?
答:使用Array对象的fill函数创建它们。
scala> Array.fill(2)(1.0)
res8: Array[Double] = Array(1.0, 1.0)
scala> Array.fill(2,3)(1.0)
res9: Array[Array[Double]] = Array(Array(1.0, 1.0, 1.0), Array(1.0, 1.0, 1.0))
scala> Array.fill(2,3,2)(1.0)
res10: Array[Array[Array[Double]]] = Array(Array(Array(1.0, 1.0), Array(1.0, 1.0), Array(1.0, 1.0)), Array(Array(1.0, 1.0), Array(1.0, 1.0), Array(1.0, 1.0)))
一旦拥有了这些,就可以照常遍历它们。
scala> val my2d = Array.fill(2,3)(1.0)
my2d: Array[Array[Double]] = Array(Array(1.0, 1.0, 1.0), Array(1.0, 1.0, 1.0))
scala> my2d.map(row => row.map(x=>x+1))
res11: Array[Array[Double]] = Array(Array(2.0, 2.0, 2.0), Array(2.0, 2.0, 2.0))
对于字典(地图),可以使用可变的HashMaps创建一个空的Map,然后向其中添加元素。 为此,请参阅此博客文章:
http://bcomposes.wordpress.com/2011/09/19/first-steps-in-scala-for-beginning-programmers-part-8/
问: apply函数是否类似于C ++,Java中的构造函数? 应用功能将在哪里实际使用? 是否用于初始化属性值?
答:不, apply函数与其他任何函数一样,不同之处在于它允许您在不写“ apply”的情况下调用它。 考虑以下课程。
class AddX (x: Int) {
def apply(y: Int) = x+y
override def toString = "My number is " + x
}
这是我们如何使用它。
scala> val add1 = new AddX(1)
add1: AddX = My number is 1
scala> add1(4)
res0: Int = 5
scala> add1.apply(4)
res1: Int = 5
scala> add1.toString
res2: java.lang.String = My number is 1
因此, apply方法只是(非常方便)语法糖,它允许您将一个函数指定为所设计类的基础(实际上,您可以具有多个apply方法,只要每个方法都有唯一的参数列表)。 例如,对于列表, apply方法返回提供的索引处的值,对于Maps,它返回与给定键关联的值。
scala> val foo = List(1,2,3)
foo: List[Int] = List(1, 2, 3)
scala> foo(2)
res3: Int = 3
scala> foo.apply(2)
res4: Int = 3
scala> val bar = Map(1->2,3->4)
bar: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 3 -> 4)
scala> bar(1)
res5: Int = 2
scala> bar.apply(1)
res6: Int = 2
问:在SBT教程中,您将“节点”和“值”作为案例类进行讨论。 案例类的替代方法是什么?
A.普通班。 案例类是特例。 他们为您做两件事(甚至更多)。 首先,您不必使用“新”来创建新对象。 考虑以下否则相同的类。
scala> class NotACaseClass (val data: Int)
defined class NotACaseClass
scala> case class IsACaseClass (val data: Int)
defined class IsACaseClass
scala> val foo = new NotACaseClass(4)
foo: NotACaseClass = NotACaseClass@a5c0f8f
scala> val bar = IsACaseClass(4)
bar: IsACaseClass = IsACaseClass(4)
这看起来似乎有点小事,但是它可以显着提高代码的可读性。 例如,如果您必须一直使用“ new”,则考虑在Lists中的Lists中的Lists中创建Lists。 对于用于构建树的Node和Value绝对是这样。
案例类还支持匹配,如下所示。
scala> val IsACaseClass(x) = bar
x: Int = 4
普通班不能做到这一点。
scala> val NotACaseClass(x) = foo
<console>:13: error: not found: value NotACaseClass
val NotACaseClass(x) = foo
^
<console>:13: error: recursive value x needs type
val NotACaseClass(x) = foo
^
如果将案例类混合到列表中并在其上进行映射,则可以将其与其他类(如列表和整数)进行匹配。 考虑以下异构列表。
scala> val stuff = List(IsACaseClass(3), List(2,3), IsACaseClass(5), 4)
stuff: List[Any] = List(IsACaseClass(3), List(2, 3), IsACaseClass(5), 4)
通过匹配每个元素的类型,我们可以将其转换为整数列表。
scala> stuff.map { case List(x,y) => x; case IsACaseClass(x) => x; case x: Int => x }
<console>:13: warning: match is not exhaustive!
missing combination * Nil * *
stuff.map { case List(x,y) => x; case IsACaseClass(x) => x; case x: Int => x }
^
warning: there were 1 unchecked warnings; re-run with -unchecked for details
res10: List[Any] = List(3, 2, 5, 4)
如果您不想在REPL中看到警告,请为不匹配的情况添加一个案例,该案例会引发MatchError。
scala> stuff.map { case List(x,y) => x; case IsACaseClass(x) => x; case x: Int => x; case _ => throw new MatchError }
warning: there were 1 unchecked warnings; re-run with -unchecked for details
res13: List[Any] = List(3, 2, 5, 4)
更好的是,返回Options(对于不匹配的情况,请使用None),然后返回flatMapping。
scala> stuff.flatMap { case List(x,y) => Some(x); case IsACaseClass(x) => Some(x); case x: Int => Some(x); case _ => None }
warning: there were 1 unchecked warnings; re-run with -unchecked for details
res14: List[Any] = List(3, 2, 5, 4)
问:在C ++中,默认访问说明符是private; 在Java中,需要为每个类成员指定private或public,而在Scala中,一个类的默认访问说明符是public。 当该类的目的之一是数据隐藏时,其背后的设计动机是什么?
答:原因是Scala具有比Java更完善的访问规范方案,该规范使Java成为公众的理性选择。 请参阅此处的讨论: http://stackoverflow.com/questions/4656698/default-public-access-in-scala 另一个关键方面是,Scala的总体重点是使用不可变数据结构,因此,如果以这种方式设计对象,则不会有人更改对象的内部状态。 反过来,这消除了在Java程序中繁衍生息的荒谬的getter和setter方法。 有关更多讨论,请参见“为什么getter和setter是邪恶的”: http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html 在使用Scala进行编程之后,在Java代码中很常见的整个getter / setter事情值得一试。 通常,在仅对象本身需要它们时,使用private [this]作为方法和变量的修饰符仍然是一个好主意。
问:我们如何在Scala中定义重载的构造函数?
问:本教程介绍的Scala中定义类的方式似乎只有一个构造函数。 有什么办法可以提供像Java这样的多个构造函数? 答:您可以使用此声明添加其他构造函数。
class SimpleTriple (x: Int, y: Int, z: String) {
def this (x: Int, z: String) = this(x,0,z)
def this (x: Int, y: Int) = this(x,y,"a")
override def toString = x + ":" + y + ":" + z
}
scala> val foo = new SimpleTriple(1,2,"hello")
foo: SimpleTriple = 1:2:hello
scala> val bar = new SimpleTriple(1,"goodbye")
bar: SimpleTriple = 1:0:goodbye
scala> val baz = new SimpleTriple(1,3)
baz: SimpleTriple = 1:3:a
请注意,您必须为该类的每个参数提供一个初始值。 这与Java相反,Java允许您保留一些未初始化的字段(这会导致讨厌的错误和不良的设计)。
请注意,您还可以为参数提供默认值。
class SimpleTripleWithDefaults (x: Int, y: Int = 0, z: String = "a") {
override def toString = x + ":" + y + ":" + z
}
scala> val foo = new SimpleTripleWithDefaults(1)
foo: SimpleTripleWithDefaults = 1:0:a
scala> val bar = new SimpleTripleWithDefaults(1,2)
bar: SimpleTripleWithDefaults = 1:2:a
但是,在指定最后一个参数时不能省略中间参数。
scala> val foo = new SimpleTripleWithDefaults(1,"xyz")
<console>:12: error: type mismatch;
found : java.lang.String("xyz")
required: Int
Error occurred in an application involving default arguments.
val foo = new SimpleTripleWithDefaults(1,"xyz")
^
但是,如果您想执行此操作,则可以在初始化中命名参数。
scala> val foo = new SimpleTripleWithDefaults(1,z="xyz")
foo: SimpleTripleWithDefaults = 1:0:xyz
这样,您就可以完全自由地更改参数。
scala> val foo = new SimpleTripleWithDefaults(z="xyz",x=42,y=3)
foo: SimpleTripleWithDefaults = 42:3:xyz
问:关于类和特征之间的区别,我仍然不清楚。 我想我看到了概念上的区别,但是我真的不明白功能上的区别是什么-创建“特征”与创建一个可能具有较少关联方法的类有何不同?
答:是的,它们是不同的。 首先,特征是抽象的,这意味着您不能创建任何成员。 考虑以下对比。
scala> class FooClass
defined class FooClass
scala> trait FooTrait
defined trait FooTrait
scala> val fclass = new FooClass
fclass: FooClass = FooClass@1b499616
scala> val ftrait = new FooTrait
<console>:8: error: trait FooTrait is abstract; cannot be instantiated
val ftrait = new FooTrait
^
但是,您可以扩展特征以形成具体的类。
scala> class FooTraitExtender extends FooTrait
defined class FooTraitExtender
scala> val ftraitExtender = new FooTraitExtender
ftraitExtender: FooTraitExtender = FooTraitExtender@53d26552
当然,如果特征具有某些方法,这将变得更加有趣。 这是一个特性Animal ,它声明了两个抽象方法makeNoise和doBehavior 。
trait Animal {
def makeNoise: String
def doBehavior (other: Animal): String
}
我们可以使用新的类定义来扩展此特性。 每个扩展类都必须实现这两种方法(否则必须声明为抽象)。
case class Bear (name: String, defaultBehavior: String = "Regard warily...") extends Animal {
def makeNoise = "ROAR!"
def doBehavior (other: Animal) = other match {
case b: Bear => makeNoise + " I'm " + name + "."
case m: Mouse => "Eat it!"
case _ => defaultBehavior
}
override def toString = name
}
case class Mouse (name: String) extends Animal {
def makeNoise = "Squeak?"
def doBehavior (other: Animal) = other match {
case b: Bear => "Run!!!"
case m: Mouse => makeNoise + " I'm " + name + "."
case _ => "Hide!"
}
override def toString = name
}
注意,Bear和Mouse具有不同的参数列表,但是它们都可以是Animals,因为它们完全实现了Animal特性。 现在,我们可以开始创建Bear和Mouse类的对象并使它们交互。 我们不需要使用“ new”,因为它们是case类(这也使它们可以在doBehavior方法的match语句中使用)。
val yogi = Bear("Yogi", "Hello!")
val baloo = Bear("Baloo", "Yawn...")
val grizzly = Bear("Grizzly")
val stuart = Mouse("Stuart")
println(yogi + ": " + yogi.makeNoise)
println(stuart + ": " + stuart.makeNoise)
println("Grizzly to Stuart: " + grizzly.doBehavior(stuart))
我们还可以使用以下声明来创建动物类型的单例对象。
object John extends Animal {
def makeNoise = "Hullo!"
def doBehavior (other: Animal) = other match {
case b: Bear => "Nice bear... nice bear..."
case _ => makeNoise
}
override def toString = "John"
}
在这里, 约翰是一个对象,而不是一个类。 由于此对象实现了Animal特质,因此可以成功扩展它并可以充当Animal 。 这意味着像baloo这样的Bear可以与John互动。
println("Baloo to John: " + baloo.doBehavior(John))
作为脚本运行时,上述代码的输出如下。
斯图尔特:吱吱声?
对斯图尔特灰熊:吃吧!
向约翰致敬:打哈欠…
特质和抽象类之间更紧密的区别。 实际上,上面显示的所有内容都可以将Animal作为抽象类而不是特征来完成。 一个区别是抽象类可以具有构造函数,而特征不能。 它们之间的另一个主要区别是特质可用于支持有限的多重继承,如下一个问题/答案所示。
问:Scala是否支持多重继承?
答:是的,通过特征和某些方法的实现。 这是一个示例,其特征Clickable具有抽象的(未实现的)方法getMessage ,已实现的方法click以及一个私有的可重新分配的变量numTimesClicked (后两个特征清楚地表明,特征与Java接口不同)。
trait Clickable {
private var numTimesClicked = 0
def getMessage: String
def click = {
val output = numTimesClicked + ": " + getMessage
numTimesClicked += 1
output
}
}
现在,我们有一个MessageBearer类(出于完全不同的原因,我们可能想要与单击无关的类)。
class MessageBearer (val message: String) {
override def toString = message
}
现在可以通过扩展MessageBearer并“混入” Clickable特征来创建一个新类。
class ClickableMessageBearer(message: String) extends MessageBearer(message) with Clickable {
def getMessage = message
}
ClickableMessageBearer现在具有MessageBearers (将能够检索其消息)和Clickables的功能 。
scala> val cmb1 = new ClickableMessageBearer("I'm number one!")
cmb1: ClickableMessageBearer = I'm number one!
scala> val cmb2 = new ClickableMessageBearer("I'm number two!")
cmb2: ClickableMessageBearer = I'm number two!
scala> cmb1.click
res3: java.lang.String = 0: I'm number one!
scala> cmb1.message
res4: String = I'm number one!
scala> cmb1.click
res5: java.lang.String = 1: I'm number one!
scala> cmb2.click
res6: java.lang.String = 0: I'm number two!
scala> cmb1.click
res7: java.lang.String = 2: I'm number one!
scala> cmb2.click
res8: java.lang.String = 1: I'm number two!
问:为什么有toString , toInt和toList函数,但是没有toTuple函数?
答:这是一个基本问题,直接导致更高级的隐式主题。 这背后有许多原因。 首先,重要的是要意识到,有很多类型的元组,从具有单个元素的元组(一个Tuple1)到最多22个元素(一个Tuple22)开始。 请注意,当您使用(,)创建元组时,它隐式调用了正确Arity的相应TupleN的构造函数。
scala> val b = (1,2,3)
b: (Int, Int, Int) = (1,2,3)
scala> val c = Tuple3(1,2,3)
c: (Int, Int, Int) = (1,2,3)
scala> b==c
res4: Boolean = true
鉴于此,显然是没有意义的对Seqs功能toTuple是比22长,这意味着有是有,说一个列表或数组,没有通用的方法,然后调用它toTuple并期望可靠的行为(序列)即将发生。
但是,如果您想要此功能(即使受上述最大22个元素的约束所限制),Scala允许您使用隐式定义将方法“添加”到现有类中。 通过搜索“标量隐式”,可以找到许多有关隐式的讨论。 但是,这是一个示例,显示了它如何在这种特殊情况下工作。
val foo = List(1,2)
val bar = List(3,4,5)
val baz = List(6,7,8,9)
foo.toTuple
class TupleAble[X] (elements: Seq[X]) {
def toTuple = elements match {
case Seq(a) => Tuple1(a)
case Seq(a,b) => (a,b)
case Seq(a,b,c) => (a,b,c)
case _ => throw new RuntimeException("Sequence too long to be handled by toTuple: " + elements)
}
}
foo.toTuple
implicit def seqToTuple[X](x: Seq[X]) = new TupleAble(x)
foo.toTuple
bar.toTuple
baz.toTuple
如果将其放入Scala REPL中,您将看到foo.toTuple的第一次调用会出现错误:
scala> foo.toTuple
<console>:9: error: value toTuple is not a member of List[Int]
foo.toTuple
^
请注意, TupleAble类在其构造函数中采用一个Seq,然后使用该Seq提供方法toTuple 。 对于具有1、2或3个元素的Seq,它可以这样做,并且在此之上,它将引发异常。 (我们当然可以继续列出更多的案例,最多增加22个元素元组,但这表明了这一点。)
foo.toTuple的第二次调用仍然不起作用-这是因为foo是一个List(一种Seq),并且List没有toTuple方法。 这就是隐式函数seqToTuple出现的地方—一旦声明,Scala便指出您正在尝试在Seq上调用toTuple ,并注意到Seqs没有此类函数,但是看到通过Seqs到TupleAbles的隐式转换seqToTuple ,然后看到TupleAble具有toTuple方法。 基于此,它将编译并产生所需的行为。 这是Scala的一种非常方便的功能,如果您谨慎使用它,它实际上可以简化您的代码。
参考: JCG合作伙伴 关于Scala的学生问题,第1部分 Bcomposes博客的Jason Baldridge 。
翻译自: https://www.javacodegeeks.com/2012/02/student-questions-about-scala-part-1.html