Scala 03 —— Scala Puzzle 拓展
文章目录
Scala 03 —— Scala Puzzle 拓展一、占位符二、模式匹配的变量和常量模式三、继承 成员声明的位置结果初始化顺序分析`BMember` 类`BConstructor` 类 四、缺省初始值与重载五、Scala的集合操作和集合类型保持一致性第一部分代码解释第二部分代码解释 六、for和map的区别
一、占位符
val list1:List[Int] = List(1,2,3).map(_ + 1)val list2:List[Int] = List(1,2,3).map(r => r + 1)
在上面的例子中,两代码的返回结果其实是一样的,都是 List(2,3,4)
,而在第一行中的 _
叫做占位符,可以让我们的代码更加简洁 但是这并不意味着使用 _
和箭头函数是同样的结果
val list1:List[Int] = List(1,2,3).map{println("ss");_ + 1}val list2:List[Int] = List(1,2,3).map{r => println("ss");r + 1}
在上面的代码中,看起来运行的结果还是一样的,但是事实却不是这样的,当然 list1
和 list2
的返回值是一样的,但是执行的过程中打印的结果却是不一样的, 第一行代码打印了一行 ss
,第二行代码打印了三行 ss
为什么会有这样的不同?
Map
函数的本质是对每个集合中的元素应用一个函数,第一个语句中函数的执行体只是_+1
,第二个语句中函数的执行体是println("ss");r + 1
。
二、模式匹配的变量和常量模式
变量模式:模式匹配的标识符是小写字母,作用是赋值
val (x,y) = (1,2)
常量模式:模式匹配的标识符是大写字母,该常量必须是已赋值的,否则会报错,作用是判断
val (X,Y) = (1,2)// 报错x match { case PI => "圆周率"}
三、继承 成员声明的位置
trait A{ val audience:String println("Hello " + audience)}class BMember (a:String = "World") extends A{ override val audience: String = a println("I repeat:Hello " + audience)}class BConstructor(val audience:String = "World") extends A{ //该种方法声明的变量不会存在null的情况 println("I repeat:Hello " + audience)}new BMember("reader")new BConstructor("reader")
结果
Hello nullI repeat:Hello readerHello readerI repeat:Hello reader
初始化顺序分析
BMember
类
调用构造器:当创建 BMember
的实例时,首先初始化父类 A
。父类 A
的初始化:在 Scala 中,父类的构造代码块(包括字段的初始化和任何其他语句)首先被执行。在 A
中,audience
还未被 BMember
初始化,因此其值为 null
(String
类型的默认值)。打印语句执行:打印 "Hello " + audience
,由于 audience
还是 null
,输出 Hello null
。子类 BMember
的字段初始化:初始化 audience
为传入的参数 "reader"
。子类中的打印语句:接着执行 BMember
中的打印语句,输出 "I repeat: Hello reader"
。 BConstructor
类
构造器参数:在 BConstructor
类中,audience
是通过构造器参数直接定义的。这意味着在调用父类 A
的构造器之前,audience
已经被初始化。父类 A
的初始化:由于 audience
已经初始化为 "reader"
,当父类 A
中的打印语句执行时,输出 "Hello reader"
。子类中的打印语句:紧接着在 BConstructor
中,再次打印 "I repeat: Hello reader"
。 总结:
一般来说,子类在初始化时会先初始化父类构造器构造出父类对象,因为子类可能有依赖于父类对象的属性或方法。
作为类字段被赋值,在父类构造器执行后才初始化;作为构造参数被赋值,在父类构造器执行前初始化。
四、缺省初始值与重载
trait A { val foo: Int //缺省初始值,Boolean缺省初始值是false,Unit缺省初始值是() def bar: Int = 10 //附初值的变量后面只能用override println(s"In A:foo=$foo,bar=$bar") //0,0,0}class B extends A { val foo: Int = 25 println(s"In B:foo=$foo,bar=$bar") //25,36,0}class C extends B { override val foo: Int = 46 //当一个val被重载的时候,只能初始化一次 override def bar: Int = 100 println(s"In C:foo=$foo,bar=$bar") //25,36,99}new C()/*In A:foo=0,bar=100In B:foo=0,bar=100In C:foo=46,bar=100*/
字段初始化顺序:像val foo:Int
,字段初始化发生在构造器体执行之前,但超类的构造器(包括 println
语句)会在任何子类字段初始化之前执行。
方法动态绑定:像def bar:Int=5
,Scala 会使用动态绑定来决定应该调用哪个版本的方法。即使在超类的构造器中,也会调用最终重写的方法版本(在 C
中为 100
)。
字段重写:foo
在子类中被重写,但在超类和任何父类的构造器中引用这个字段时,它们看到的是其默认值(在赋值之前),直到子类自己的构造器赋予它新值。
当一个 val
被重载,只能初始化一次
五、Scala的集合操作和集合类型保持一致性
def sumSizes(collections:Iterable[Iterable[_]]):Int = { // println(s"collections:$collections") // println(collections.map(_.size)) collections.map(_.size).sum}
第一部分代码解释
println(sumSizes(List(Set(1,2),List(3,4))))
在这里,输入是List
类型的,包含两个集合:一个Set
和一个List
。List
映射(map)操作会返回一个新的List
,其中包含每个子集合的大小:
Set(1, 2)
的大小为2(因为集合中不允许重复值)List(3, 4)
的大小为2 因此,map(_.size)
返回List(2, 2)
,其和为4。
第二部分代码解释
println(sumSizes(Set(List(1,2),Set(3,4))))
这里输入是Set
类型的。由于Set
在map
操作后仍保持Set
类型,而且不允许重复值,它会影响结果:
List(1, 2)
的大小为2Set(3, 4)
的大小也是2 由于结果Set
不能有重复值,map(_.size)
产生的结果是Set(2)
,其和为2,因为只有一个元素。
def sumSizes1(collections:Iterable[Iterable[_]]):Int = { collections.toSeq.map(_.size).sum}
不管是什么集合类型,都将其转换成Seq
六、for和map的区别
val ys = for(Seq(x,y,z) <- xs) yield x+y+z
这里的for
表达式等价于先进行withFilter
过滤掉不符合模式Seq(x, y, z)
的元素,然后对过滤后的元素应用map
。因此,Seq("g","h")
因为只有两个元素而被忽略,不参与后续的map
操作。
val zs1 = xs map {case Seq(x,y,z) =>x+y+z}
当遇到Seq("g", "h")
时,模式Seq(x, y, z)
无法匹配只有两个元素的序列,因此Scala抛出了MatchError
。