一、变量和数据类型
1、变量
说明:在Scala中声明一个变量时,可以不指定类型,编译器根据值确定
var | val 变量名 [: 变量类型] = 变量值
- 声明变量时,类型可以省略(编译器自动推导,即类型推导)
- 类型确定后,就不能修改,说明Scala是强数据类型语言。
- 变量声明时,需要初始值
object TestVar { def main(args: Array[String]): Unit = { //(1)声明变量时,类型可以省略(编译器自动推导,即类型推导) var age = 18 age = 30 //(2)类型确定后,就不能修改,说明Scala是强数据类型语言。 // age = "tom" // 错误 //(3)变量声明时,需要初始值 // var name //错误 } }
- 在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。
object TestVar { def main(args: Array[String]): Unit = { var num1 = 10 // 可变 val num2 = 20 // 不可变 num1 = 30 // 正确 //num2 = 100 //错误,因为num2是val修饰的 } }
- val修饰的变量在编译后,等同于加上final通过反编译看下底层代码
通过反编译软件,得到对应的底层的.class是object TestVar { var num1 = 10 // 可变 val num2 = 20 // 不可变 def main(args: Array[String]): Unit = { num1 = 30 // 正确 //num2 = 100 //错误,因为num2是val修饰的 } }
public final class TestVar$ { public static final MODULE$; private int num1; private final int num2;
- var修饰的对象引用可以改变,val修饰的则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
object TestVar { def main(args: Array[String]): Unit = { // p1是var修饰的,p1的属性可以变,而且p1本身也可以变 var p1 = new Person() p1.name = "dalian" p1 = null // p2是val修饰的,则p2的属性可以变,但是p2本身不可变(即p2的内存地址不能变) val p2 = new Person() p2.name="xiaolian" // p2 = null // 错误的,因为p2是val修饰的 } } class Person{ var name : String = "jinlian" }
2、键盘输入
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
import scala.io.StdIn
object TestInput {
def main(args: Array[String]): Unit = {
// 1 输入姓名
println("input name:")
var name = StdIn.readLine()
// 2 输入年龄
println("input age:")
var age = StdIn.readShort()
// 3 输入薪水
println("input sal:")
var sal = StdIn.readDouble()
// 4 打印
println("name=" + name)
println("age=" + age)
println("sal=" + sal)
}
}
3、数据类型
- Scala中一切数据都是对象,都是Any的子类。
- Scala中数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef),不管是值类型还是引用类型都是对象。
- Scala数据类型仍然遵守,低精度的值类型向高精度值类型,自动转换(隐式转换)
- Scala特殊的类型之Null,它只有一个实例就是Null,它是所有引用类型(AnyRef)的子类。
- Scala特殊类型之Nothing,是所有数据类型的子类,主要在一个函数没有正常返回值使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。
3.1 整数类型(Byte、Short、Int、Long)
Scala的整数类型就是用于存放整数值的,比如12,30,3456等等。
数据类型 | 描述 |
---|---|
Byte [1] | 8位有符号补码整数。数值区间为 -128 到 127 |
Short [2] | 16位有符号补码整数。数值区间为 -32768 到 32767 |
Int [4] | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long [8] | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1 |
3.2 浮点类型(Float、Double)
Scala的浮点类型可以表示一个小数,比如123.4f,7.8,0.12等等。
数据类型 | 描述 |
---|---|
Float [4] | 32 位, IEEE 754标准的单精度浮点数 |
Double [8] | 64 位 IEEE 754标准的双精度浮点数 |
Scala的浮点型常量默认为Double型,声明Float型常量,须后加‘f’或‘F’。
object TestDataType {
def main(args: Array[String]): Unit = {
// 建议,在开发中需要高精度小数时,请选择Double
var n7 = 2.2345678912f
var n8 = 2.2345678912
println("n7=" + n7)
println("n8=" + n8)
}
}
3.3 字符类型(Char)
字符类型可以表示单个字符,字符类型是Char,16位无符号Unicode字符(2个字节),区间值为U+0000到U+FFFF。
- 字符常量是用单引号 ’ ’ 括起来的单个字符。
- 可以直接给Char赋一个整数,然后输出时,会按照对应的unicode字符输出
object TestCharType { def main(args: Array[String]): Unit = { //(1)字符常量是用单引号 ' ' 括起来的单个字符。 var c1: Char = 'a' println("c1=" + c1) //(2)可以直接给Char赋一个整数,然后输出时,会按照对应的unicode字符输出 println("c1码值=" + c1.toInt) } }
- Char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码。
object TestCharType { def main(args: Array[String]): Unit = { var c2: Char = 98 // 正确,因为直接将一个数值给char,编译器只判断是否越界 var c3: Char = 'a' + 1 // 错误,Int高->char低,编译器判断类型 var c4: Char = ('a' + 1).toChar } }
- \t :一个制表位,实现对齐的功能
- \n :换行符
- \ :表示\
- " :表示"
object TestCharType { def main(args: Array[String]): Unit = { //(4)\t :一个制表位,实现对齐的功能 println("姓名\t年龄") //(5)\n :换行符 println("西门庆\n潘金莲") //(6)\\ :表示\ println("c:\\岛国\\avi") //(7)\" :表示" println("同学们都说:\"大海哥最帅\"") } }
3.4 布尔类型:Boolean
- 布尔类型也叫Boolean类型,Booolean类型数据只允许取值true和false
- boolean类型占1个字节。
3.5 Unit类型、Null类型和Nothing类型
数据类型 | 描述 |
---|---|
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null , Null 类型只有一个实例值null |
Nothing | Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
- Null类:Null类只有一个实例对象,Null类似于Java中的null引用。Null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
object TestDataType { def main(args: Array[String]): Unit = { //null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal) var n1: Int = null // 错误 println("n1:" + n1) var cat = new Cat(); cat = null // 正确 } } class Cat { }
- Unit类:Unit类型用来标识过程,也就是没有明确返回值的函数。由此可见,Unit类似于Java里的void。Unit只有一个实例:( ),这个实例也没有实质意义
object TestSpecialType { def main(args: Array[String]): Unit = { def sayOk : Unit = { // unit表示没有返回值,即void println("say ok") } sayOk } }
- Nothing类:Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
object TestSpecialType { def main(args: Array[String]): Unit = { def test() : Nothing={ throw new Exception() } test } }
4、数值类型间转换
4.1 数值类型自动转换
当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
- 当我们把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
- (byte,short)和char之间不会相互自动转换。
- byte,short,char他们三者可以计算,在计算时首先转换为int类型。
object TestValueTransfer {
def main(args: Array[String]): Unit = {
//(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数值类型,然后再进行计算。
var n = 1 + 2.0
println(n) // n 就是Double
//(2)当我们把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
var n2 : Long = 1L
//var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低精度。
//(3)(byte,short)和char之间不会相互自动转换。
var n4 : Byte = 1
//var c1 : Char = n4 //错误
//(4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。
var n5 : Byte = 1
var c2 : Char = 1
// var n : Short = n5 + c2 //当n5 + c2 结果类型就是int
// var n6 : Short = 10 + 90 //错误
var n7 : Short = 100 //正确
}
}
Scala还提供了非常强大的隐式转换机制(隐式函数,隐式类等)类似 数值类型自动转换。
4.2 强制类型转换
强制类型转换是自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
- java : int num = (int)2.5
- scala : var num : Int = 2.7.toInt
- 当进行数据的从大——>小,就需要使用到强制转换
- 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
object TestForceTransfer { def main(args: Array[String]): Unit = { //(1)当进行数据的从大——>小,就需要使用到强制转换 var n1: Int = 2.5.toInt // 这个存在精度损失 //(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级 var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1 = 36 var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt = 44 println("r1=" + r1 + " r2=" + r2) } }
- Char类型可以保存Int的常量值,但不能保存Int的变量值,需要强转
- Byte和Short类型在进行运算时,当做Int类型处理。
object TestForceTransfer { def main(args: Array[String]): Unit = { //(3)Char类型可以保存Int的常量值,但不能保存Int的变量值,需要强转 var c2: Char = 98 // 正确,因为直接将一个数值给char,编译器只判断是否越界 var c3: Char = 'a' + 1 // 错误,Int高->char低,编译器判断类型 var c4: Char = ('a' + 1).toChar //(4)Byte和Short类型在进行运算时,当做Int类型处理。 var a : Short = 5 // a = a-2 // 错误, Int->Short var b : Byte = 3 // b = b + 4 // 错误,Int->Byte } }
5、数值类型和String类型间转换
在程序开发中,我们经常需要将基本数值类型转成String类型。或者将String类型转成基本数值类型。
- 基本类型转String类型(语法:将基本类型的值+"" 即可)
- String类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
object TestStringTransfer { def main(args: Array[String]): Unit = { //(1)基本类型转String类型(语法:将基本类型的值+"" 即可) var str1 : String = true + "" var str2 : String = 4.5 + "" var str3 : String = 100 +"" //(2)String类型转基本数值类型(语法:调用相关API) var s1 : String = "12" var n1 : Byte = s1.toByte var n2 : Short = s1.toShort var n3 : Int = s1.toInt var n4 : Long = s1.toLong } }
- 在将String类型转成基本数值类型时,要确保String类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数。
二 、集合/Collection(序列Seq、Set集、映射Map)
1、集合简介
Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质。
对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,分别位于以下两个包:
- 不可变集合:scala.collection.immutable
- 可变集合: scala.collection.mutable
Scala不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。
可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。
object TestList {
def main(args: Array[String]): Unit = {
//不可变List
val immutableList: List[Int] = List(1, 2, 3, 4, 5)
//对不可变List进行修改,在头部添加一个元素0
val newImmutableList: List[Int] = 0 +: immutableList
println(immutableList)
println(newImmutableList)
//可变List
val mutableList: ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 5)
//对可变List进行修改,在头部添加一个元素0
val newMutableList: ListBuffer[Int] = 0 +=: mutableList
println(mutableList)
println(newMutableList)
}
}
- Set、Map是Java中也有的集合
- Seq是Java没有的,我们发现List归属到Seq了,因此这里的List就和Java不是同一个概念了
- 我们前面的for循环有一个 1 to 3,就是IndexedSeq下的Vector
- String也是属于IndexeSeq
- 我们发现经典的数据结构比如Queue和Stack被归属到LinerSeq
- 大家注意Scala中的Map体系有一个SortedMap,说明Scala的Map可以支持排序
- IndexSeq和LinearSeq的区别:
- IndexSeq是通过索引来查找和定位,因此速度快,比如String就是一个索引集合,通过索引即可定位
- LineaSeq是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找
2、Seq集合
2.1 Array
2.1.1 不可变Array
val arr1 = new Array[Int](10)
- new是关键字
- [Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定Any
- (10) 表示数组的大小,确定后就不可以变化
val arr1 = Array(1, 2)
- 在定义数组时,直接赋值
- 使用apply方法创建数组对象
2.1.2 可变ArrayBuffer
val arr01 = ArrayBuffer[Any](3, 2, 5)
- [Any]存放任意数据类型
- (3, 2, 5)初始化好的三个元素
- ArrayBuffer需要引入scala.collection.mutable.ArrayBuffer
- ArrayBuffer是有序的集合
- 增加元素使用的是append方法(),支持可变参数
2.1.3 不可变Array与可变Array的转换
arr1.toBuffer //不可长数组转可变数组
arr2.toArray //可变数组转不可变数组
- arr2.toArray返回结果才是一个不可变数组,arr2本身没有变化
- arr1.toBuffer返回结果才是一个可变数组,arr1本身没有变化
object TestArrayBuffer {
def main(args: Array[String]): Unit = {
//(1)创建一个空的可变数组
val arr2 = ArrayBuffer[Int]()
//(2)追加值
arr2.append(1, 2, 3)
println(arr2) // 1,2,3
//(3)ArrayBuffer ==> Array
//(3.1)arr2.toArray 返回的结果是一个新的定长数组集合
//(3.2)arr2它没有变化
val newArr = arr2.toArray
println(newArr)
//(4)Array ===> ArrayBuffer
//(4.1)newArr.toBuffer 返回一个变长数组 newArr2
//(4.2)newArr 没有任何变化,依然是定长数组
val newArr2 = newArr.toBuffer
newArr2.append(123)
println(newArr2)
}
}
2.2 List
2.2.1 不可变List
object TestList {
def main(args: Array[String]): Unit = {
//(1)List默认为不可变集合
//(2)创建一个List(数据有顺序,可重复)
val list: List[Int] = List(1,2,3,4,3)
val list0: List[Int] = (1 to 5).toList
//(7)空集合Nil
val list5 = 1::2::3::4::Nil
//(4)List增加数据
//(4.1)::的运算规则从右向左
//val list1 = 5::list
val list1 = 7::6::5::list
//(4.2)添加到第一个元素位置
val list2 = list.+:(5)
//(5)集合间合并:将一个整体拆成一个一个的个体,称为扁平化
val list3 = List(8,9)
//val list4 = list3::list1
val list4 = list3:::list1
//(6)取指定数据
println(list(0))
//(3)遍历List
//list.foreach(println)
//list1.foreach(println)
//list3.foreach(println)
//list4.foreach(println)
list5.foreach(println)
}
}
2.2.2 可变ListBuffer
import scala.collection.mutable.ListBuffer
object TestList {
def main(args: Array[String]): Unit = {
//(1)创建一个可变集合
val buffer = ListBuffer(1,2,3,4)
//(2)向集合中添加数据
buffer.+=(5)
//(3)打印集合数据
buffer.foreach(println)
}
}
2.3 Vector
Vector是ArrayBuffer的不可变版本,是一个带下标的序列,我们可以通过下标(索引号),来访问Vector中的元素。
2.4 Range
Range表示一个整数序列
3、Set集合
默认情况下,Scala使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包
3.1 不可变Set
object TestSet {
def main(args: Array[String]): Unit = {
//(1)Set默认是不可变集合,数据无序
val set = Set(1,2,3,4,5,6)
//(2)数据不可重复
val set1 = Set(1,2,3,4,5,6,3)
//(3)遍历集合
for(x<-set1){
println(x)
}
}
}
3.2 可变Set
object TestSet {
def main(args: Array[String]): Unit = {
//(1)创建可变集合
val set = mutable.Set(1,2,3,4,5,6)
//(3)集合添加元素
set += 8
//(4)向集合中添加元素,返回一个新的Set
val ints = set.+(9)
println(ints)
println("set2=" + set)
//(5)删除数据
set-=(5)
//(2)打印集合
set.foreach(println)
println(set.mkString(","))
}
}
4、Map集合
Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的。
4.1 不可变Map
object TestMap {
def main(args: Array[String]): Unit = {
// Map
//(1)创建不可变集合Map
val map = Map( "a"->1, "b"->2, "c"->3 )
//(3)访问数据
for (elem <- map.keys) {
// 使用get访问map集合的数据,会返回特殊类型Option(选项):有值(Some),无值(None)
println(elem + "=" + map.get(elem).get)
}
//(4)如果key不存在,返回0
println(map.get("d").getOrElse(0))
println(map.getOrElse("d", 0))
//(2)循环打印
map.foreach((kv)=>{
println(kv)})
}
}
4.2 可变Map
object TestSet {
def main(args: Array[String]): Unit = {
//(1)创建可变集合
val map = mutable.Map( "a"->1, "b"->2, "c"->3 )
//(3)向集合增加数据
map.+=("d"->4)
// 将数值4添加到集合,并把集合中原值1返回
val maybeInt: Option[Int] = map.put("a", 4)
println(maybeInt.getOrElse(0))
//(4)删除数据
map.-=("b", "c")
//(5)修改数据
map.update("d",5)
//(2)打印集合
map.foreach((kv)=>{
println(kv)})
}
}
5、元组
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组。
注意:元组中最大只能有22个元素。
object TestTuple {
def main(args: Array[String]): Unit = {
//(1)声明元组的方式:(元素,元素2,元素3)
val tuple: (Int, String, Boolean) = (40,"bobo",true)
//(2)访问元组
//(2.1)通过元素的顺序进行访问,调用方式:_顺序号
println(tuple._1)
println(tuple._2)
println(tuple._3)
//(2.2)通过索引访问数据
println(tuple.productElement(0))
//(2.3)通过迭代器访问数据
for (elem <- tuple.productIterator) {
println(elem)
}
//(3)Map中的键值对其实就是元组,只不过元组的元素个数为2,称之为对偶
val map = Map("a"->1, "b"->2, "c"->3)
map.foreach(tuple=>{
println(tuple._1 + "=" + tuple._2)})
}
}
6、集合常用函数
6.1 基本属性和常用操作
- (1)获取集合长度:length
- (2)获取集合大小:size
- (3)循环遍历:foreach
- 方法foreach使用函数作为参数。这个函数的定义应该使用一个集合元素作为输入参数,同时不应该有返回值。并且这个函数的输入参数类型必须是集合中的元素类型。
- 当foreach方法运行的时候,它每次把一个元素从集合中取出作为参数传入到函数中,直到所有的元素都遍历完毕。
- foreach负责遍历集合中的每一个元素,遍历到每一个元素的时候具体要执行什么动作则是调用者通过传入函数来确定
- 一个通用的用来打印集合信息的例子:
这是一种函数定义的普通写法,但其实Scala可以推断出i的类型为Int,所以没有必要写出i:Int,如下:scala> val x = Vector(1,2,3) x: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) scala> x.foreach((i:Int) => println(i)) 1 2 3
还有一种更为简洁的写法,这种方式用到了_这种通配符来替代一个临时变量i:scala> x.foreach(i => println(i)) 1 2 3
在这种情况下,函数字面量包含一个带单个参数的声明,它可以写成更为简洁的形式:scala> x.foreach(println _) 1 2 3
只要你的函数只有一个参数,这个参数的类型是和集合元素类型一致的,并且函数没有任何返回值,那么你就可以调用foreach方法。接下来的例子中,printit方法接受一个char类型的参数,然后对它执行若干操作,同时没有返回值:scala> x.foreach(println) 1 2 3
如果这个函数你只使用一次,那么就没有必要去声明它,直接使用函数字面量就行了:scala> def printIt(c:Char){ println(c)} printIt: (c: Char)Unit scala> "HAL".foreach(printIt) H A L
除了可以用于有序集合外,foreach还可以用于Map类,当使用时函数的入参是一个二元祖(key,value):scala> "HAL".foreach((c:Char) => println(c)) H A L
通常情况下,使用下面“模式匹配”的写法更能表达出程序的含义:scala> val m = Map(1->"a",2->"b",3->"c") m: scala.collection.immutable.Map[Int,String] = Map(1 -> a, 2 -> b, 3 -> c) scala> m.foreach(x => println(x._1 + "->" + x._2)) 1->a 2->b 3->c
movieRatings.foreach { case(movie, rating) => println(s"key: $movie, value: $rating") }
- (4)迭代器:for (elem <- list.iterator) { }
- (5)生成字符串:mkString
- (6)是否包含:contains
object TestList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
//(1)获取集合长度
println(list.length)
//(2)获取集合大小
println(list.size)
//(3)循环遍历
list.foreach(println)
//(4)迭代器
for (elem <- list.iterator) {
println(elem)
}
//(5)生成字符串
println(list.mkString(","))
//(6)是否包含
println(list.contains(3))
}
}
6.2 衍生集合
- (01)获取集合的头head
- (02)获取集合的尾(不是头就是尾)tail
- (03)集合最后一个数据 last
- (04)集合初始数据(不包含最后一个)
- (05)反转
- (06)取前(后)n个元素
- (07)去掉前(后)n个元素
- (08)并集
- (09)交集
- (10)差集
- (11)拉链
- (12)滑窗
object TestList {
def main(args: Array[String]): Unit = {
val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
val list2: List[Int] = List(4, 5, 6, 7, 8, 9, 10)
//(1)获取集合的头
println(list1.head)
//(2)获取集合的尾(不是头的就是尾)
println(list1.tail)
//(3)集合最后一个数据
println(list1.last)
//(4)集合初始数据(不包含最后一个)
println(list1.init)
//(5)反转
println(list1.reverse)
//(6)取前(后)n个元素
println(list1.take(3))
println(list1.takeRight(3))
//(7)去掉前(后)n个元素
println(list1.drop(3))
println(list1.dropRight(3))
//(8)并集
println(list1.union(list2))
//(9)交集
println(list1.intersect(list2))
//(10)差集
println(list1.diff(list2))
//(11)拉链 注:如果两个集合的元素个数不相等,那么会将同等数量的数据进行拉链,多余的数据省略不用
println(list1.zip(list2))
//(12)滑窗
list1.sliding(2, 5).foreach(println)
}
}
6.3 集合计算初级函数
- (1)求和
- (2)求乘积
- (3)最大值
- (4)最小值
- (5)排序
object TestList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 5, -3, 4, 2, -7, 6)
//(1)求和
println(list.sum)
//(2)求乘积
println(list.product)
//(3)最大值
println(list.max)
//(4)最小值
println(list.min)
//(5)排序
// (5.1)按照元素大小排序
println(list.sortBy(x => x))
// (5.2)按照元素的绝对值大小排序
println(list.sortBy(x => x.abs))
// (5.3)按元素大小升序排序
println(list.sortWith((x, y) => x < y))
// (5.4)按元素大小降序排序
println(list.sortWith((x, y) => x > y))
}
}
6.4 集合计算高级函数:map(转化/映射)、filter(过滤)、reduce(规约)、fold(折叠)
- (1)过滤:filter
- (2)转化/映射:map
- (3)扁平化:flatten
- (4)扁平化+映射 注:flatMap(注:flatMap相当于先进行map操作,再进行flatten操作)
- (5)分组:groupBy
object TestList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val wordList: List[String] = List("hello world", "hello tom", "hello scala")
//(1)过滤
println("list.filter(x => x % 2 == 0) = " + list.filter(x => x % 2 == 0))
//(2)转化/映射
println("list.map(x => x + 1) = " + list.map(x => x + 1))
//(3)扁平化
println("nestedList.flatten = " + nestedList.flatten)
//(4)扁平化+映射 注:flatMap相当于先进行map操作,在进行flatten操作
println("wordList.flatMap(x => x.split(\" \")) = " + wordList.flatMap(x => x.split(" ")))
//(5)分组
println("list.groupBy(x => x % 2) = " + list.groupBy(x => x % 2))
}
}
打印结果:
list.filter(x => x % 2 == 0) = List(2, 4, 6, 8)
list.map(x => x + 1) = List(2, 3, 4, 5, 6, 7, 8, 9, 10)
nestedList.flatten = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
wordList.flatMap(x => x.split(" ")) = List(hello, world, hello, tom, hello, scala)
list.groupBy(x => x % 2) = Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(2, 4, 6, 8))
- (6)简化(规约):reduce,通过指定的逻辑将集合中的数据进行聚合,从而减少数据,最终获取结果。
object TestReduce {
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4)
// 将数据两两结合,实现运算规则
val i: Int = list.reduce( (x,y) => x-y )
println("i = " + i)
// 从源码的角度,reduce底层调用的其实就是reduceLeft
//val i1 = list.reduceLeft((x,y) => x-y)
// ((4-3)-2-1) = -2
val i2 = list.reduceRight((x,y) => x-y)
println(i2)
}
}
- (7)折叠:fold,化简的一种特殊情况。
object TestFold {
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4)
// fold方法使用了函数柯里化,存在两个参数列表
// 第一个参数列表为 : 零值(初始值)
// 第二个参数列表为:
// fold底层其实为foldLeft
val i = list.foldLeft(1)((x,y)=>x-y)
val i1 = list.foldRight(10)((x,y)=>x-y)
println(i) // -9
println(i1) // 8
}
}
object TestFold {
def main(args: Array[String]): Unit = {
// 两个Map的数据合并
val map1 = mutable.Map("a"->1, "b"->2, "c"->3)
val map2 = mutable.Map("a"->4, "b"->5, "d"->6)
val map3: mutable.Map[String, Int] = map2.foldLeft(map1) {
(map, kv) => {
val k = kv._1
val v = kv._2
map(k) = map.getOrElse(k, 0) + v
map
}
}
println(map3) // Map(b -> 7, d -> 6, a -> 5, c -> 3)
}
}
三、流程控制
1、分支控制if-else
让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支。基本与Java一致,Scala与Java不同之处:
- Scala中if else表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容。
object TestIfElse { def main(args: Array[String]): Unit = { println("input your age") var age = StdIn.readInt() var res = if(age > 18){ "您以成人" }else{ "小孩子一个" } println(res) } }
- 如果大括号{}内的逻辑代码只有一行,大括号可以省略。
- Scala中是没有三元运算符,因为可以这样简写
object TestIfElse { def main(args: Array[String]): Unit = { // Java // int result = flag?1:0 // Scala var flag:Boolean = true var result = if(flag) 1 else 0 } }
2、嵌套分支
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。嵌套分支不要超过3层。
3、Switch分支结构
在Scala中没有Switch,而是使用模式匹配来处理。
4、For循环控制
Scala也为for循环这一常见的控制结构提供了非常多的特性,这些for循环的特性被称为for推导式或for表达式。
4.1 范围数据循环方式01
for(i <- 1 to 3){
print(i + " ")
}
- i 表示循环的变量,<- 规定to
- i 将会从 1-3 循环,前后闭合
4.2 范围数据循环方式02
for(i <- 1 until 3) {
print(i + " ")
}
- 这种方式和前面的区别在于i是从1到3-1
- 即使前闭合后开的范围
4.3 循环守卫
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue。等价于:
for (i <- 1 to 3){
if (i != 2) {
print(i + "")
}
}
4.4 循环步长
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
by表示步长
实例:输出1到10以内的所有奇数
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
输出结果:
i=1
i=3
i=5
i=7
i=9
4.5 嵌套循环
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
没有关键字,所以范围后一定要加;来隔断逻辑。代码等价于:
for (i <- 1 to 3) {
for (j <- 1 to 3) {
println("i =" + i + " j=" + j)
}
}
4.6 引入变量
for(i <- 1 to 3; j = 4 - i) {
println("i=" + i + " j=" + j)
}
for推导式一行中有多个表达式时,所以要加;来隔断逻辑。代码等价于:
for (i <- 1 to 3) {
var j = 4 - i
println("i=" + i + " j=" + j)
}
4.7 循环返回值
val res = for(i <- 1 to 10) yield i
println(res)
将遍历过程中处理的结果返回到一个新Vector集合中,使用yield关键字
实例:将原数据中所有值乘以2,并把数据返回到一个新的集合中
object TestFor {
def main(args: Array[String]): Unit = {
var res = for( i <-1 to 10 ) yield {
i * 2
}
println(res)
}
}
输出结果:
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
5、While/do…While循环控制
5.1 While循环
循环变量初始化
while (循环条件) {
循环体(语句)
循环变量迭代
}
- 循环条件是返回一个布尔值的表达式
- while循环是先判断再执行语句
- 与if语句不同,while语句没有返回值,即整个while语句的结果是Unit类型()
- 因为while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在while循环的外部,那么就等同于循环的内部对外部的变量造成了影响,也就违背了函数式编程的重要思想(输入=>函数=>输出,不对外界造成影响),所以不推荐使用,而是推荐使用for循环。
5.2 do…While循环
循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)
- 循环条件是返回一个布尔值的表达式
- do…while循环是先执行,再判断
5.3 While循环中断
Scala内置控制结构特地去掉了break和continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。scala中使用breakable控制结构来实现break和continue功能。
- 循环遍历10以内的所有数据,数值为5,结束循环(break)
import util.control.Breaks._ object TestBreak { def main(args: Array[String]): Unit = { var n = 1 breakable { while (n < 10) { println("n=" + n) n += 1 if (n == 5) { break() } } } println("exit") } }
- 循环遍历10以内的所有数据,奇数打印,偶数跳过(continue)
import util.control.Breaks._ object TestBreak { def main(args: Array[String]): Unit = { var n = 0 while (n < 10) { breakable { n += 1 if (n % 2 != 0) { println(n) } else { println("continue") break() } } } } }
6、多重循环控制
- 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过3层】
- 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。
- 设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。
object TestWhile {
def main(args: Array[String]): Unit = {
var max = 9
for (i <- 1 to max) {
for (j <- 1 to i) {
print(j + "*" + i + "=" + (i * j) + "\t")
}
println()
}
}
}
输出结果:
四、函数式编程
1、函数和方法的区别
- Scala语言的语法非常灵活,可以在任何的语法结构中声明任何的语法
- 为完成某一功能的程序指令(语句)的集合,称为函数。
- 类中的函数称之方法。
- 函数没有重载和重写的概念;方法可以进行重载和重写
- scala中函数可以嵌套定义
object TestFunction {
// (2)方法可以进行重载和重写,程序可以执行
def main(): Unit = {
}
def main(args: Array[String]): Unit = {
// (1)Scala语言的语法非常灵活,可以在任何的语法结构中声明任何的语法
import java.util.Date
new Date()
// (2)函数没有重载和重写的概念,程序报错
def test(): Unit ={
println("无参,无返回值")
}
test()
def test(name:String):Unit={
println()
}
//(3)scala中函数可以嵌套定义
def test2(): Unit ={
def test3(name:String):Unit={
println("函数可以嵌套定义")
}
}
}
}
2、函数声明
- 函数1:无参,无返回值
- 函数2:无参,有返回值
- 函数3:有参,无返回值
- 函数4:有参,有返回值
- 函数5:多参,无返回值
object TestFunctionDeclare {
def main(args: Array[String]): Unit = {
// 函数1:无参,无返回值
def test(): Unit ={
println("无参,无返回值")
}
test()
// 函数2:无参,有返回值
def test2():String={
return "无参,有返回值"
}
println(test2())
// 函数3:有参,无返回值
def test3(s:String):Unit={
println(s)
}
test3("jinlian")
// 函数4:有参,有返回值
def test4(s:String):String={
return s+"有参,有返回值"
}
println(test4("hello "))
// 函数5:多参,无返回值
def test5(name:String, age:Int):Unit={
println(s"$name, $age")
}
test5("dalang",40)
}
}
3、函数参数
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后
- 参数默认值
- 带名参数
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)可变参数
def test( s : String* ): Unit = {
println(s)
}
// 有输入参数:输出 Array
test("Hello", "Scala")
// 无输入参数:输出List()
test()
// (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
/*
可变参数一般放置在最后
def test2( s: String*,name : String ): Unit = {
println(name + "," + s)
}
*/
test2("jinlian", "dalang")
// (3)参数默认值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jinlian", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("dalang")
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// scala函数中参数传递是,从左到右
// 一般情况下,将有默认值的参数放置在参数列表的后面
// test4("wusong")
//(4)带名参数
test4(name="ximenqing")
}
}
4、函数至简原则
- return可以省略,Scala会使用函数体的最后一行代码作为返回值
- 返回值类型如果能够推断出来,那么可以省略
- 如果函数体只有一行代码,可以省略花括号
- 如果函数无参,则可以省略小括号。若定义函数时省略小括号,则调用该函数时,也需省略小括号;若定时函数时未省略,则调用时,可省可不省。
- 如果函数明确声明Unit,那么即使函数体中使用return关键字也不起作用
- Scala如果想要自动推断无返回值,可以省略等号
- 如果不关心名称,只关系逻辑处理,那么函数名(def)可以省略
- 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,需要声明返回值类型
object TestFunction {
def main(args: Array[String]): Unit = {
// 0)函数标准写法
def f1( s : String ): String = {
return s + " jinlian"
}
println(f1("Hello"))
// 至简原则:能省则省
//(1) return可以省略,scala会使用函数体的最后一行代码作为返回值
def f2( s : String ): String = {
s + " jinlian"
}
println(f2("Hello"))
// 如果函数名使用return关键字,那么函数就不能使用自行推断了,需要声明返回值类型
/*
def f22(s:String)={
return "jinlian"
}
*/
//(2)返回值类型如果能够推断出来,那么可以省略
def f3( s : String ) = {
s + " jinlian"
}
println(f3("Hello"))
//(3)如果函数体只有一行代码,可以省略花括号
//def f4(s:String) = s + " jinlian"
//def f4(s:String) = "jinlian"
def f4() = " dalang"
// 如果函数无参,但是声明参数列表,那么调用时,小括号,可加可不加。
println(f4())
println(f4)
//(4)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f5 = "dalang"
// val f5 = "dalang"
println(f5)
//(5)如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
def f6(): Unit = {
//return "abc"
"dalang"
}
println(f6())
//(6)scala如果想要自动推断无返回值,可以省略等号
// 将无返回值的函数称之为过程
def f7() {
"dalang"
}
println(f7())
//(7)如果不关心名称,只关系逻辑处理,那么函数名(def)可以省略
//()->{println("xxxxx")}
val f = (x:String)=>{
"wusong"}
// 万物皆函数 : 变量也可以是函数
println(f("ximenqing"))
//(8)如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,需要声明返回值类型
def f8() :String = {
return "ximenqing"
}
println(f8())
}
}
5、高阶函数(参数为函数的函数称为高阶函数)
object TestFunction {
def main(args: Array[String]): Unit = {
//高阶函数————函数作为参数
def calculator(a: Int, b: Int, operater: (Int, Int) => Int): Int = {
operater(a, b)
}
//函数————求和
def plus(x: Int, y: Int): Int = {
x + y
}
//方法————求积
def multiply(x: Int, y: Int): Int = {
x * y
}
//函数作为参数
println(calculator(2, 3, plus))
println(calculator(2, 3, multiply))
}
}
6、匿名函数/箭头函数(参考ES6里的箭头函数)
没有名字的函数就是匿名函数,可以直接通过函数字面量(lambda表达式)来设置匿名函数,函数字面量定义格式如下。
object TestFunction {
//高阶函数————函数作为参数
def calculator(a: Int, b: Int, operator: (Int, Int) => Int): Int = {
operator(a, b)
}
//函数————求和
def plus(x: Int, y: Int): Int = {
x + y
}
def main(args: Array[String]): Unit = {
//函数作为参数
println(calculator(2, 3, plus))
//匿名函数作为参数
println(calculator(2, 3, (x: Int, y: Int) => x + y))
//匿名函数简写形式
println(calculator(2, 3, _ + _))
}
}
7、函数柯里化&闭包
7.1 闭包
闭包:就是一个函数和与其相关的引用环境(变量)组合的一个整体(实体),可以把闭包简单理解成"定义在一个函数内部的函数"。
//外部变量
var x: Int = 10
//闭包
def f(x: Int, y: Int): Int = {
x + y
}
7.2 函数柯里化
函数柯里化:将一个接收多个参数的函数转化成一个接受一个参数的函数过程,可以简单的理解为一种特殊的参数列表声明方式。
object TestFunction {
val sum = (x: Int, y: Int, z: Int) => x + y + z
val sum1 = (x: Int) => {
y: Int => {
z: Int => {
x + y + z
}
}
}
val sum2 = (x: Int) => (y: Int) => (z: Int) => x + y + z
def sum3(x: Int)(y: Int)(z: Int) = x + y + z
def main(args: Array[String]): Unit = {
sum(1, 2, 3)
sum1(1)(2)(3)
sum2(1)(2)(3)
sum3(1)(2)(3)
}
}
8、递归
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用。
object TestFunction {
def main(args: Array[String]): Unit = {
// 阶乘
// 递归算法
// 1) 方法调用自身
// 2) 方法必须要有跳出的逻辑
// 3) 方法调用自身时,传递的参数应该有规律
// 4) scala中的递归必须声明函数返回值类型
println(test(5))
}
def test( i : Int ) : Int = {
if ( i == 1 ) {
1
} else {
i * test(i-1)
}
}
}
9、控制抽象
Scala中可以自己定义类似于if-else,while的流程控制语句,即所谓的控制抽象。
案例:定义如下控制结构
loop(5) {
println("control")
}
上述控制结构的功能为将println(“control”)循环执行5次。
提示:scala中,以下结构称为代码块(block),可视为无参函数,作为 =>Unit类型的参数值。
{
code
}
object TestBlock {
def loop(n: Int)(op: => Unit): Unit = {
if (n > 0) {
op
loop(n - 1)(op)
}
}
def main(args: Array[String]): Unit = {
loop(5) {
println("test")
}
}
}
10、惰性求值
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。
注意:lazy不能修饰var类型的变量
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
println("sum被执行。。。")
return n1 + n2
}
输出结果:
----------------
sum被执行。。。
res=40
11、函数参数的传名调用(call-by-name)和传值调用(call-by-value)
11.1 传名调用(call-by-name)
将未计算的参数表达式直接应用到函数内部。名调用是在函数内部进行参数表达式的值计算的。
这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。
11.2 传值调用(call-by-value)
先计算参数表达式的值,再应用到函数内部。在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕
object Add {
def addByName(a: Int, b: => Int) = a + b
def addByValue(a: Int, b: Int) = a + b
}
addByName是传名调用,addByValue是传值调用。语法上可以看出,使用传名调用时,在参数名称和参数类型中间有一个=> 符号。
以a为2,b为2 + 2为例,他们在Scala解释器进行参数规约(reduction)时的顺序分别是这样的:
addByName(2, 2 + 2)
->2 + (2 + 2)
->2 + 4
->6
addByValue(2, 2 + 2)
->addByValue(2, 4)
->2 + 4
->6
可以看出,在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。
这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。对于有副作用(side-effect)的参数来说,这无疑造成了两种调用方式结果的不同。
12、偏函数
偏函数相对于函数来讲,是缩小版的函数,或者说是残缺版的函数。
偏函数:被包在花括号内没有match的一组case语句
偏函数是PartialFunction[A, B]的一个实例
- A代表输入参数类型
- B代表返回结果类型
12.1 偏函数案例01:输入参数为Int类型,返回值为String类型的偏函数
object Test01 {
def main(args: Array[String]): Unit = {
val function0: PartialFunction[Int, String] = {
case 1 => "一"
case 2 => "二"
case 3 => "三"
case _ => "其它"
}
println(function0(3))
}
}
打印结果:
三
12.2 偏函数案例02
object Test02 {
def main(args: Array[String]): Unit = {
//定义一个列表,包含0-10的数字
//将1-3的数字都转换为[1-3]
//请将4-8的数字都转换为[4-8]
//将大于8的数字转换为(8-*]
//将其它的数字转化成其它
val list = (0 to 10).toList
val list2 = list.map {
case x if x >= 1 && x <= 3 => "[1-3]"
case x if x >= 4 && x <= 8 => "[4-8]"
case x if x > 8 => "(8-*]"
case _ => "其它"
}
println(list2)
}
}
打印结果:
List(其它, [1-3], [1-3], [1-3], [4-8], [4-8], [4-8], [4-8], [4-8], (8-*], (8-*])
五、面向对象
1、Scala包
1.1 访问权限
在Java中,访问权限分为:public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
- scala 中属性和方法的默认访问权限为public,但scala中无public关键字。
- private为私有权限,只在类的内部和伴生对象中可用。
- protected为受保护权限,Scala中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问。
- private[包名]增加包访问权限,包名下的其他类也可以使用
2、类和对象
2.1 定义类
[修饰符] class 类名 {
类体
}
- Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
- 一个Scala源文件可以包含多个类
2.2 属性
属性是类的一个组成部分
[修饰符] var 属性名称 [:类型] = 属性值
- 注:Bean属性(@BeanPropetry),可以自动生成规范的setXxx/getXxx方法
import scala.beans.BeanProperty
class Person {
var name: String = "bobo" //定义属性
var age: Int = _ // _表示给属性一个默认值
//Bean属性(@BeanProperty)
@BeanProperty var sex: String = "男"
}
object Person {
def main(args: Array[String]): Unit = {
var person = new Person()
println(person.name)
person.setSex("女")
println(person.getSex)
}
}
2.3 方法
def 方法名(参数列表) [:返回值类型] = {
方法体
}
class Person {
def sum(n1:Int, n2:Int) : Int = {
n1 + n2
}
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person()
println(person.sum(10, 20))
}
}
2.4 创建对象
val | var 对象名 [:类型] = new 类型()
- val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
- var修饰对象,可以修改对象的引用和修改对象的属性值
class Person {
var name: String = "canglaoshi"
}
object Person {
def main(args: Array[String]): Unit = {
//val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
val person = new Person()
person.name = "bobo"
// person = new Person()// 错误的
println(person.name)
}
}
2.5 构造器
和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括:主构造器和辅助构造器
class 类名(形参列表) {
// 主构造器
// 类体
def this(形参列表) {
// 辅助构造器
}
def this(形参列表) {
//辅助构造器可以有多个...
}
}
- 辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数来区分。
- 辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
- 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。
//(1)如果主构造器无参数,小括号可省略
//class Person (){
class Person {
var name: String = _
var age: Int = _
def this(age: Int) {
this()
this.age = age
println("辅助构造器")
}
def this(age: Int, name: String) {
this(age)
this.name = name
}
println("主构造器")
}
object Person {
def main(args: Array[String]): Unit = {
val person2 = new Person(18)
}
}
2.6 构造器参数
Scala类的主构造器函数的形参包括三种类型:未用任何修饰、var修饰、val修饰
- 未用任何修饰符修饰,这个参数就是一个局部变量
- var修饰参数,作为类的成员属性使用,可以修改
- val修饰参数,作为类只读属性使用,不能修改
class Person(name: String, var age: Int, val sex: String) {
}
object Test {
def main(args: Array[String]): Unit = {
var person = new Person("bobo", 18, "男")
// (1)未用任何修饰符修饰,这个参数就是一个局部变量
// printf(person.name)
// (2)var修饰参数,作为类的成员属性使用,可以修改
person.age = 19
println(person.age)
// (3)val修饰参数,作为类的只读属性使用,不能修改
// person.sex = "女"
println(person.sex)
}
}
3、封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。java封装操作如下,
- 将属性进行私有化
- 提供一个公共的set方法,用于对属性赋值
- 提供一个公共的get方法,用于获取属性的值
scala中的public属性,底层实际为private,并通过get方法(obj.field())和set方法(obj.field_=(value))对其进行操作。
所以scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。
但由于很多java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为scala的属性设置getXXX和setXXX方法(通过@BeanProperty注解实现)。
4、继承
class 子类名 extends 父类名 {
类体 }
- 子类继承父类的属性和方法
- scala是单继承
- 子类继承父类的属性和方法
- 继承的调用顺序:父类构造器->子类构造器
class Person(nameParam: String) {
var name = nameParam
var age: Int = _
def this(nameParam: String, ageParam: Int) {
this(nameParam)
this.age = ageParam
println("父类辅助构造器")
}
println("父类主构造器")
}
class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {
var empNo: Int = _
def this(nameParam: String, ageParam: Int, empNoParam: Int) {
this(nameParam, ageParam)
this.empNo = empNoParam
println("子类的辅助构造器")
}
println("子类主构造器")
}
object Test {
def main(args: Array[String]): Unit = {
new Emp("z3", 11,1001)
}
}
5、抽象类、抽象属性、抽象方法
- 定义抽象类:abstract class Person{} //通过abstract关键字标记抽象类
- 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
- 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
abstract class Person {
val name: String
def hello(): Unit
}
class Teacher extends Person {
override val name: String = "teacher"
override def hello(): Unit = {
println("hello teacher")
}
}
5.1 继承&重写
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
- 重写非抽象方法需要用override修饰,重写抽象方法则可以不加override。
- 子类中调用父类的方法使用super关键字
- 属性重写只支持val类型,而不支持var。
- scala中属性和方法都是动态绑定,而java中只有方法为动态绑定。
对比java与scala的重写
class Person {
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Teacher extends Person {
override val name: String = "teacher"
override def hello(): Unit = {
println("hello teacher")
}
}
object Test {
def main(args: Array[String]): Unit = {
val teacher: Teacher = new Teacher()
println(teacher.name)
teacher.hello()
val teacher1:Person = new Teacher
println(teacher1.name)
teacher1.hello()
}
}
class Person {
public String name = "person";
public void hello() {
System.out.println("hello person");
}
}
class Teacher extends Person {
public String name = "teacher";
@Override
public void hello() {
System.out.println("hello teacher");
}
}
public class TestDynamic {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Person teacher1 = new Teacher();
System.out.println(teacher.name);
teacher.hello();
System.out.println(teacher1.name);
teacher1.hello();
}
}
结果对比
6、单例对象(伴生对象)
6.1 伴生对象&伴生类
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。
但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。
若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
object Person{
val country:String="China"
}
- 单例对象采用object关键字声明
- 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
- 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
//(1)伴生对象采用object关键字声明
object Person {
var country: String = "China"
}
//(2)伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class Person {
var name: String = "bobo"
}
object Test {
def main(args: Array[String]): Unit = {
//(3)伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
println(Person.country)
}
}
6.2 apply方法
- 通过伴生对象的apply方法,实现不使用new方法创建对象。
- 如果想让主构造器变成私有的,可以在()之前加上private。
- apply方法可以重载。
- Scala中obj(arg)的语句实际是在调用该对象的apply方法,即obj.apply(arg)。用以同一面向对象编程和函数式编程的风格。
object Test {
def main(args: Array[String]): Unit = {
//(1)通过伴生对象的apply方法,实现不使用new关键字创建对象。
val p1 = Person()
println("p1.name=" + p1.name)
val p2 = Person("bobo")
println("p2.name=" + p2.name)
}
}
//(2)如果想让主构造器变成私有的,可以在()之前加上private
class Person private(cName: String) {
var name: String = cName
}
object Person {
def apply(): Person = {
println("apply空参被调用")
new Person("xx")
}
def apply(name: String): Person = {
println("apply有参被调用")
new Person(name)
}
}
7、特质(Trait)
- Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。
- Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。
- Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
trait 特质名 {
trait体
}
trait PersonTrait {
// 声明属性
var name:String = _
// 声明方法
def eat():Unit={
}
// 抽象属性
var age:Int
// 抽象方法
def say():Unit
}
7.1 特质基本语法
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
- 没有父类:
class 类名 extends 特质1 with 特质2 with 特质3 …
- 有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3…
- 类和特质的关系:使用继承的关系。
- 当一个类去继承特质时,第一个连接词是extends,后面是with。
- 如果一个类在继承特质和父类时,应当把父类写在extends后。
- 特质可以同时拥有抽象方法和具体方法
- 一个类可以混入(mixin)多个特质
- 所有的Java接口都可以当做Scala特质使用
- 动态混入:可灵活的扩展类的功能
- 动态混入:创建对象时混入trait,而无需使类混入该trait
- 如果混入的trait中有未实现的方法,则需要实现
trait PersonTrait {
//(1)特质可以同时拥有抽象方法和具体方法
// 声明属性
var name: String = _
// 抽象属性
var age: Int
// 声明方法
def eat(): Unit = {
println("eat")
}
// 抽象方法
def say(): Unit
}
trait SexTrait {
var sex: String
}
//(2)一个类可以实现/继承多个特质
//(3)所有的Java接口都可以当做Scala特质使用
class Teacher extends PersonTrait with java.io.Serializable {
override def say(): Unit = {
println("say")
}
override var age: Int = _
}
object TestTrait {
def main(args: Array[String]): Unit = {
val teacher = new Teacher
teacher.say()
teacher.eat()
//(4)动态混入:可灵活的扩展类的功能
val t2 = new Teacher with SexTrait {
override var sex: String = "男"
}
//调用混入trait的属性
println(t2.sex)
}
}
7.2 特质叠加
由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:
- 第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
- 第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。
所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来,案例如下,
结果如下:trait Ball { def describe(): String = { "ball" } } trait Color extends Ball { override def describe(): String = { "blue-" + super.describe() } } trait Category extends Ball { override def describe(): String = { "foot-" + super.describe() } } class MyBall extends Category with Color { override def describe(): String = { "my ball is a " + super.describe() } } object TestTrait { def main(args: Array[String]): Unit = { println(new MyBall().describe()) } }
7.3 特质叠加执行顺序
当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法。排序规则如下:
- 案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball。
- 如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如super[Category].describe()。
7.4 特质自身类型
自身类型可实现依赖注入的功能。
class User(val name: String, val age: Int)
trait Dao {
def insert(user: User) = {
println("insert into database :" + user.name)
}
}
trait APP {
_: Dao =>
def login(user: User): Unit = {
println("login :" + user.name)
insert(user)
}
}
object MyApp extends APP with Dao {
def main(args: Array[String]): Unit = {
login(new User("bobo", 11))
}
}
8、样例类
case class Person (name: String, age: Int)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
- 样例类是为模式匹配而优化的类,因为其默认提供了unapply方法,因此,样例类可以直接使用模式匹配,而无需自己实现unapply方法。
- 构造器中的每一个参数都成为val,除非它被显式地声明为var(不建议这样做)
六、模式匹配
Scala中的模式匹配类似于Java中的switch语法,但是更加强大。
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。
object TestMatchCase {
def main(args: Array[String]): Unit = {
var a: Int = 10
var b: Int = 20
var operator: Char = 'd'
var result = operator match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case _ => "illegal"
}
println(result)
}
}
- 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,若没有case _ 分支,那么会抛出MatchError。
- 每个case中,不用break语句,自动中断case。
- match case语句可以匹配任何类型,而不只是字面量。
- => 后面的代码块,是作为一个整体执行,可以使用{}括起来,也可以不括。
1、模式守卫
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
object TestMatchGuard {
def main(args: Array[String]): Unit = {
def abs(x: Int) = x match {
case i: Int if i >= 0 => i
case j: Int if j < 0 => -j
case _ => "type illegal"
}
println(abs(-5))
}
}
2、模式匹配类型
2.1 匹配常量
scala中,模式匹配可以匹配所有的 “字面量”,包括字符串,字符,数字,布尔值等等。
object TestMatchVal {
def main(args: Array[String]): Unit = {
println(describe(6))
}
def describe(x: Any) = x match {
case 5 => "Int five"
case "hello" => "String hello"
case true => "Boolean true"
case '+' => "Char +"
}
}
2.2 匹配类型
需要进行类型判断时,可以使用isInstanceOf[T]和asInstanceOf[T],也可使用模式匹配实现同样的功能。
object TestMatchClass {
def describe(x: Any) = x match {
case i: Int => "Int"
case s: String => "String hello"
case m: List[_] => "List"
case c: Array[Int] => "Array[Int]"
case someThing => "something else " + someThing
}
def main(args: Array[String]): Unit = {
//泛型擦除
println(describe(List(1, 2, 3, 4, 5)))
//数组例外,可保留泛型
println(describe(Array(1, 2, 3, 4, 5, 6)))
println(describe(Array("abc")))
}
}
2.3 匹配数组
scala模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组。
object TestMatchArray {
def main(args: Array[String]): Unit = {
// 对一个数组集合进行遍历
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1), Array("hello", 90))) {
val result = arr match {
case Array(0) => "0" //匹配Array(0) 这个数组
case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将将元素值赋给对应的x,y
case Array(0, _*) => "以0开头的数组" //匹配以0开头和数组
case _ => "something else"
}
println("result = " + result)
}
}
}
2.4 匹配列表
object TestMatchList {
def main(args: Array[String]): Unit = {
//list是一个存放List集合的数组
//请思考,如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回.应该怎么写
for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0), List(88))) {
val result = list match {
case List(0) => "0" //匹配List(0)
case List(x, y) => x + "," + y //匹配有两个元素的List
case List(0, _*) => "0 ..."
case _ => "something else"
}
println(result)
}
}
}
object TestMatchList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 5, 6, 7)
list match {
case first :: second :: rest => println(first + "-" + second + "-" + rest) // 1-2-List(5, 6, 7)
case _ => println("something else")
}
}
}
2.5 匹配元组
object TestMatchTuple {
def main(args: Array[String]): Unit = {
//对一个元组集合进行遍历
for (tuple <- Array((0, 1), (1, 0), (1, 1), (1, 0, 2))) {
val result = tuple match {
case (0, _) => "0 ..." //是第一个元素是0的元组
case (y, 0) => "" + y + "0" // 匹配后一个元素是0的对偶元组
case (a, b) => "" + a + " " + b
case _ => "something else" //默认
}
println(result)
}
}
}
2.6 匹配对象及样例类
class User(val name: String, val age: Int)
object User {
def apply(name: String, age: Int): User = new User(name, age)
def unapply(user: User): Option[(String, Int)] = {
if (user == null)
None
else
Some(user.name, user.age)
}
}
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
- val user = User(“zhangsan”,11),该语句在执行时,实际调用的是User伴生对象中的apply方法,因此不用new关键字就能构造出相应的对象。
- 当将User(“zhangsan”, 11)写在case后时[case User(“zhangsan”, 11) => “yes”],会默认调用unapply方法(对象提取器),user作为unapply方法的参数,unapply方法将user对象的name和age属性提取出来,与User(“zhangsan”, 11)中的属性值进行匹配
- case中对象的unapply方法(提取器)返回Some,且所有属性均一致,才算匹配成功,属性不一致,或返回None,则匹配失败。
- 若只提取对象的一个属性,则提取器为unapply(obj:Obj):Option[T]
若提取对象的多个属性,则提取器为unapply(obj:Obj):Option[(T1,T2,T3…)]
若提取对象的可变个属性,则提取器为unapplySeq(obj:Obj):Option[Seq[T]]
case class Person (name: String, age: Int)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
- 样例类是为模式匹配而优化的类,因为其默认提供了unapply方法,因此,样例类可以直接使用模式匹配,而无需自己实现unapply方法。
- 构造器中的每一个参数都成为val,除非它被显式地声明为var(不建议这样做)
上述匹配对象的案例使用样例类会节省大量代码
case class User(name: String, age: Int)
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
3、变量声明中的模式匹配
case class Person(name: String, age: Int)
object TestMatchVariable {
def main(args: Array[String]): Unit = {
val (x, y) = (1, 2)
println(s"x=$x,y=$y")
val Array(first, second, _*) = Array(1, 7, 2, 9)
println(s"first=$first,second=$second")
val Person(name, age) = Person("zhangsan", 16)
println(s"name=$name,age=$age")
}
}
打印结果:
x=1,y=2
first=1,second=7
name=zhangsan,age=16
4、 for表达式中的模式匹配
object TestMatchFor {
def main(args: Array[String]): Unit = {
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) {
//直接将map中的k-v遍历出来
println(k + " -> " + v) //3个
}
println("----------------------")
//遍历value=0的 k-v ,如果v不是0,过滤
for ((k, 0) <- map) {
println(k + " --> " + 0) // B->0
}
println("----------------------")
//if v == 0 是一个过滤的条件
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v) // A->1 和 c->33
}
}
}
打印结果:
A -> 1
B -> 0
C -> 3
----------------------
B --> 0
----------------------
A ---> 1
C ---> 3
5、偏函数中的模式匹配
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为List[Int],而我们需要的是第一个元素是0的集合,这就是通过模式匹配实现的。
5.1 1 偏函数定义
val second: PartialFunction[List[Int], Option[Int]] = {
case x :: y :: _ => Some(y)
}
该偏函数的功能是返回输入的List集合的第二个元素
5.1 2 偏函数原理
上述代码会被scala编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数检查的函数——isDefinedAt,其返回值类型为Boolean。
val second = new PartialFunction[List[Int], Option[Int]] {
//检查输入参数是否合格
override def isDefinedAt(list: List[Int]): Boolean = list match {
case x :: y :: _ => true
case _ => false
}
//执行函数逻辑
override def apply(list: List[Int]): Option[Int] = list match {
case x :: y :: _ => Some(y)
}
}
5.1 3 偏函数使用
偏函数不能像second(List(1,2,3))这样直接使用,因为这样会直接调用apply方法,而应该调用applyOrElse方法,如下:
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)
applyOrElse方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即isDefinedAt返回true,则执行apply方法,否则执行defalut方法,default方法为参数不满足要求的处理逻辑。
5.1 4 偏函数实操
将该List(1,2,3,4,5,6,“test”)中的Int类型的元素加一,并去掉字符串。
- 方法一:
List(1,2,3,4,5,6,"test").filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int] + 1).foreach(println)
- 方法二:
List(1, 2, 3, 4, 5, 6, "test").collect { case x: Int => x + 1 }.foreach(println)
6、模式匹配案例
6.1 利用模式匹配,编写一个 swap(arr: Array[Int]) 函数,交换数组中前两个元素的位置
object ScalaDemo {
def main(args: Array[String]): Unit = {
def swap(arr: Array[Int]) = {
arr match {
case Array(a, b, rest@_*) => Array(b, a) ++ rest
}
}
val arr = swap(Array(1, 2, 3, 4, 5))
println(arr.mkString(", "))
}
}
打印结果:
2, 1, 3, 4, 5
6.2 计算 List[Option[Int]] 中所有非None值之和
object ScalaDemo {
def main(args: Array[String]): Unit = {
//计算 List[Option[Int]] 中所有非None值之和。分别使用 match 和不使用 match 来计算
def sum1(list: List[Option[Int]]) = {
var sum = 0
list.foreach(op => {
if (op.isDefined) sum += op.get
})
sum
}
// 聚合函数 reduce 返回值的类型, 要和集合中的类型一致
def sum2(list: List[Option[Int]]) = {
// filter(_.isDefined)过滤得到非None值
// map(_.get)得到list中的每一个Some里的值组成新的map,然后用reduce函数
list.filter(_.isDefined).map(_.get).reduce(_ + _)
}
// 聚合函数 reduce foldLeft
def sum3(list: List[Option[Int]]) = {
// 初始化值为0,第二个元素如果有值直接取出,如果为None则取0
list.foldLeft(0)(_ + _.getOrElse(0))
}
println("sum1 = " + sum1(List(Some(10), None, Some(20), Some(20))))
println("sum2 = " + sum2(List(Some(10), None, Some(20), Some(20))))
println("sum3 = " + sum3(List(Some(10), None, Some(20), Some(20))))
}
}
打印结果:
sum1 = 50
sum2 = 50
sum3 = 50
6.3 用列表制作只在叶子节点存放值的树
我们可以用列表制作只在叶子节点存放值的树。举例来说,列表((3 8) 2 (5)) 描述的是如下这样一棵树:
*
/|\
* 2 *
/\ |
3 * 5
/\
10 20
List[Any] =List(List(3, List(10, 20)), 2, List(5))
不过,有些列表元素是数字,而另一些是列表。在Scala中,你必须使用List[Any]。
编写一个leafSum函数,计算所有叶子节点中的元素之和.
object ScalaDemo {
def main(args: Array[String]): Unit = {
val list: List[Any] = List(List(3, List(10, 20)), 2, List(5))
// 解法一:计算list中所有的int类型的值
def sum1(list: List[Any]): Int = {
var sum = 0
list.foreach(x => x match {
case a: Int => sum += a
case list: List[_] => sum += sum1(list) // 使用递归
})
sum
}
// 解法二:计算list中所有的int类型的值
def sum2(list: List[Any]): Int = {
list.foldLeft(0)((x, y) => x + (y match {
case a: Int => a
case list: List[_] => sum2(list)
}))
}
println("sum1(list) = " + sum1(list))
println("sum2(list) = " + sum2(list))
}
}
打印结果:
sum1(list) = 40
sum2(list) = 40
6.4 计算所有叶子节点的元素的和
sealed abstract class BinaryTree
case class Leaf(value: Int) extends BinaryTree
case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree
object ScalaDemo {
def main(args: Array[String]): Unit = {
val binaryTree: BinaryTree = Node(Leaf(8), Node(Node(null, Leaf(20)), Leaf(9)))
def sum(node: BinaryTree): Int = {
node match {
case Leaf(v) => v
case Node(left, right) => sum(left) + sum(right)
case _ => 0
}
}
println("sum(binaryTree) = " + sum(binaryTree))
}
}
打印结果:
sum(binaryTree) = 37
6.5 一张大面值的货币要兑换成零钱,问有多少种兑换方式
/**
* 假设某国的货币有若干面值,现给一张大面值的货币要兑换成零钱,问有多少种兑换方式
动态规划:
针对第一种硬币来说:
1. 包含
2. 不包含
出口:
1. money < 0 或 硬币是0
0种换法
2. money = 0
1中换法
递归条件
change(money, coins.tail) + change(money - coins.head, coins)
*/
object ScalaDemo {
def main(args: Array[String]): Unit = {
def change(money: Int, coins: Array[Int]):Int = {
if(money < 0 || coins.isEmpty) 0
else if(money == 0) 1
// 不包含第一种 + 包含第一种
else change(money, coins.tail) + change(money - coins.head, coins)
}
println("change(11, Array(1, 2, 5, 10)) = " + change(11, Array(1, 2, 5, 10)))
}
}
打印结果:
change(11, Array(1, 2, 5, 10)) = 12
七、隐式转换
隐式转换可以再不需改任何代码的情况下,扩展某个类的功能。例如通过隐式转化为 “Int类型” 增加myMax()方法、myMin()方法。
class MyRichInt(val self: Int) {
def myMax(i: Int): Int = {
if (self < i) i else self
}
def myMin(i: Int): Int = {
if (self < i) self else i
}
}
object TestImplicitFunction {
implicit def convert(arg: Int): MyRichInt = {
new MyRichInt(arg)
}
def main(args: Array[String]): Unit = {
println(2.myMax(6))
}
}
1、隐式参数
普通方法或者函数可以通过implicit关键字声明隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
- 同一个作用域中,相同类型的隐式值只能有一个
- 编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
- 隐式参数优先于默认参数
object TestImplicitParameter {
implicit val str: String = "hello world!"
def hello(implicit arg: String="good bey world!"): Unit = {
println(arg)
}
def main(args: Array[String]): Unit = {
hello
}
}
打印结果:
hello world!
2、隐式类
在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。
- 其所带的构造参数有且只能有一个
- 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
object TestImplicitClass {
implicit class MyRichInt(arg: Int) {
def myMax(i: Int): Int = {
if (arg < i) i else arg
}
def myMin(i: Int) = {
if (arg < i) arg else i
}
}
def main(args: Array[String]): Unit = {
println(1.myMax(3))
}
}
3、隐式解析机制
- 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。
八、符号应用总结
1、中置运算符
1 + 2
1 to 100
2、一元运算符
2.1 后置:
1 toString
5!
2.2 前置:
+5
-10
这四种前置运算符:
+ - ! ~
3、运算符的结合性
重点理解右结合
=
: 结尾
4、apply方法
任意对象都可以定义apply’方法,
对象(…) === 对象.apply(…)
- 伴生对象有apply Person(a)
- 普通的类也可以有apply new Person()(a)
- 调用函数
val f = 函数
f()
f.apply()
5、update 方法
更新
对象(参数) = 值 === 对象.update(参数, 值)
6、_总结
-
导入包, 通配符
-
元组元素的前缀 t._1
-
函数的隐式参数 (占位符) f(_ + _)
-
方法转函数
def f = …
val f1 = f _ -
给类的属性设置默认值
class …
var a: Int = _ -
在一个标识符中隔开字符和运算符
±…
a_+ -
模式匹配的时候通配符
case _ => -
部分应用函数
val square = math.pow(_, 2) -
分解集合
foo(1,2,3)
foo(1 to 100: _)
def foo(a: Int) -
模式匹配集合的时候
Array(rest@_*)
…
7、额外的类
7.1 Option[T]
语义: 表示值要么存在要么不存在
Some
None
7.2 Either[T,V]
语义: 表示值要么正确要么错误
Left 左值: 表示错误的值
Right 右值: 表示正确的值
九、IntellJ安装Scala
1、将scala-2.11.8.tgz解压到C:\Program_Files_AI\scala-2.11.8
2、配置环境变量
添加Path:C:\Program_Files_AI\scala-2.11.8\bin
3、IntellJ支持Scala配置
右击项目“scala01”
一般scala与java混合编写
hint设置