Scala语言
1.为什么要学习scala?
用于进行在线计算(使用spark)
2.scala的介绍
Scala是一门多范式的编程语言,一种类似java的编程语言 ,设计初衷是实现可伸缩的语言 、多范式既即面向对象也是函数式编程。
面向对象:万物皆对象 封装 实例对象 类 继承 。
函数式编程: 面向过程 函数当成编程
3.scala语言和Java语言对比
相同点:
-
java和scala可以无缝混编,他们都是基于jvm。
-
可以相互调用
-
变量名命名规范与java一样
不同点: -
类型自动推断,根据值去给予类型,如果要写入类型,则写在变量名后(例子:var a:String = “111”)
-
定义变量(var)和常量(val),变量为运行时值可以改变的量,常量在运行时值不可改变。
-
支持函数式编程。
-
构造器不一样。
- 主构造器:class 类名(){}(可写有参和无参,不可重载)
- 副构造器:在class 内 创建方法,def 类名(){}(可写有参和无参,可重载)
-
scala中必须手动给默认值。
-
不需要写分号,按照换行处理语句
-
get,set方法底层实现,
- age:相当于java中的getter方法
- age_$eq:相当于java中set方法
-
scala中的方法返回值不需要写return。
-
空返回值为unit
4.编译工具的安装
- JDK
- IDEA
- 安装插件scala-intellij-bin-2017.2.2.zip。
- 安装SDK (scala-2.10.6.zip)
- 创建scala项目,创建object包
5.main方法讲解
- 同Java一样,如果要运行一个程序,必须编写一个包含main方法的类。
- 在Scala中,也必须要有一个main方法,作为入口。
- Scala中的main方法定义为 def main(args:Array[String] ),而且必须定义在object中。
- 除了自己实现mian方法之外,还可以继承App Trait ,然后,将需要写在main方法中运行的代码,直接作为object的constructor代码即可,而且还可以使用args接收传入的参数。
- 案例说明:
//1.在object中定义main方法 object Main_Demo1 { def main(args: Array[String]) { if(args.length > 0){ println("Hello, " + args(0)) }else{ println("Hello World!") } } } //2.使用继承App Trait ,将需要写在 main 方法中运行的代码 // 直接作为 object 的 constructor 代码即可, // 而且还可以使用 args 接收传入的参数。 object Main_Demo2 extends App{ if(args.length > 0){ println("Hello, " + args(0)) }else{ println("Hello World!") } }
6. scala中的数据数据类型
any是所有类型的超类,也称为顶级类型
-
anyVal(值类型的基类)
int short byte long double string char boolean Uint (9个) -
anyRef(引用类型的基类)
List map option yourclass …
注意:java类型中没有和scala中Nothing对应的类型
7. 懒加载
- scala中使用lazy关键字修饰变量,就是惰性变量,实现延迟加载,但只能是常量,只有在调用的时候,才会去实例化这个变量
- 例子:
- 正常:var str = {
println(“helloworld”)
}
当方法执行的时候自动输出helloworld - 懒加载的形式:lazy val str1 = {
println(“helloworld”)
}
只有当调用这个变量的时候才会输出helloworld - 好处:使用在比较耗时的业务中,如网络IO 磁盘IO
- 正常:var str = {
8.插值器
- s插值器:
val name:String=“cat”
println(s"she is name is ${name}")
打印结果为she is name is cat - f插值器: 对浮点型进行操作
val height=1.23568
println(f"身高是${height}%.2f")
打印结果为身高是1.23 - raw插值器:是输入字符串原样,不进行转义
println(raw"a\nb\n\tc")
打印结果为a\nb\n\tc(\n不进行换行,\t也不是制表符,直接原样打印)
9.访问修饰符
不指定修饰符的话,则默认public
- private: 私有的,仅类的内部可用。
- protected:受保护的,自己类和子类可以被访问,同一个包的其他类不可访问
- public:公共的,任何类都可访问
10.运算符
- 算术运算符
加 减 乘 除 取余(+,-,*,/,%) - 关系运算符
全等,不等,大于,小于,大于等于,小于等于(== ,!= ,>,<,>=, <=) - 逻辑运算符
与 或 非 (&& ,|| ,!) - 赋值运算符
等于 加等于 减等于 乘等于 除等于 取余等于(= ,+=, -= ,*= ,/= ,%= )
11.类型转换
值类型和字符串类型的转换
4. String转为Int(字符串中必须为数字)
val age:String =“123” age.toInt
5. Int 类型转换为String
val b:Int =123 b.toString
6. String类型转换为Float类型(字符串中必须为数字)
val c:String =“123.123” c.toFloat
细节注意:
在将String转化为基本类型时,确保能够有效的转换,将字符串"1.25"转成Int
val int1 = “1.25”.toInt //error
val int2 = “1.25”.toDouble.toInt //ok
隐式转换
-
在scala中有多种类型进行混合运算时,系统首先将所有的类型,转换成精度最大的那种数据类型,然后再进行计算
例子:
4.5f+10 ==>float、 -
当我们把精度大的类型赋值给精度小的类型时,就会出现错误,反之就是隐式转换.
-
(byte,short)和char之间是不会进行类型转换的
-
byte ,short ,char 三者是可以计算,计算的时候首先转换成Int类型
-
当进行数据的从大–>小的转换的时候,我们需要使用强制转换
-
强制转换只针对最近的操作数有效,往往需要使用小括弧,提升其优先级
-
Byte和Short类型,在运算的时候当做Int类型来处理
显示转换
它是自动转换类型的逆过程,将容量大的数据类型转换成容量小的数据类型,使用的时候要加上强制转换函数,但可能造成精度的降低和溢出,使用的时候 要格外的注意
案例:
val a:Int=2.345.toInt
val b:Int=2.345f.toInt
12.键盘输入
- Console.readLine() 读取一行,StdIn.readInt() 读取Int等等
- 转为maven(加入pom.xml)依赖:
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.12.2</version>
</dependency>
13.控制语句
if判断语句
- if 语句的语法格式如下:
if(布尔表达式)
{
// 如果布尔表达式为 true 则执行该语句块
} - if…else 语句对应的语法如下:
if(布尔表达式){
// 如果布尔表达式为 true 则执行该语句块
}else{
// 如果布尔表达式为 false 则执行该语句块
} - if…else if…else 语法格式如下
if(布尔表达式 1){
// 如果布尔表达式 1 为 true 则执行该语句块
}else if(布尔表达式 2){
// 如果布尔表达式 2 为 true 则执行该语句块
}else if(布尔表达式 3){
// 如果布尔表达式 3 为 true 则执行该语句块
}
···
else {
// 如果以上条件都为 false 执行该语句块
} - 嵌套判断
if(){
if(){
}
}
循环语句
scala的循环语句给我们提供了三类
- while
语法格式如下:
while(条件)
{
方法体
} - do while
do {
方法体
} while( 条件 );
注意:他是先执行一次方法体 - for
基本语法
for (循环条件) {
方法体
}
for的例子:
-
for (i <- 0 to 10) {
println(i)
}
打印0到10的数(包含0和10) -
for (i <- 0 until10) {
println(i)
}
打印0到9的数(包含0不含10) -
for (i <- list) {
println(i)
}
对list中的遍历,i为list中的每一项 -
for 循环保护式,也成条件判断式
基本案例://if i!=2 相当于我们的continue,它不是退出,而是跳过
for(i<-0 to 3 if i!=2){ //注意这里,有关键字的时候,没有分号
println(i)
} -
多条件循环
for(i<-0 to 3; j<- 0 to 3){ //注意这里,没有关键字的时候,一定要有分号
println(i+j)//相当于循环嵌套,i的每一次变化,都会执行j的所有变化
} -
循环返回值
基本语法:
yield :可有把for循环的内容放到一个Vector集合里面,并返回。- val Vector=for(i<- 0 to 10) yield i
println(list)
输出:Vector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - val vec = for (i <- 0 to 10) yield {
if (i % 2 == 0) {
i
} else {
“不是偶数”
}
}
输出:Vector(0, 不是偶数, 2, 不是偶数, 4, 不是偶数, 6, 不是偶数, 8, 不是偶数, 10)
- val Vector=for(i<- 0 to 10) yield i
-
循环跳出语句
循环控制语句(scala 中不支持break continue)
//创建Breakes对象
var look = new Breaks
// 在 breakable 中循环
loop.breakable{
while(true){
println(“这里只循环一次-------”)
loop.break;//跳出
}
}
14.元祖
- 概念:元祖是scala中一个非常有用的容器对象,用于存放不同类型的数据元素,不可变。
关键:不同类型,不可变 - 元祖的创建
var tuple = (1,“abc”,false);
val tuple1 = new Tuple2(1,2)
//最大可new Tuple22 - 元祖的操作
元组是通过下划线+下标索引进行访问的,并且是从1开始。
val tuple = (1, 2, 3, 4)
val a = tuple._1 + tuple._2 + tuple._3 + tuple._4
a =10//(1+2+3+4 )
15.数组
- 概念: 数组是一个存放相同类型元素的集合,分为可变数组和定长数组。
- 数组的创建方式
- 数组的操作使用
- 定长数组的赋值和访问 ,下标所引,从0开始
array(0) = “a”
array(1) = “b”
array(2) = “c”
println(array(0))
println(array(1))
println(array(2)) - b.变长数据的赋值和访问
arrayBuffer.append(1)
arrayBuffer.append(2)
arrayBuffer.append(3)
println(arrayBuffer(0))
println(arrayBuffer(1))
println(arrayBuffer(2))
- 定长数组的赋值和访问 ,下标所引,从0开始
- 数组的遍历
1. for (item <- array) {
println(item)
}
2. for(item <- 0 until){
println(array(item))
} - 数组的常用函数
e.数组的常用函数
//元素求和
println(arrayBuffer.sum)
//最大值
println(arrayBuffer.max)
//最小值
println(arrayBuffer.min)
//排序(默认是升序)
println(arrayBuffer.sorted)
//降序
println(arrayBuffer.sortWith(>))
16.集合
分类:
List(列表) :有序 可重复
Set(集合) :无序 不可重复
Map(映射) :键值对 (key 不可重复)
tuple(元祖):是不同类型值的集合
Vector(容器):是一个容器,可以保存不同数据对象
-
List(列表),有序 可重复
列表类似于元祖,他们所有的元素的类型都是一样
//1.字符串列表
val strings = List(“abc”,“d”,“e”)
//2.整数列表
val ints = List(1,2,3,4)
//3.二维列表
val intses = List(List(1,2,3,4),List(1,2,3,4))
//4.List的操作
for循环遍历打印列表中的元素
for(i<- 0 until strings.length ){
println( strings(i))
}
//
for (item <- list) {
println(item)
}
list.head:返回列表第一个元素
list.tail:返回一个列表 处理第一元素以外的其他元素
list.isEmpty:列表如果为空的时候放回出
list.last :获取最后一个元素
合并两个列表(两种方式)
val strss=strings:::strings01
val strss = List.concat(strings, strings01)
fill创建一个重复次数为n次的列表
val site = List.fill(3)(“gao”) 重复3次3的列表,每一项为元祖
//5.List和listBuffer的区别
//可变的
val buffer = ListBuffer[String] ()
//向可变的buffer中添加元素,并打印查看
buffer.append(“a”)
buffer.append(“b”)
buffer.append(“c”)
println(buffer)
//元素移除
buffer.remove(1) -
Set(集合)::没有重复的对象集合,所有元素都是唯一
特点:无序 ,不重复
//1.Set的定义(可变和不可变)
不可变集合定义
val set=Set(1,2,3)
可变集合的定义
val set = collection.mutable.Set[String] ()
//2.Set的操作
增加元素
//1.赋值
添加元素
set.add(“a”)
set.add(“b”)
set.add(“c”)
set+=“d”
减少元素
set.drop(1)
set-=“b”
//2.打印
set.foreach(item => {
println(item)
}) -
Map(映射)
Map映射是一种可迭代的键值对,(key/value)结构
特点:Key不可重复(重复后,会覆盖之前的value)
分类:可变和不可变
//不可变
var A:Map[Type,Type] = Map()
val map = Map(“key01” -> “value01”, “key02” -> “value02”)
//可变的
val map = collection.mutable.Map[String,String] ()
//添加
map.put("", “”)
//去除
map.remove(“k1”)
//获取所有的keys
println(map.keys)
//获取所有的value
println(map.values)
//判断是否为空
println(map.isEmpty)
//遍历
map.map(m => m._1 m._2 ···)遍历Value -
Vector是Scala标准包的一部分,我们可以直接使用,不需要导包.
无需使用new关键字就可以创建 Vector 容器实例。创建实例的同时我们可以放入元素
//创建Vector对象
val vec = Vector(1,2,3)
//Vector通过索引下标访问元素,Vector 的元素索引是从0开始的, 使用 圆括号将索引号括起来可以获得指定元素
println(vec(0))
//Vector的遍历
for (item <- vec) {
println(item)
}
//倒转 Vector
val vec = Vector(1, 2, 3)
println(vec)
println(vec.reverse)
//Vector的排序
val vec = Vector(1, 2, 3,1,4,7,1,3,9)
println(vec)
println(vec.sorted)
//返回第一元素
vec.head
//返回除了第一个元素的所有元素
vec.tail
17.类
在Scala中,类并不用声明为public类型
Scala源文件中可以包含多个类,所有这些类都具有共有可见性
类的定义
-
伴生类
class Person{}
-
//用val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
val id=“9527” -
//用var修饰的变量都既有getter,又有setter
var age:Int=18 -
//类私有字段,只能在类的内部使用或者伴生对象中访问
private var name : String = “唐伯虎” -
//类私有字段,访问权限更加严格的,该字段在当前类中被访问,在伴生对象里面也不可以访问
private[this] var pet = “小强” -
//伴生对象(这个名字和类名相同,叫伴生对象,是一个单例的对象)
object Person{
def main(args: Array[String]): Unit = {
val p=new Person//如果是下面的修改,发现下面有红线,说明val类型的不支持重新赋值,但是可以获取到值
//自动调用了getter和setter
//p.id = “123”
println(p.id)
//打印age
println(p.age)
//打印name,伴生对象中可以在访问private变量
println(p.name)
//由于pet字段用private[this]修饰,伴生对象中访问不到pet变量
//p.pet(访问不到)
}
}
方法的定义
def eat(food:String): Unit ={
println(s"吃${food}很爽啊----------")
}
def :定义方法的关键字
eat :方法名
Unit :返回值类型 可以使用return 也可以不使用 ,scala中尽量不使用
构造器
Scala中的每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。
注意:调用主构造器时候会执行主构造器内所有的语句。
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")
}
}
18.Scala面向对象编程之对象
Scala中的object
object 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;
在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的。
作用:
1.存放工具方法和常量
2.高效共享单个不可变的实例
3.单例模式
object SessionFactory{
//该部分相当于java中的静态块
val session=new Session
//在object中的方法相当于java中的静态方法
def getSession(): Session ={
session
}
}
Scala中的伴生对象
- 如果有一个class文件,还有一个与class同名的object文件,name就称这个object为class的伴生对象,class为object的伴生类。
- 伴生类和伴生对象必须存放在一个.scala文件中
- 伴生类和伴生对象的最大特点就是可以相互访问
- 举例说明:
//伴生类 class Dog { val id = 1 private var name = "bw" def printName(): Unit ={ //在Dog类中可以访问伴生对象Dog的私有属性 println(Dog.CONSTANT + name ) } } //伴生对象 object Dog { //伴生对象中的私有属性 private val CONSTANT = "汪汪汪 : " def main(args: Array[String]) { val p = new Dog //访问私有的字段name p.name = "123" p.printName() } } //执行结果 汪汪汪 : 123
Scala中的apply方法
-
object中非常重要的一个特殊方法,就是apply方法。
-
apply方法通常是在伴生对象中实现的,其目的是,通过伴生类中的构造函数功能,来实现伴生对象的构造函数功能。
-
通常我们在伴生对象中定义apply方法,当遇到类名(参数1,参数2····,参数n)时 apply方法就会被调用。
-
在创建伴生对象或者伴生类的对象时,通常不会使用 new 类名()方法,而是直接使用类名()方法,隐式的调用伴生对象的apply方法,这样会让对象创建的更加简洁。
-
举例说明:
- 简化创建对象例子
//伴生类 class Foo(name:String) { } //伴生对象 object Foo { def apply(foo: String) : Foo = { new Foo(foo) } } //测试类 object Client { def main(args: Array[String]): Unit = { val foo = Foo("Hello") } }
- 工厂方法
/** * Array 类的伴生对象中,就实现了可接收变长参数的 apply 方法, * 并通过创建一个 Array 类的实例化对象,实现了伴生对象的构造函数功能 */ // 指定 T 泛型的数据类型,并使用变长参数 xs 接收传参,返回 Array[T] 数组 // 通过 new 关键字创建 xs.length 长的 Array 数组 // 其实就是调用Array伴生类的 constructor进行 Array对象的初始化 // def apply[T: ClassTag](xs: T*): Array[T] = { // val array = new Array[T](xs.length) // var i = 0 // for (x <- xs.iterator) { array(i) = x; i += 1 } // array // } object ApplyDemo { def main(args: Array[String]) { //调用了Array伴生对象的apply方法 //def apply(x: Int, xs: Int*): Array[Int] //arr1中只有一个元素5 val arr1 = Array(5) //new了一个长度为5的array,数组里面包含5个null var arr2 = new Array(5) println(arr1.toBuffer) } }
19.Scala面向对象编程之继承
Scala中继承(extends)的概念
- Scala中,让子类继承父类,与Java一样,也是使用extends关键字。
- 继承就代表,子类可以继承父类的field和method,然后子类还可以在自己的内部实现父类没有的,子类特有的field和method,使用继承可以有效复用代码。
- 子类可以覆盖父类的field和method,但是如果父类用final修饰,或者field和method用final修饰,则,该类是无法被继承的,或者field和method是无法被覆盖的。
- private修饰的field和method不可以被子类继承,只能在类的内部使用。
- field必须要被定义成val的形式才能被继承,并且还要使用override关键字,var修饰的field是可变的,在子类中可以直接引用被赋值,不需要被继承。即val修饰的才允许被继承,var修饰的只允许被引用。继承就是改变、覆盖的意思,修改父类中val的field可以直接覆盖,修改var的field则要用setter。
- Java中的访问控制权,同样适用于Scala,但比Java更严格点
类内部 | 本包 | 子类 | 外部包 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | × | √ | × |
private | √ | × | × | × |
- 举例说明:
class Person1 {
val name=“super”
def getName=this.name
}
class Student1 extends Person1{
//继承加上关键字
override
val name=“sub”
//子类可以定义自己的field和method
val score=“A”
def getScore=this.score
}
Scala中override 和 super 关键字
- Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用override关键字。子类可以覆盖父类的val修饰的field,只要在子类中使用override关键字即可。
- override关键字可以帮助开发者今早的发现代码中的错误,比如,override修饰的父类方法名拼写错误。
- 此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用super关键字,显示的指出要调用的父类方法。
- 举例说明:
class Person2 {
private val name = "leo"
val age=50
def getName = this.name
}
class Student2 extends Person2{
private val score = "A"
//子类可以覆盖父类的 val field,使用override关键字
override
val age=30
def getScore = this.score
//覆盖父类非抽象方法,必须要使用 override 关键字
//同时调用父类的方法,使用super关键字
override def getName = "your name is " + super.getName
}
Scala中isInstanceOf 和 asInstanceOf
如果实例化了子类的对象,但是将其赋予了父类类型的变量,在后序的过程中,又需要将父类类型的变量转换为子类类型的变量,应该如何做
Class A extends class B
B b=new A
- 首先,需要使用 isInstanceOf 判断是否为指定类的对象,如果是的话,则可以使用 asInstanceOf 将对象转换为指定类型
- 注意:p.isInstanceOf[XX] 判断 p是否为xx类的实例,p.asInstanceOf[XX] 把 p 转换成 XX 类的实例。
- 注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用asInstanceOf去转换,则有可能会抛出异常。
- 注意:如果对象是 null,则 isInstanceOf 一定会返回 false,asInstanceOf 一定会返回 null。
- Scala与Java的类型转换和检查表:
Scala | Java |
---|---|
obj.isInstanceOf[C] | obj instanceof C |
obj.asInstanceOf[C] | (C)obj |
classOf[C] | C.class |
- 举例说明:
class Person3 {}
class Student3 extends Person3
object Student3{
def main (args: Array[String] ) {
val p: Person3 = new Student3
var s: Student3 = null
//如果对象是 null,则 isInstanceOf 一定返回 false
println (s.isInstanceOf[Student3])
// 判断 p 是否为 Student3 对象的实例
if (p.isInstanceOf[Student3] ) {
//把 p 转换成 Student3 对象的实例
s = p.asInstanceOf[Student3]
}
println (s.isInstanceOf[Student3] )
}
}
Scala中getClass 和 classOf
Class A extends class B
B b=new A b.getClass ==classOf[A]
B b=new B b.getClass ==classOf[B]
- isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。
- 如果要求精确地判断出对象就是指定类的对象,那么就只能使用getClass 和 classOf 。
- p.getClass可以精确地获取对象的类,classOf[xx] 可以用精确的获取类,然后使用 == 操作符即可精确的判断
- 举例说明:
class Person4 {} class Student4 extends Person4 object Student4{ def main(args: Array[String]) { val p:Person4=new Student4 //判断p是否为Person4类的实例 println(p.isInstanceOf[Person4])//true //判断p的类型是否为Person4类 println(p.getClass == classOf[Person4])//false //判断p的类型是否为Student4类 println(p.getClass == classOf[Student4])//true } }
Scala中抽象类
- 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式叫做抽象方法。
- 一个类中如果含有一个抽象方法和抽象field,就必须使用 abstract 将类声明为抽象类,改类是不可以被实例的。
- 在子类中覆盖类的抽象方法时,可以不加override关键字。
- 举例说明:
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)
}
}
注意:
2. 在scala中覆盖一个抽象类中字段时,可以不写override
3. 使用var定义的抽象字段只能使用var覆盖
4. 使用val定义的抽象字段只能使用val覆盖
5. 抽象类不一定有抽象字段或抽象方法,只需要添加abstract关键字
6. 有抽象字段一定是抽象类
7. 重写字段的实质是在重写字段的setter、getter方法
抽象类中可以有非抽象的方法
Scala中抽象field
- 如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field,则可以不加override。
- 举例说明:
abstract class Person10 (val name:String){
//抽象fields
val age:Int
}
class Student10(name: String) extends Person10(name) {
val age: Int = 50
}
20.Scala中面向对象编程之trait
将trait作为接口使用
- Scala 中的 trait 是一种特殊的概念。
- 首先先将trait作为接口使用,此时的trait就与Java中的接口(interface)非常类似。
- 在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可。
- 类可以使用extend关键字继承trait,注意,这里不是implement,而是extend,Scala中没有implement的概念,无论继承类还是trait,统一都是extend。
- 类继承后,必须实现其中的抽象方法,实现时,不需要使用override。
- Scala不支持队类进行多继承,但是支持多重继承trait,使用with关键字即可。
- trait中未被实现的方法默认是抽象方法,因此不需要在方法前加 abstract。
-
- 有父类:
class extends 特质 1 with 特质2 with 特质3- 没有父类:
class extend 父类 with 特质1 with 特质2 with 特质3
- 没有父类:
- 有父类:
- 举例说明:
trait HelloTrait {
def sayHello(): Unit
}
trait MakeFriendsTrait {
def makeFriends(c: Children): Unit
}
//多重继承 trait
class Children(val name: String) extends HelloTrait with MakeFriendsTrait with Serializable{
def sayHello() =println("Hello, " + this.name)
def makeFriends(c: Children) = println("Hello, my name is " + this.name + ", your name is " + c.name)
}
object Children{
def main(args: Array[String]) {
val c1=new Children("tom")
val c2=new Children("jim")
c1.sayHello()//Hello, tom
c1.makeFriends(c2)//Hello, my name is tom, your name is jim
}
}
在trait中定义具体的方法
- Scala 中的 trait不仅可以定义抽象方法,还可以定义具体的犯法,此时,trait更像是包含了通用方法的工具,可以认为trait还包含了类的功能。
- 举例说明:
/**
* 比如 trait 中可以包含很多子类都通用的方法,例如打印日志或其他工具方法等等。
* spark就使用trait定义了通用的日志打印方法;
*/
trait Logger {
def log(message: String): Unit = println(message)
}
class PersonForLog(val name: String) extends Logger {
def makeFriends(other: PersonForLog) = {
println("Hello, " + other.name + "! My name is " + this.name + ", I miss you!!")
this.log("makeFriends method is invoked with parameter PersonForLog[name = " + other.name + "]")
}
}
object PersonForLog{
def main(args: Array[String]) {
val p1=new PersonForLog("jack")
val p2=new PersonForLog("rose")
p1.makeFriends(p2)
//Hello, rose! My name is jack, I miss you!!
//makeFriens method is invoked with parameter PersonForLog[name = rose]
}
}
在trait中定义具体field
- Scala 中的 trait 可以定义具体的 field ,此时继承 trait 的子类就自动获得了 trait 中定义的 field 。
- 但是这种获取field的方式与继承的 class 的是不同的。如果继承 class 获取的 field ,实际上还是定义在父类中的。而继承trait获取的 field ,就直接被添加到了子类中。
- 举例说明:
trait PersonForField {
val age:Int=50
}
//继承 trait 获取的field直接被添加到子类中
class StudentForField(val name: String) extends PersonForField {
def sayHello = println("Hi, I'm " + this.name + ", my age is "+ age)
}
object StudentForField{
def main(args: Array[String]) {
val s=new StudentForField("tom")
s.sayHello
}
}
在trait中定义抽象field
- Scala 中的 trait 也能定义抽象 field ,而trait 中的具体方法也能基于抽象 field 编写。
- 继承 trait 的类,则必须覆盖 field ,提供具体的值。
- 举例说明:
trait SayHelloTrait {
val msg:String
def sayHello(name: String) = println(msg + ", " + name)
}
class PersonForAbstractField(val name: String) extends SayHelloTrait {
//必须覆盖抽象 field
val msg = "Hello"
def makeFriends(other: PersonForAbstractField) = {
this.sayHello(other.name)
println("I'm " + this.name + ", I want to make friends with you!!")
}
}
object PersonForAbstractField{
def main(args: Array[String]) {
val p1=new PersonForAbstractField("Tom")
val p2=new PersonForAbstractField("Rose")
p1.makeFriends(p2)
}
}
在实例对象指定混入某个trait
- 可在创建类的对象时,为该对象指定混入某个 trait ,且只有混入了 trait 的对象才具有 trait 中的方法,而其他类的对象则没有。
- 在创建对象时,使用with关键字指定混入某个 trait 。
- 举例说明:
trait LoggedTrait {
// 该方法为实现的具体方法
def log(msg: String) = {}
}
//必须写该trait
trait MyLogger extends LoggedTrait{
// 覆盖 log() 方法
override def log(msg: String) = println("log: " + msg)
}
class PersonForMixTraitMethod(val name: String) extends LoggedTrait {
def sayHello = {
println("Hi, I'm " + this.name)
log("sayHello method is invoked!")
}
}
object PersonForMixTraitMethod{
def main(args: Array[String]) {
val tom= new PersonForMixTraitMethod("Tom").sayHello //结果为:Hi, I'm Tom
// 使用 with 关键字,指定混入MyLogger trait
val rose = new PersonForMixTraitMethod("Rose") with MyLogger
rose.sayHello
// 结果为: Hi, I'm Rose
// 结果为: log: sayHello method is invoked!
}
}
trait 调用链
- Scala 中支持让类继承多个 trait 后,可一次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行super关键字即可。
- 类中调用多个trait中都有的这个方法时,首先会从最右边的 trait 开始执行,然后依次往左执行,形成一个调用链条。
- 这种特性非常强大,其实就是设计模式中责任链模式中的一种具体实现。
- 案例说明:
trait HandlerTrait {
def handle(data: String) = {println("last one")}
}
trait DataValidHandlerTrait extends HandlerTrait {
override def handle(data: String) = {
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandlerTrait extends HandlerTrait {
override def handle(data: String) = {
println("check signature: " + data)
super.handle(data)
}
}
class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
def sayHello = {
println("Hello, " + this.name)
this.handle(this.name)
}
}
object PersonForRespLine{
def main(args: Array[String]) {
val p=new PersonForRespLine("tom")
p.sayHello
//执行结果:
// Hello, tom
// check data: tom
// check signature: tom
// last one
}
}
trait 调用链
- Scala 中支持让类继承多个 trait 后,可一次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行super关键字即可。
- 类中调用多个trait中都有的这个方法时,首先会从最右边的 trait 开始执行,然后依次往左执行,形成一个调用链条。
- 这种特性非常强大,其实就是设计模式中责任链模式中的一种具体实现。
- 案例说明:
trait HandlerTrait {
def handle(data: String) = {println("last one")}
}
trait DataValidHandlerTrait extends HandlerTrait {
override def handle(data: String) = {
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandlerTrait extends HandlerTrait {
override def handle(data: String) = {
println("check signature: " + data)
super.handle(data)
}
}
class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
def sayHello = {
println("Hello, " + this.name)
this.handle(this.name)
}
}
object PersonForRespLine{
def main(args: Array[String]) {
val p=new PersonForRespLine("tom")
p.sayHello
//执行结果:
// Hello, tom
// check data: tom
// check signature: tom
// last one
}
}
trait 继承 class
- 在 Scala 中 trait 也可以继承 class,此时这个 class 就会成为所有继承该 trait 的子类的超级父类。
- Class A
- Trait B extends A
- Class C extends B
- Trait D extends B
- 举例说明:
class MyUtil {
def printMsg(msg: String) = println(msg)
}
trait Logger_Two extends MyUtil {
def log(msg: String) = this.printMsg("log: " + msg)
}
class Person_Three(val name: String) extends Logger_Two {
def sayHello {
this.log("Hi, I'm " + this.name)
this.printMsg("Hello, I'm " + this.name)
}
}
object Person_Three{
def main(args: Array[String]) {
val p=new Person_Three("Tom")
p.sayHello
//执行结果:
// log: Hi, I'm Tom
// Hello, I'm Tom
}
}
21、模式匹配和样例类
Scala有一个十分强大的模式匹配机制,可以应用到很多场合:如switch 语句、类型检查等。并且 Scala 还提供了样例类,对模式匹配进行了优化,可以快速进行匹配。
匹配字符串
import scala.util.Random
object CaseDemo01 extends App{
val arr = Array("hadoop", "zookeeper", "spark")
val name = arr(Random.nextInt(arr.length))
name match {
case "hadoop" => println("大数据分布式存储和计算框架...")
case "zookeeper" => println("大数据分布式协调服务框架...")
case "spark" => println("大数据分布式内存计算框架...")
case _ => println("我不认识你...")
}
}
匹配类型
import scala.util.Random
object CaseDemo01 extends App{
val arr = Array("hello", 1, 2.0, CaseDemo)
val v = arr(Random.nextInt(4))
println(v)
v match {
case x: Int => println("Int " + x)
case y: Double if(y >= 0) => println("Double "+ y)
case z: String => println("String " + z)
case _ => throw new Exception("not match exception")
}
}
注意:case y:Double if(y>=0)=> …
模式匹配的时候还可以添加守卫条件。如不符合守卫条件,将调入case _中
匹配数组、元组、集合
object CaseDemo03 extends App{
val arr = Array(1, 3, 5)
arr match {
case Array(1, x, y) => println(x + " " + y)
//匹配第一个元素为1 然后接着有2个元素的 数组
case Array(0) => println("only 0")
//匹配只有一个0的数组
case Array(0, _*) => println("0 ...")
//匹配第一个元素为0的数组
case _ => println("something else")
}
val lst = List(3, -1)
lst match {
case 0 :: Nil => println("only 0")
//匹配只有一个0的list
case x :: y :: Nil => println(s"x: $x y: $y")
//匹配 有2个元素的ist
case 0 :: tail => println("0 ...")
//匹配开头的list
case _ => println("something else")
//匹配其他
}
val tup = (1, 3, 7)
tup match {
case (1, x, y) => println(s"1, $x , $y")
case (_, z, 5) => println(z)
case _ => println("else")
}
}
注意:在Scala中列表要么为空(Nill表示空列表)要么是一个head元素加上一个tail列表。
9::List(5,2) ::操作符是将给定的头和尾创建一个新的列表
注意::: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))
样例类
- 在 Scala 中样例类是一种特殊的类,可用于模式匹配。
- 定义形式:
- case class 类型,是多例的,后面要跟构造参数。
case class Student(name:String) - case object 类型,是单例的。
case object Person
- case class 类型,是多例的,后面要跟构造参数。
import scala.util.Random
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
object CaseDemo04 extends App{
val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))
arr(Random.nextInt(arr.length)) match {
case SubmitTask(id, name) => {
println(s"$id, $name")
}
case HeartBeat(time) => {
println(time)
}
case CheckTimeOutTask => {
println("check")
}
}
}
Option类型
在 Scala 中 Option 类型用样例类来表示可能存在或者可能不存在的值(Option的子类有 Some 和 None)。Some包装了某个值,None 表示可能没有值
object OptionDemo {
def main(args: Array[String]) {
val map = Map("a" -> 1, "b" -> 2)
val v = map.get("b") match {
case Some(i) => i
case None => 0
}
println(v)
//更好的方式
val v1 = map.getOrElse("c", 0)
println(v1)
}
}
偏函数
被包在花括号内没有 match 的一组 case 语句是一个偏函数,它是 PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回结果类型,常用作输入模式匹配,偏函数最大的特点就是它只接受和处理其参数定义域的一个子集。
object PartialFuncDemo {
val func1: PartialFunction[String, Int] = {
case "one" => 1
case "two" => 2
case _ => -1
}
def func2(num: String) : Int = num match {
case "one" => 1
case "two" => 2
case _ => -1
}
def main(args: Array[String]) {
println(func1("one"))
println(func2("one"))
}
}
22、Scala 中的协变、逆变、非变
协变、逆变、非变介绍
Array[String] Array[Object]
协变和逆变主要是用来解决参数化类型的泛化问题。Scala 的协变与逆变是非常有特色的,完全解决了 Java 中泛型的一大缺憾,举例来说,Java 中,如果有 A 是 B 的子类,但 Card[A] 却不是 Card[B] 的子类。而 Scala 中,只要灵活使用协变与逆变,就可以解决此类 Java 泛型问题。
由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?Java中这种情况时不可泛化的,然而 Scala 提供了三个选择 即协变(“+”)、逆变(“-”)和非变
-
trait Queue[T] {}
这是非变情况。这种情况下,当类型 B 是类型 A 的子类型,则Queue[B]与Queue[A]没有任何从属关系,这种情况是和Java一样的。
-
trait Queue[+T] {}
这是协变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]也可以认为是Queue[A]的子类型,即Queue[B]可以泛化为Queue[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。
-
trait Queue[-T] {}
这是逆变情况。这种情况下,当类型B是类型A的子类型,则Queue[A]反过来可以认为是Queue[B]的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。
协变、逆变、非变总结
- C[+T] : 如果A是B的子类,那么 C[A] 是C[B]的子类。
- C[-T] : 如果A是B的子类,那么C[B] 是 C[A]的子类
- C[T] : 无论A和B是什么关系,C[A] 和 C[B] 没有从属关系。
案例说明:
class Super
class Sub extends Super
//协变
class Temp1[+A](title: String)
//逆变
class Temp2[-A](title: String)
//非变
class Temp3[A](title: String)
object Covariance_demo{
def main(args: Array[String]) {
//支持协变 Temp1[Sub]还是Temp1[Super]的子类
val t1: Temp1[Super] = new Temp1[Sub]("hello scala!!!")
//支持逆变 Temp1[Super]是Temp1[Sub]的子类
val t2: Temp2[Sub] = new Temp2[Super]("hello scala!!!")
//支持非变 Temp3[Super]与Temp3[Sub]没有从属关系,如下代码会报错
//val t3: Temp3[Sub] = new Temp3[Super]("hello scala!!!")
//val t4: Temp3[Super] = new Temp3[Sub]("hello scala!!!")
println(t1.toString)
println(t2.toString)
}
}
23、Scala中的上下界
4.1. 上界、下界介绍
? extends T ? super T (这是Java中的表示形式)
- 在指定泛型类型时,有时候需要界定泛型类型的范围,而不是接收任意类型,比如,要求某个泛型类型,这样在程序中就可以放心的调用父类的方法,程序才能正常的使用与运行。此时,就可以使用上下边界 Bounds 的特性。
- Scala 的上下边界特性允许泛型类型是某个类的子类,或者某个类的父类。
U >: T ? super T
这是类型下界的定义,也就是 U 必须是类型 T 的父类(或者本身,自己也可以认为是自己的父类)
S <: T ? extends T
这是类型上界 的定义,也就是 S 必须是类型 T 的子类(或者本身,自己也可以认为是自己的子类)
小知识
2.获取字符串首尾字母
var a = "abc;
首:a(0),a.head,a.charArt(0),a.take(1)。
尾:a(a.length-1),a.last,a.charArt(a.length-1),a.take(a.length),a.reverse(0),a.reverse.head(),a.reverse.charArt(0),a.reverse.take(1)。
2. 随机数字生成
val i = Random.nextInt(10)