scala
Posted fjdsj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scala相关的知识,希望对你有一定的参考价值。
一: scala基础
1 概念
Scala就是一门语言, 是spark的框架语言. 继承了面向对象编程和函数式编程.
Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。http://www.scala-lang.org
scala的优点:
1 优雅
2 速度快
3 能融合到Hadoop生态圈
2 安装
Windows安装Scala编译器 :
配置环境变量
SCALA_HOME=C:Worksoftscala-2.11.8
path=%SCALA_HOME%in;
?
Linux安装Scala编译器 :
vi /etc/profile
export SCALA_HOME=/export/servers/scala-2.11.8
export PATH=$PATH:$SCALA_HOME/bin
在配置完毕以后需要更新一下linux的配置文件
source /etc/profile
3 基本语法
1 变量和常量
关键词 "var" 声明变量,
关键词 "val" 声明常量
2 常用类型:
scala有7中数值类型:
? Byte、Char、Short、Int、Long、Float、Double类型和1个Boolean类型 l
val str: String = "itcast" 变量名在前, 类型在后边
在scala中, 一切都是方法
在scala中,数据类型都是对象,也就是说scala没有java中的原生类型。在scala是可以对数字等基础类型调用方法的。
scala中+ - * % 等操作符 位操作符 & | ^ >> << 这些操作符都是方法
如:
? a + b 本质为 a.+(b)
3 Any AnyVal AnyRef区别
Any是所有类的超类 相当于java中object,AnyRef和AnyVal是它的两个子类
AnyRef是所有引用类型的基类。除了值类型,所有类型都继承自AnyRef 。
AnyVal所有值类型的基类,它描述的是值,而不是代表一个对象。
4 条件表达式
object ConditionDemo {
def main(args: Array[String]) {
val x = 1
//判断x的值,将结果赋给y
val y = if (x > 0) 1 else -1
//打印y的值
println(y)
?
//支持混合类型表达式
val z = if (x > 1) 1 else "error"
//打印z的值
println(z)
?
//如果缺失else,相当于if (x > 2) 1 else ()
val m = if (x > 2) 1
println(m)
?
//在scala中每个表达式都有值,scala中有个Unit类,用作不返回任何结果的方法的结果类型,相当于Java中的void,Unit只有一个实例值,写成()。
val n = if (x > 2) 1 else ()
println(n)
?
//if和else if
val k = if (x < 0) 0
else if (x >= 1) 1 else -1
println(k)
}
}
5 块表达式
定义变量时用 {} 包含一系列表达式,其中块的最后一个表达式的值就是块的值。
object BlockExpressionDemo {
def main(args: Array[String]) {
val a = 10
val b = 20
//在scala中{}中包含一系列表达式,块中最后一个表达式的值就是块的值
//下面就是一个块表达式
val result = {
val c=b-a
val d=b-c
d //块中最后一个表达式的值
}
//result的值就是块表达式的结果
println(result)
}
}
?
6 循环
在scala中有for循环和while循环,用for循环比较多
for循环语法结构:for (i <- 表达式/数组/集合)
?
for(i <- 表达式),表达式1 to 10返回一个Range(区间)
//每次循环将区间中的一个值赋给i
for (i <- 1 to 10)
println(i)
输出:1 2 3 4 .... 10
?
//for(i <- 数组)
val arr = Array("a", "b", "c")
for (i <- arr)
println(i)
输出: a b c
?
//高级for循环
//每个生成器都可以带一个条件,注意:if前面没有分号
for(i <- 1 to 3; j <- 1 to 3 if i != j)
print((10 * i + j) + " ")
println()
输出:12 13 21 23 31 32
//for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
//每次迭代生成集合中的一个值
val v = for (i <- 1 to 10) yield i * 10
println(v)
输出:Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
while循环:
scala> var a = 10
a: Int = 10
scala> while (a >5 ){
| println(a)
| a =a-1
| }
?
while 循环的关键点是循环可能一次都不会执行。当条件为 false 时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句
do….while循环
scala> var a = 10
a: Int = 10
scala> do {
| println(a)
| a =a-1
| } while(a >5)
while条件表达式出现在循环的尾部,所以循环会在条件被测试之前至少执行一次。
7 方法和函数
定义方法:
方法由def关键字定义。def后面跟着一个名字、参数列表、返回类型和方法体。方法体的最后一个表达式就是方法的返回值。
在大多数的时候方法的返回值可以不写, 但是对于递归函数, 必须指定返回值类型, 不然会报错
error: recursive method m1 needs result type
定义方法
函数是带有参数的表达式。=>的左边是参数列表,右边是一个包含参数的表达式。
(x: Int) => x + 1
?
你也可以给函数命名。Java中思想叫做引用
val addOne = (x: Int) => x + 1
?
函数可带有多个参数。
val add = (x: Int, y: Int) => x + y
?
或者不带参数。
val getTheAnswer = () => 42
方法和函数的区别:
函数是一个对象,继承自FuctionN 函数对象有apply、curried、toString、tupled这些方法。而方法不具有这些特性
方法和函数可以同步使用
方法变成函数的例子
def m1(x:Int,y:Int):Int=x*y
m1: (x: Int, y: Int)Int
scala> val f1 = m1 _
f1: (Int, Int) => Int = <function2>
8 数组
长度固定则使用Array,若长度有可能变化则使用ArrayBuffer
如果提供初始值时可不使用new,底层会直接调用apply方法
数组这块如果固定了数组长度, 就用 import scala.collection.mutable.ArrayBuffer 的包
数组长度不固定, 使用 import scala.collection.mutable.ArrayBuffer包
(1)定长数组定义格式:
val arr = new Array[T] (数组长度) 无初始值 使用new
val arr2 = Array(1,2,3) 有初始值 不需要new
(2)变长数组定义格式:
val arr = new ArrayBuffer[T] ()
val arr2 = ArrayBuffer (1,2,3)
object ArrayDemo {
def main(args: Array[String]) {
//初始化一个长度为8的定长数组,其所有元素均为0
val arr1 = new Array[Int](8)
//直接打印定长数组,内容为数组的hashcode值
println(arr1)
//将数组转换成数组缓冲,就可以看到原数组中的内容了
//toBuffer会将数组转换长数组缓冲
println(arr1.toBuffer)
?
//注意:如果new,相当于调用了数组的apply方法,直接为数组赋值
//初始化一个长度为1的定长数组
val arr2 = Array[Int](10)
println(arr2.toBuffer)
?
//定义一个长度为3的定长数组
val arr3 = Array("hadoop", "storm", "spark")
//使用()来访问元素
println(arr3(2))
?
//变长数组(数组缓冲)
//如果想使用数组缓冲,需要导入import scala.collection.mutable.ArrayBuffer包
val ab = ArrayBuffer[Int]()
//向数组缓冲的尾部追加一个元素
//+=尾部追加元素
ab += 1
//追加多个元素
ab += (2, 3, 4, 5)
//追加一个数组++=
ab ++= Array(6, 7)
//追加一个数组缓冲
ab ++= ArrayBuffer(8,9)
//打印数组缓冲ab
?
//在数组某个位置插入元素用insert,从某下标插入
ab.insert(0, -1, 0)
//删除数组某个位置的元素用remove 按照下标删除
ab.remove(0)
println(ab)
}
}
遍历数组
两种方式
? 1 for (i <- arr2) println(i) 增强for循环
? 2 for (i <- 0 until arr2.length) println(arr2(i))
? 注意: 0 until 10 返回一个区间 包含0不包含10
数组转换:
yield 和 map 两种方式 转变后相当于产生了新的数组
例:
? val arr = Array(1,2,3,4,5)
? val arr1 = for(e <- arr) yied e*2
? 输出: res: Array[Int] = Array(2,4,6,8,10)
? arr.map(_ *2)
? 输出 res1 : Array[Int] = Array(2,4,6....)
数组常用算法:
? arr.sum 求和
? arr.max 求最大值
? arr.sorted 排序
对偶Array转为Map
拉链操作(zip)
? 1 使用zip命令将多个值绑定在一起
? 注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数 组的元素个数
? 2 如果其中一个元素的个数比较少,可以使用zipAll用默认的元素填充**
?
4 Collection
1 映射Map
不可变Map
导包 import scala.collection.immutable.Map
定义不可变Map方式:
操作遍历map
可变map
? 导包: import scala.collection.mutable._
定义可变Map
操作可变Map
2 元组Tuple
元组表示通过将不同的值用小括号括起来,是不同类型的值的聚集。可以装着多个不同类型的值。
对偶是最简单的元组(两个元素),映射是K/V对偶的集合。
创建:
? val t = ("hadoop", 3.14 , 43523)
? 定义元组时, 用小括号将多个元素包起来, 元素之间用逗号分隔 , 元素的类型可以不一样, 元素个数可以任意个
scala默认给22个确定长度的元素对象
获取元素值:
? 使用下划线加脚标 ,例如 t.1 t._2 t._3
? 注意:元组中的元素脚标是1 从开始的
元素迭代器:
? Tuple.productIterator() 该方法返回一个迭代器
? t1.productIterator.foreach(println(_)) _表名循环的每一次元素
? t2.productIterator.foreach(i => println(i)) 输出哪个元素
3 列表
1 不可变List import scala.collection.immutable._
列表的定义:
?
head 返回列表第一个元素
tail 返回一个列表,包含除了第一元素之外的其他元素
isEmpty 在列表为空时返回true
list常用的操作符:
val lst1 = list(1,2,3,4)
+: (elem: A): List[A] 在列表的头部添加一个元素
? val lst2 = 0 +: lst1 在头部添加一个元素
:: (x: A): List[A] 在列表的头部添加一个元素
? val lst3 = 0 :: lst1
:+ (elem: A): List[A] 在列表的尾部添加一个元素
? val lst4 = lst1 :+ 3
++[B] (that: GenTraversableOnce[B]): List[B] 从列表的尾部添加另外一个列表
? val lst0 = List(4,5,6)
? val lst5 = lst1 ++ lst0
::: (prefix: List[A]): List[A] 在列表的头部添加另外一个列表
? val lst6 = lst1 ++: lst0
2 可变List: import scala.collection.mutable. ListBuffer
object MutListDemo extends App{
//构建一个可变列表,初始有3个元素1,2,3
val lst0 = ListBuffer[Int](1,2,3)
//创建一个空的可变列表
val lst1 = new ListBuffer[Int]
//向lst1中追加元素,注意:没有生成新的集合
lst1 += 4
lst1.append(5)
?
//将lst1中的元素追加到lst0中, 注意:没有生成新的集合
lst0 ++= lst1
?
//将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个集合
val lst2= lst0 ++ lst1
?
//将元素追加到lst0的后面生成一个新的集合
val lst3 = lst0 :+ 5
?
//删除元素,注意:没有生成新的集合
val lst4 = ListBuffer[Int](1,2,3,4,5)
lst4 -= 5
?
//删除一个集合列表,生成了一个新的集合
val lst5=lst4--List(1,2)
?
//把可变list 转换成不可变的list 直接加上toList
val lst6=lst5.toList
?
//把可变list 转变数组用toArray
val lst7=lst5.toArray
4 集合 Set
Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。且 Set 是不保证插入顺序的,即 Set 中的元素是乱序的。
Scala 集合分为可变的和不可变的集合。
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。(注意与val修饰的变量进行区别)
不可变Set import scala.collection.immutable._
可变的Set import scala.collection.mutable._
基础语法的练习:
//创建一个List
val list0=List(1,7,9,8,0,3,5,4,6,2)
?
//将list0中的每一个元素乘以10后生成一个新的集合
val list1=list0.map(x=>x*10)
?
//将list0中的偶数取出来生成一个新的集合
val list2=list0.filter(x=>x%2==0)
?
//将list0排序后生成一个新的集合
val list3=list0.sorted
val list4=list0.sortBy(x=>x)
val list5=list0.sortWith((x,y)=>x<y)
?
//反转顺序
val list6=list3.reverse
?
//将list0中的元素4个一组,类型为Iterator[List[Int]]
val list7=list0.grouped(4)
?
//将Iterator转换成List
val list8=list7.toList
?
//将多个list压扁成一个List
val list9=list8.flatten
?
val lines = List("hello tom hello jerry", "hello jerry", "hello kitty")
//先按空格切分,在压平
val result1=lines.flatMap(_.split(" "))
?
//并行计算求和
val result2=list0.par.sum
?
//化简:reduce
//将非特定顺序的二元操作应用到所有元素
val result3=list0.reduce((x,y) => x + y)
?
//按照特定的顺序
val result4 = list0.reduceLeft(_+_)
val result5= list0.reduceRight(_+_)
?
//折叠:有初始值(无特定顺序)
val result6 = list0.fold(100)((x,y)=>x+y)
?
//折叠:有初始值(有特定顺序)
val result7 = list0.foldLeft(100)((x,y)=>x+y)
?
//聚合
val list10= List(List(1, 2, 3), List(4, 5, 6), List(7,8), List(9,0))
val result8 = list10.par.aggregate(10)(_+_.sum,_+_)
?
//获取到参与并行计算的线程
println(list10.par.collect{
case _=>Thread.currentThread().getName
}.distinct)
?
val l1 = List(5,6,4,7)
val l2 = List(1,2,3,4)
//求并集
val r1=l1.union(l2)
?
//求交集
val r2=l1.intersect(l2)
?
//求差集
val r3=l1.diff(l2)
par并行计算
将普通集合转化成为并行集合:arr(非并行) arr.par(并行集合)
?
1现在有一个集合,对它的每个元素进行处理,比如:
?
scala> (1 to 5).foreach(println(_))
?
12345
?
scala> (1 to 5).par.foreach(println(_))
?
31425
?
?
2以下代码获取到参与并行计算的线程:
?
scala> (0 to 10000).collect{case _ => Thread.currentThread.getName}.distinct
?
res53: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(Thread-57)
?
scala> (0 to 10000).par.collect{case _ => Thread.currentThread.getName}.distinct
?
res54: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(ForkJoinPool-1-worker-0, ForkJoinPool-1-worker-4, ForkJoinPool-1-worker-5, ForkJoinPool-1-worker-2, ForkJoinPool-1-worker-6, ForkJoinPool-1-worker-1, ForkJoinPool-1-worker-7)
?
?
3 fold在并行计算中
?
(1)arr.fold(0)(_+_)与reduce的区别是有初始值,当初始值为0,效果一样
?
(2)arr.par.fold
?
在并行计算要注意!几个线程就会加进去几个初始值
二: scala的几大特性:
1 : 类 对象 继承 特质
1 类 :
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占 用存储空间 p
在scala中,类不用声明为public类型的. 默认访问级别都是pulic
-
类私有字段, 只能在类的内部使用,或者伴生对象中访问
private var name : String = "唐伯虎" -
类的私有字段 被private[this]修饰的只能在本类中被访问, 伴生类中也不行
private[this] var pet = "小强"
构造器:
与Java构造不同之处在与Scala不需要定义与类名相同的方法作为构造器。
主构造器
主构造器的参数直接放置类名后面,与类交织在一起。主构造器会执行类定义中的所有语句,所以,一个Scala类中,除了参数,方法以外的代码都是主构造的内容。
辅助构造器
都以def this开始。每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
package cn.itcast.class_demo
?
/**
*每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起
*/
class Student(val name:String,var age:Int) {
//主构造器会执行类定义的所有语句
println("执行主构造器")
private var gender="male"
def this(name:String,age:Int,gender:String){
//每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
this(name,age)
println("执行辅助构造器")
this.gender=gender
}
}
?
object Student {
def main(args: Array[String]): Unit = {
val s1=new Student("zhangsan",20)
val s2=new Student("zhangsan",20,"female")
}
}
?
2 对象
1 object
object,相当于 class 的单个实例。可以直接访问,不需要实例化该类的对象。因此main方法可以写在object中,作为程序启动的入口。
在object中,属性、方法相当于都是静态的。
object作用:存放工具方法和常量、高效共享单个不可变的实例、单例模式等。
2 伴生对象
如果有一个 class 文件,还有一个与 class 同名的 object 文件,那么就称这个 object是 class 的伴生对象,class 是 object 的伴生类;
伴生类和伴生对象必须存放在一个.scala 文件中;
伴生类和伴生对象的最大特点是,可以相互访问 (包括private属性除非属性被[this]修饰)
伴生类:
? 名字和类名相同, 伴生类中可以直接访问类中的方法,当然被[this]修饰的private不能访问呢
3 apply方法
apply方法一般在伴生对象中实现, 可以帮伴生对象构造函数 , 当我们定义了apply方法后,遇到(参数) 等同于apply方法后,就直接调用了apply的 方法 ,同时不会new对象,而是直接调用
apply通常被称作注入方法
unapply通常被称为提取方法,使用unapply来提取固定数量的对象,使用unapplySeq来提取一个序列
apply方法通常是在伴生对象中实现的,其目的是,通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,...参数n)时apply方法会被调用;
在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用 class(),隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
4 main方法
在scala中 如果类直接继承了App Trait , 就表示可以不写主方法了.
3 继承
在scala中的继承:
? 只是用extends关键字, 表示继承父类的 field(属性) 和method(方法 )
几种特殊情况:
? 1 如果父类被final修饰, 或者field 和 method 用final修饰 该类无法被继承和覆盖
? 2 private 修饰的 field 和 method 不可以被子类继承, 只能在父类的内部使用
? 3 field 必须被定义为 val 的形式才能被继承,并且还要使用override 关键字
? val修饰的才允许被继承,var 修饰的只允许被引用
override 和 super 关键字
Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字;子类可以覆盖父类的 val 修饰的field,只要在子类中使用 override 关键字即可。
override 关键字可以帮助开发者尽早的发现代码中的错误,比如, override 修饰的父类方法的方法名拼写错误。
此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用 super 关键字,显示的指出要调用的父类方法
isInstanceOf 和 asInstanceOf
首先,需要使用 isInstanceOf 判断对象是否为指定类的对象,如果是的话,则可以使用 asInstanceOf 将对象转换为指定类型;
注意: p.isInstanceOf[XX] 判断 p 是否为 XX 对象的实例;p.asInstanceOf[XX] 把 p 转换成 XX 对象的实例
注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常;
注意:如果对象是 null,则 isInstanceOf 一定返回 false, asInstanceOf 一定返回 null;
getClass 和 classOf
isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象;
如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 了;
p.getClass 可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用 == 操作符即可判断;
模式匹配进行类型判断:
object TestMatch {
def main(args: Array[String]): Unit = {
// val a1 = Array(1,2,3)
val a2 = Array(1,"name",1.3)
?
// if(v ==1){
// println("11111111111")
// }else if(v ==2) {
// println("2222222")
// }
// else if(v ==3) {
// println("3333333")
// }
// a1(Random.nextInt(a1.length)) match {
// case 1 => println("111111111")
// case 2 => println("222222222")
// case 3 => println("333333333")
// case _ => println("00000000")
// }
?
a2(Random.nextInt(a2.length)) match {
//TODO 如果匹配条件都满足 按照顺序只执行第一个匹配
case x:Int => println("111111111")
case y:Int => println("222222222")
//TODO 加入if添加的case语句叫做守卫条件
//除了满足case匹配外 还要满足守卫条件
//如果不满足 直接落入 case _
case z:Double if(z > 1.5)=> println("1.3")
case _ => println("nothing")
}
// val a = "allen"
// val b = "tom"
// println(s"a:$a b:$b")
// println(a+b)
}
}
protected
只允许保护成员在定义了该成员的类的子类中被访问
就是同一个包的其他类不能访问了, 这个和java很类似,用法不受限制
作用域保护
private[x] 或者是 protected[x]
这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。
这种技巧在横跨了若干包的大 型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西
构造器 constructor
-
Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的;
-
只能在子类的主constructor中调用父类的constructor。
-
如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字。
匿名内部类:
定义一个没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量,即匿名内部类的实例化对象。然后将该对象传递给其他函数使用。
?
class Person8(val name:String) {
def sayHello="Hello ,I‘m "+name
}
class GreetDemo{
//接受Person8参数,并规定Person8类只含有一个返回String的sayHello方法
//就是在这里使用了匿名内部类, 可以直接调用 p:Person8{.....}的方法,没有重新new对象
def greeting(p:Person8{def sayHello:String})={
println(p.sayHello)
}
}
object GreetDemo {
def main(args: Array[String]) {
//创建Person8的匿名子类对象
val p=new Person8("tom")
val g=new GreetDemo
g.greeting(p)
}
}
?
抽象类:
如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;
一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的;
在子类中覆盖抽象类的抽象方法时,可以不加override关键字;
package cn.itcast.extends_demo
?
abstract class Person9(val name:String) {
//必须指出返回类型,不然默认返回为Unit
def sayHello:String
def sayBye:String
}
class Student9(name:String) extends Person9(name){
//必须指出返回类型,不然默认
def sayHello: String = "Hello,"+name
def sayBye: String ="Bye,"+name
}
object Student9{
def main(args: Array[String]) {
val s = new Student9("tom")
println(s.sayHello)
println(s.sayBye)
}
}
?
抽象field
如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field;
4 traid
Scala Trait(特征、特质) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,Trait可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。Scala 中没有 implement 的概念,无论继承类还是 trait,统一都是 extends。
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait。
1 将trait作为接口
类继承trait后,必须实现其中的抽象方法(没有具体实现的方法),实现时,不需要使用 override 关键字
如果一个类要想实现多个Trait,除第一个外后面的使用with关键字
一: scala基础
1 概念
Scala就是一门语言, 是spark的框架语言. 继承了面向对象编程和函数式编程.
Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。http://www.scala-lang.org
scala的优点:
1 优雅
2 速度快
3 能融合到Hadoop生态圈
2 安装
Windows安装Scala编译器 :
配置环境变量
SCALA_HOME=C:Worksoftscala-2.11.8
path=%SCALA_HOME%in;
?
Linux安装Scala编译器 :
vi /etc/profile
export SCALA_HOME=/export/servers/scala-2.11.8
export PATH=$PATH:$SCALA_HOME/bin
在配置完毕以后需要更新一下linux的配置文件
source /etc/profile
3 基本语法
1 变量和常量
关键词 "var"声明变量,
关键词 "val"声明常量
2 常用类型:
scala有7中数值类型:
? Byte、Char、Short、Int、Long、Float、Double类型和1个Boolean类型 l
val str: String = "itcast" 变量名在前, 类型在后边
在scala中, 一切都是方法
在scala中,数据类型都是对象,也就是说scala没有java中的原生类型。在scala是可以对数字等基础类型调用方法的。
scala中+ - * % 等操作符 位操作符 & | ^ >> << 这些操作符都是方法
如:
? a + b 本质为 a.+(b)
3 Any AnyVal AnyRef区别
Any是所有类的超类 相当于java中object,AnyRef和AnyVal是它的两个子类
AnyRef是所有引用类型的基类。除了值类型,所有类型都继承自AnyRef 。
AnyVal所有值类型的基类,它描述的是值,而不是代表一个对象。
4 条件表达式
object ConditionDemo {
def main(args: Array[String]) {
val x = 1
//判断x的值,将结果赋给y
val y = if (x > 0) 1 else -1
//打印y的值
println(y)
?
//支持混合类型表达式
val z = if (x > 1) 1 else "error"
//打印z的值
println(z)
?
//如果缺失else,相当于if (x > 2) 1 else ()
val m = if (x > 2) 1
println(m)
?
//在scala中每个表达式都有值,scala中有个Unit类,用作不返回任何结果的方法的结果类型,相当于Java中的void,Unit只有一个实例值,写成()。
val n = if (x > 2) 1 else ()
println(n)
?
//if和else if
val k = if (x < 0) 0
else if (x >= 1) 1 else -1
println(k)
}
}
5 块表达式
定义变量时用 {} 包含一系列表达式,其中块的最后一个表达式的值就是块的值。
object BlockExpressionDemo {
def main(args: Array[String]) {
val a = 10
val b = 20
//在scala中{}中包含一系列表达式,块中最后一个表达式的值就是块的值
//下面就是一个块表达式
val result = {
val c=b-a
val d=b-c
d //块中最后一个表达式的值
}
//result的值就是块表达式的结果
println(result)
}
}
?
6 循环
在scala中有for循环和while循环,用for循环比较多
for循环语法结构:for (i <- 表达式/数组/集合)
?
for(i <- 表达式),表达式1 to 10返回一个Range(区间)
//每次循环将区间中的一个值赋给i
for (i <- 1 to 10)
println(i)
输出:1 2 3 4 .... 10
?
//for(i <- 数组)
val arr = Array("a", "b", "c")
for (i <- arr)
println(i)
输出: a b c
?
//高级for循环
//每个生成器都可以带一个条件,注意:if前面没有分号
for(i <- 1 to 3; j <- 1 to 3 if i != j)
print((10 * i + j) + " ")
println()
输出:12 13 21 23 31 32
//for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
//每次迭代生成集合中的一个值
val v = for (i <- 1 to 10) yield i * 10
println(v)
输出:Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
while循环:
scala> var a = 10
a: Int = 10
scala> while (a >5 ){
| println(a)
| a =a-1
| }
?
while 循环的关键点是循环可能一次都不会执行。当条件为 false 时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句
do….while循环
scala> var a = 10
a: Int = 10
scala> do {
| println(a)
| a =a-1
| } while(a >5)
while条件表达式出现在循环的尾部,所以循环会在条件被测试之前至少执行一次。
7 方法和函数
定义方法:
方法由def关键字定义。def后面跟着一个名字、参数列表、返回类型和方法体。方法体的最后一个表达式就是方法的返回值。
在大多数的时候方法的返回值可以不写, 但是对于递归函数, 必须指定返回值类型, 不然会报错
error: recursive method m1 needs result type
定义方法
函数是带有参数的表达式。=>的左边是参数列表,右边是一个包含参数的表达式。
(x: Int) => x + 1
?
你也可以给函数命名。Java中思想叫做引用
val addOne = (x: Int) => x + 1
?
函数可带有多个参数。
val add = (x: Int, y: Int) => x + y
?
或者不带参数。
val getTheAnswer = () => 42
方法和函数的区别:
函数是一个对象,继承自FuctionN 函数对象有apply、curried、toString、tupled这些方法。而方法不具有这些特性
方法和函数可以同步使用
方法变成函数的例子
def m1(x:Int,y:Int):Int=x*y
m1: (x: Int, y: Int)Int
scala> val f1 = m1 _
f1: (Int, Int) => Int = <function2>
8 数组
长度固定则使用Array,若长度有可能变化则使用ArrayBuffer
如果提供初始值时可不使用new,底层会直接调用apply方法
数组这块如果固定了数组长度, 就用 import scala.collection.mutable.ArrayBuffer 的包
数组长度不固定, 使用 import scala.collection.mutable.ArrayBuffer包
(1)定长数组定义格式:
val arr = new Array[T] (数组长度) 无初始值 使用new
val arr2 = Array(1,2,3) 有初始值 不需要new
(2)变长数组定义格式:
val arr = new ArrayBuffer[T] ()
val arr2 = ArrayBuffer (1,2,3)
object ArrayDemo {
def main(args: Array[String]) {
//初始化一个长度为8的定长数组,其所有元素均为0
val arr1 = new Array[Int](8)
//直接打印定长数组,内容为数组的hashcode值
println(arr1)
//将数组转换成数组缓冲,就可以看到原数组中的内容了
//toBuffer会将数组转换长数组缓冲
println(arr1.toBuffer)
?
//注意:如果new,相当于调用了数组的apply方法,直接为数组赋值
//初始化一个长度为1的定长数组
val arr2 = Array[Int](10)
println(arr2.toBuffer)
?
//定义一个长度为3的定长数组
val arr3 = Array("hadoop", "storm", "spark")
//使用()来访问元素
println(arr3(2))
?
//变长数组(数组缓冲)
//如果想使用数组缓冲,需要导入import scala.collection.mutable.ArrayBuffer包
val ab = ArrayBuffer[Int]()
//向数组缓冲的尾部追加一个元素
//+=尾部追加元素
ab += 1
//追加多个元素
ab += (2, 3, 4, 5)
//追加一个数组++=
ab ++= Array(6, 7)
//追加一个数组缓冲
ab ++= ArrayBuffer(8,9)
//打印数组缓冲ab
?
//在数组某个位置插入元素用insert,从某下标插入
ab.insert(0, -1, 0)
//删除数组某个位置的元素用remove 按照下标删除
ab.remove(0)
println(ab)
}
}
遍历数组
两种方式
? 1 for (i <- arr2) println(i) 增强for循环
? 2 for (i <- 0 until arr2.length) println(arr2(i))
? 注意: 0 until 10 返回一个区间 包含0不包含10
数组转换:
yield 和 map 两种方式 转变后相当于产生了新的数组
例:
? val arr = Array(1,2,3,4,5)
? val arr1 = for(e <- arr) yied e*2
? 输出: res: Array[Int] = Array(2,4,6,8,10)
? arr.map(_ *2)
? 输出 res1 : Array[Int] = Array(2,4,6....)
数组常用算法:
? arr.sum 求和
? arr.max 求最大值
? arr.sorted 排序
对偶Array转为Map
拉链操作(zip)
? 1 使用zip命令将多个值绑定在一起
? 注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数 组的元素个数
? 2 如果其中一个元素的个数比较少,可以使用zipAll用默认的元素填充**
?
4 Collection
1 映射Map
不可变Map
导包 import scala.collection.immutable.Map
定义不可变Map方式:
操作遍历map
可变map
? 导包: import scala.collection.mutable._
定义可变Map
操作可变Map
2 元组Tuple
元组表示通过将不同的值用小括号括起来,是不同类型的值的聚集。可以装着多个不同类型的值。
对偶是最简单的元组(两个元素),映射是K/V对偶的集合。
创建:
? val t = ("hadoop", 3.14 , 43523)
? 定义元组时, 用小括号将多个元素包起来, 元素之间用逗号分隔 , 元素的类型可以不一样, 元素个数可以任意个
scala默认给22个确定长度的元素对象
获取元素值:
? 使用下划线加脚标 ,例如 t.1 t._2 t._3
? 注意:元组中的元素脚标是1 从开始的
元素迭代器:
? Tuple.productIterator() 该方法返回一个迭代器
? t1.productIterator.foreach(println(_)) _表名循环的每一次元素
? t2.productIterator.foreach(i => println(i)) 输出哪个元素
3 列表
1 不可变List import scala.collection.immutable._
列表的定义:
?
head 返回列表第一个元素
tail 返回一个列表,包含除了第一元素之外的其他元素
isEmpty 在列表为空时返回true
list常用的操作符:
val lst1 = list(1,2,3,4)
+: (elem: A): List[A] 在列表的头部添加一个元素
? val lst2 = 0 +: lst1 在头部添加一个元素
:: (x: A): List[A] 在列表的头部添加一个元素
? val lst3 = 0 :: lst1
:+ (elem: A): List[A] 在列表的尾部添加一个元素
? val lst4 = lst1 :+ 3
++[B] (that: GenTraversableOnce[B]): List[B] 从列表的尾部添加另外一个列表
? val lst0 = List(4,5,6)
? val lst5 = lst1 ++ lst0
::: (prefix: List[A]): List[A] 在列表的头部添加另外一个列表
? val lst6 = lst1 ++: lst0
2 可变List: import scala.collection.mutable. ListBuffer
object MutListDemo extends App{
//构建一个可变列表,初始有3个元素1,2,3
val lst0 = ListBuffer[Int](1,2,3)
//创建一个空的可变列表
val lst1 = new ListBuffer[Int]
//向lst1中追加元素,注意:没有生成新的集合
lst1 += 4
lst1.append(5)
?
//将lst1中的元素追加到lst0中, 注意:没有生成新的集合
lst0 ++= lst1
?
//将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个集合
val lst2= lst0 ++ lst1
?
//将元素追加到lst0的后面生成一个新的集合
val lst3 = lst0 :+ 5
?
//删除元素,注意:没有生成新的集合
val lst4 = ListBuffer[Int](1,2,3,4,5)
lst4 -= 5
?
//删除一个集合列表,生成了一个新的集合
val lst5=lst4--List(1,2)
?
//把可变list 转换成不可变的list 直接加上toList
val lst6=lst5.toList
?
//把可变list 转变数组用toArray
val lst7=lst5.toArray
4 集合 Set
Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。且 Set 是不保证插入顺序的,即 Set 中的元素是乱序的。
Scala 集合分为可变的和不可变的集合。
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。(注意与val修饰的变量进行区别)
不可变Set import scala.collection.immutable._
可变的Set import scala.collection.mutable._
基础语法的练习:
//创建一个List
val list0=List(1,7,9,8,0,3,5,4,6,2)
?
//将list0中的每一个元素乘以10后生成一个新的集合
val list1=list0.map(x=>x*10)
?
//将list0中的偶数取出来生成一个新的集合
val list2=list0.filter(x=>x%2==0)
?
//将list0排序后生成一个新的集合
val list3=list0.sorted
val list4=list0.sortBy(x=>x)
val list5=list0.sortWith((x,y)=>x<y)
?
//反转顺序
val list6=list3.reverse
?
//将list0中的元素4个一组,类型为Iterator[List[Int]]
val list7=list0.grouped(4)
?
//将Iterator转换成List
val list8=list7.toList
?
//将多个list压扁成一个List
val list9=list8.flatten
?
val lines = List("hello tom hello jerry", "hello jerry", "hello kitty")
//先按空格切分,在压平
val result1=lines.flatMap(_.split(" "))
?
//并行计算求和
val result2=list0.par.sum
?
//化简:reduce
//将非特定顺序的二元操作应用到所有元素
val result3=list0.reduce((x,y) => x + y)
?
//按照特定的顺序
val result4 = list0.reduceLeft(_+_)
val result5= list0.reduceRight(_+_)
?
//折叠:有初始值(无特定顺序)
val result6 = list0.fold(100)((x,y)=>x+y)
?
//折叠:有初始值(有特定顺序)
val result7 = list0.foldLeft(100)((x,y)=>x+y)
?
//聚合
val list10= List(List(1, 2, 3), List(4, 5, 6), List(7,8), List(9,0))
val result8 = list10.par.aggregate(10)(_+_.sum,_+_)
?
//获取到参与并行计算的线程
println(list10.par.collect{
case _=>Thread.currentThread().getName
}.distinct)
?
val l1 = List(5,6,4,7)
val l2 = List(1,2,3,4)
//求并集
val r1=l1.union(l2)
?
//求交集
val r2=l1.intersect(l2)
?
//求差集
val r3=l1.diff(l2)
par并行计算
将普通集合转化成为并行集合:arr(非并行) arr.par(并行集合)
?
1现在有一个集合,对它的每个元素进行处理,比如:
?
scala> (1 to 5).foreach(println(_))
?
12345
?
scala> (1 to 5).par.foreach(println(_))
?
31425
?
?
2以下代码获取到参与并行计算的线程:
?
scala> (0 to 10000).collect{case _ => Thread.currentThread.getName}.distinct
?
res53: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(Thread-57)
?
scala> (0 to 10000).par.collect{case _ => Thread.currentThread.getName}.distinct
?
res54: scala.collection.parallel.immutable.ParSeq[java.lang.String] = ParVector(ForkJoinPool-1-worker-0, ForkJoinPool-1-worker-4, ForkJoinPool-1-worker-5, ForkJoinPool-1-worker-2, ForkJoinPool-1-worker-6, ForkJoinPool-1-worker-1, ForkJoinPool-1-worker-7)
?
?
3 fold在并行计算中
?
(1)arr.fold(0)(_+_)与reduce的区别是有初始值,当初始值为0,效果一样
?
(2)arr.par.fold
?
在并行计算要注意!几个线程就会加进去几个初始值
二: scala的几大特性:
1 : 类 对象 继承 特质
1 类 :
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占 用存储空间 p
在scala中,类不用声明为public类型的. 默认访问级别都是pulic
-
类私有字段, 只能在类的内部使用,或者伴生对象中访问
private var name : String = "唐伯虎" -
类的私有字段 被private[this]修饰的只能在本类中被访问, 伴生类中也不行
private[this] var pet = "小强"
构造器:
与Java构造不同之处在与Scala不需要定义与类名相同的方法作为构造器。
主构造器
主构造器的参数直接放置类名后面,与类交织在一起。主构造器会执行类定义中的所有语句,所以,一个Scala类中,除了参数,方法以外的代码都是主构造的内容。
辅助构造器
都以def this开始。每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
package cn.itcast.class_demo
?
/**
*每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起
*/
class Student(val name:String,var age:Int) {
//主构造器会执行类定义的所有语句
println("执行主构造器")
private var gender="male"
def this(name:String,age:Int,gender:String){
//每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
this(name,age)
println("执行辅助构造器")
this.gender=gender
}
}
?
object Student {
def main(args: Array[String]): Unit = {
val s1=new Student("zhangsan",20)
val s2=new Student("zhangsan",20,"female")
}
}
?
2 对象
1 object
object,相当于 class 的单个实例。可以直接访问,不需要实例化该类的对象。因此main方法可以写在object中,作为程序启动的入口。
在object中,属性、方法相当于都是静态的。
object作用:存放工具方法和常量、高效共享单个不可变的实例、单例模式等。
2 伴生对象
如果有一个 class 文件,还有一个与 class 同名的 object 文件,那么就称这个 object是 class 的伴生对象,class 是 object 的伴生类;
伴生类和伴生对象必须存放在一个.scala 文件中;
伴生类和伴生对象的最大特点是,可以相互访问 (包括private属性除非属性被[this]修饰)
伴生类:
? 名字和类名相同, 伴生类中可以直接访问类中的方法,当然被[this]修饰的private不能访问呢
3 apply方法
apply方法一般在伴生对象中实现, 可以帮伴生对象构造函数 , 当我们定义了apply方法后,遇到(参数) 等同于apply方法后,就直接调用了apply的 方法 ,同时不会new对象,而是直接调用
apply通常被称作注入方法
unapply通常被称为提取方法,使用unapply来提取固定数量的对象,使用unapplySeq来提取一个序列
apply方法通常是在伴生对象中实现的,其目的是,通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,...参数n)时apply方法会被调用;
在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用 class(),隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
4 main方法
在scala中 如果类直接继承了App Trait , 就表示可以不写主方法了.
3 继承
在scala中的继承:
? 只是用extends关键字, 表示继承父类的 field(属性) 和method(方法 )
几种特殊情况:
? 1 如果父类被final修饰, 或者field 和 method 用final修饰 该类无法被继承和覆盖
? 2 private 修饰的 field 和 method 不可以被子类继承, 只能在父类的内部使用
? 3 field 必须被定义为 val 的形式才能被继承,并且还要使用override 关键字
? val修饰的才允许被继承,var 修饰的只允许被引用
override 和 super 关键字
Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字;子类可以覆盖父类的 val 修饰的field,只要在子类中使用 override 关键字即可。
override 关键字可以帮助开发者尽早的发现代码中的错误,比如, override 修饰的父类方法的方法名拼写错误。
此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用 super 关键字,显示的指出要调用的父类方法
isInstanceOf 和 asInstanceOf
首先,需要使用 isInstanceOf 判断对象是否为指定类的对象,如果是的话,则可以使用 asInstanceOf 将对象转换为指定类型;
注意: p.isInstanceOf[XX] 判断 p 是否为 XX 对象的实例;p.asInstanceOf[XX] 把 p 转换成 XX 对象的实例
注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常;
注意:如果对象是 null,则 isInstanceOf 一定返回 false, asInstanceOf 一定返回 null;
getClass 和 classOf
isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象;
如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 了;
p.getClass 可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用 == 操作符即可判断;
模式匹配进行类型判断:
object TestMatch {
def main(args: Array[String]): Unit = {
// val a1 = Array(1,2,3)
val a2 = Array(1,"name",1.3)
?
// if(v ==1){
// println("11111111111")
// }else if(v ==2) {
// println("2222222")
// }
// else if(v ==3) {
// println("3333333")
// }
// a1(Random.nextInt(a1.length)) match {
// case 1 => println("111111111")
// case 2 => println("222222222")
// case 3 => println("333333333")
// case _ => println("00000000")
// }
?
a2(Random.nextInt(a2.length)) match {
//TODO 如果匹配条件都满足 按照顺序只执行第一个匹配
case x:Int => println("111111111")
case y:Int => println("222222222")
//TODO 加入if添加的case语句叫做守卫条件
//除了满足case匹配外 还要满足守卫条件
//如果不满足 直接落入 case _
case z:Double if(z > 1.5)=> println("1.3")
case _ => println("nothing")
}
// val a = "allen"
// val b = "tom"
// println(s"a:$a b:$b")
// println(a+b)
}
}
protected
只允许保护成员在定义了该成员的类的子类中被访问
就是同一个包的其他类不能访问了, 这个和java很类似,用法不受限制
作用域保护
private[x] 或者是 protected[x]
这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。
这种技巧在横跨了若干包的大 型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西
构造器 constructor
-
Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的;
-
只能在子类的主constructor中调用父类的constructor。
-
如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字。
匿名内部类:
定义一个没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量,即匿名内部类的实例化对象。然后将该对象传递给其他函数使用。
?
class Person8(val name:String) {
def sayHello="Hello ,I‘m "+name
}
class GreetDemo{
//接受Person8参数,并规定Person8类只含有一个返回String的sayHello方法
//就是在这里使用了匿名内部类, 可以直接调用 p:Person8{.....}的方法,没有重新new对象
def greeting(p:Person8{def sayHello:String})={
println(p.sayHello)
}
}
object GreetDemo {
def main(args: Array[String]) {
//创建Person8的匿名子类对象
val p=new Person8("tom")
val g=new GreetDemo
g.greeting(p)
}
}
?
抽象类:
如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;
一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的;
在子类中覆盖抽象类的抽象方法时,可以不加override关键字;
package cn.itcast.extends_demo
?
abstract class Person9(val name:String) {
//必须指出返回类型,不然默认返回为Unit
def sayHello:String
def sayBye:String
}
class Student9(name:String) extends Person9(name){
//必须指出返回类型,不然默认
def sayHello: String = "Hello,"+name
def sayBye: String ="Bye,"+name
}
object Student9{
def main(args: Array[String]) {
val s = new Student9("tom")
println(s.sayHello)
println(s.sayBye)
}
}
?
抽象field
如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field;
4 traid
Scala Trait(特征、特质) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,Trait可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。Scala 中没有 implement 的概念,无论继承类还是 trait,统一都是 extends。
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait。
1 将trait作为接口
类继承trait后,必须实现其中的抽象方法(没有具体实现的方法),实现时,不需要使用 override 关键字
如果一个类要想实现多个Trait,除第一个外后面的使用with关键字
以上是关于scala的主要内容,如果未能解决你的问题,请参考以下文章