本文在Java基础上只记录比较不同的重点和难点
目录
2.5.1 Unit 类型、 Null 类型和 Nothing 类型(重点)
1、Scala基本概念
1.1 Scala语言特点
1.2 代码中的简单说明
/*
object 关键字,声明一个单例对象(伴生对象)
*/
object HelloWorld {
/*
main方法:从外部可以直接调用的方法
def 方法名(参数名称:参数类型):返回值类型={方法体}
*/
def main(args:Array[String]):Unit={
println("hello world")
}
}
1.3 面向对象的理解
scala中的类不能定义静态成员,而代之以定义单例对象来替代
单例对象通过object关键字来声明
单例对象中的所有方法,可以直接通过object单例对象的名字直接来调用。
一个单例对象可以绑定在一个类,当单例对象和某个类写在同一个源文件且共享一个名字,它们就产生了绑定关系。
此时单例对象称之为该类的伴生对象,类称之为该对象的伴生类。
class student(name:String,age:Int){
def printInfo():Unit={
println(name+" "+age+" "+student.school)
}
}
引入伴生对象
object student{
val school:String="666"
def main(args: Array[String]): Unit = {
val alice = new student("alice", 23)
alice.printInfo()
}
}
2、Scala基本知识
2.1 常量与变量
1 )基本语法
var 变量名 [: 变量类型] = 初始值 var i:Int = 10
val 常量名 [: 常量类型] = 初始值 val i:Int = 10
var 修饰的变量可改变,val 修饰的变量不可改
2 )
var 修饰的对象引用可以改变, val 修饰的对象则不可改变,但对象的状态(值)
却是可以改变的。(比如:自定义对象、数组、集合等等)
object TestVar {
def main(args: Array[String]): Unit = {
// p1 是 var 修饰的, p1 的属性可以变,而且 p1本身也可以变 var p1 = new Person()
p1.name = p1 = null "dalang"
/* p2 是 val 修饰的,那么 p2本身就不可变 (即 p2 的内存地址不能变),但是 ,p2 的属性是可以变,因为属性并没有用 val 修饰。*/
val p2 = new Person()
p2.name="jinlian"
// p2 = null // 错误的,因为 p2 是 val 修饰的
}
}
class Person{
var name : String = "jinlian"
2.2 字符串的输出
1 )基本语法
( 1 )字符串,通过+号连接
( 2 )printf 用法:字符串,通过%传值。
( 3 )字符串模板(插值字符串) :通过${}获取变量值
方法三的演示:要用s"" 表示在使用字符串模板
println(s"${age}的学生在学习Scala")
多行字符串,在 Scala中,利用三个双引号包围多行字符串就可以实现
val s =
"""
|select name, age
|from user
|where name="zhangsan"
""".stripMargin
println(s)
2.3 键盘输入
1 )基本语法
StdIn.readLine()、 StdIn.readShort()、 StdIn.readDouble()
2.4 简单的文件操作
//从文件中读取数据
Source.fromFile("C:\\Users\\Admin\\Desktop\\6.txt").foreach(print)
//从文件中写入数据 Scala中没有,需要调用Java中的类
val write=new PrintWriter(new File("C:\\Users\\Admin\\Desktop\\6.txt"))
write.write("hahahahahahahahaw2jdkaosfhaljkhfa")
write.close()
2.5 Scala数据类型
1)Scala中一切数据都是对象,都是Any的子类。
2)Scala中数据类型分为两大类:数值类型(AnyVal)、 引用类型(AnyRef),不管是值类型还是引用类型都是
对象。
3)Scala数据类型仍然遵守,低精度的值类型向高精 度值类型,自动转换(隐式转换)
4)Scala中的StringOps是对Java中的String增强
5)Unit:对应Java中的void ,用于方法返回值的位置,表 示方法没有返回值。Unit是一个数据类型,只有一个对象 就是() 。Void不是数据类型,只是一个关键字
6)Null是一个类型,只有一个对象就是null 。它是 所有引用类型(AnyRef)的子类。
7)Nothing ,是所有数据类型的子类,主要用在一个函数没有明确返回值时使 用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。
Scala 的整型,默认为 Int 型,声明 Long 型,须后加‘l’或‘L’
var n6 = 9223372036854775807L
Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。
var n7 = 2.2345678912f
2.5.1 Unit 类型、 Null 类型和 Nothing 类型(重点)
数据类型 | 描述 |
Unit | 空值 表示无值,和其他语言中 void 等同。用作不返回任何结果的方法类型。 Unit 只有一个实例值,写成()。 |
Null | 空引用,null , Null 类型只有一个实例值 null。 |
Nothing | Nothing 类型在Scala 的类层级最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,比如抛出异常的时候,可以用Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性)。 |
2.5.2 数据类型的自动转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这
个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:
1 )基本说明
( 1 )自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成 精度大的那种数据类型 ,然后再进行计算。
( 2 )把精度大的数值类型赋值给精度小的数值类型时,就会报错 ,反之就会进行自动 类型转换。
( 3 )(byte ,short)和 char 之间不会相互自动转换。
( 4 )byte ,short ,char 他们三者可以计算, 在计算时首先转换为 int 类型。
2.5.3 强制类型的转换
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上
强制转函数,但可能造成精度降低或溢出,格外要注意。
Java : int num = (int)2.5
Scala : var num : Int = 2.7.toInt
2.5.4 数值类型和 String 类型间转换
1 )基本说明
在程序开发中,我们经常需要将基本数值类型转成 String 类型。或者将 String 类型转成 基本数值类型。
2 )案例实操
( 1 )基本类型转 String 类型(语法 :将基本类型的值+"" 即可)
( 2 )String 类型转基本数值类型(语法 :s1.toInt、 s1.toFloat、 s1.toDouble、 s1.toByte、s1.toLong、 s1.toShort )
3、运算符
3.1关系运算符
Java :
==比较两个变量本身的值,即两个对象在内存中的首地址 ;
equals 比较字符串中所包含的内容是否相同。
Scala :==更加类似于 Java 中的 equals ,参照 jd 工具,eq类似Java==
def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2)
println(s1.eq(s2))
}
输出结果:
true
false
3.2 Scala 运算符本质
在 Scala 中其实是没有运算符的 ,所有运算符都是方法。
1 )当调用对象的方法时, 点.可以省略
2 )如果函数参数只有一个,或者没有参数 ,()可以省略
object TestOpt {
def main(args: Array[String]): Unit = {
// 标准的加法运算
val i:Int = 1.+(1)
// ( 1 )当调用对象的方法时, .可以省略
val j:Int = 1 + (1)
// ( 2 )如果函数参数只有一个,或者没有参数, ()可以省略 val k:Int = 1 + 1
println(1.toString())
println(1 toString())
println(1 toString)
}
}
3.3.1 范围数据循环( To )
基本语法:
for(i <- 1 to 3)
//循环嵌套
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
循环三次
3.3.2 范围数据循环( Until)
基本语法:
for(i <- 1 until 3) {
print(i + " ")
}
i从1到3-1=2遍历
3.3.3 循环步长
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
i表示步长
3.3.4 循环返回值
for循环的默认返回值是空
val res = for(i <- 1 to 10) yield i
println(res)
yield的用法一个句话,就是在for循环中,每次循环都会产生一个值,然后将每次产生的值保存,最后组成一个集合。
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。
运行结果:Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
3.3.5 循环中断
1 )基本说明
Scala 内置控制结构特地去掉了 break 和 continue ,是为了更好的适应函数式编程 ,推 荐使用函数式的风格解决 break 和 continue 的功能 ,而不是一个关键字。Scala 中使用 breakable控制结构来实现 break 和 continue 功能。
需求 1 :采用异常的方式退出循环
def main(args: Array[String]):Unit = {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw new RuntimeException
}
}catch {
case e => //和Java有所不同
}
println("正常结束循环")
}
需求 2 :为了简化,采用 Scala 自带的函数 ,退出循环
import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
//Breaks.breakable 用于标记
Breaks.breakable (
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) Breaks.break()
}
)
println("正常结束循环")
}
需求 3 :对 break 进行省略
import scala.util.control.Breaks._
object TestBreak {
def main(args: Array[String]): Unit = {
breakable {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) break
}
}
println("正常结束循环")
}
}
4、函数式编程
1 )面向对象编程
解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。 对象 :用户
行为 :登录、连接 JDBC、读取数据库
属性:用户名、密码
Scala 语言是一个完全面向对象编程语言。万物皆对象
对象的本质:对数据和行为的一个封装
2 )函数式编程
解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如 :请求->用户名、密码->连接 JDBC->读取数据库
Scala 语言是一个完全函数式编程语言。 万物皆函数。
函数的本质:函数可以当做一个值进行传递
3 )在 Scala 中函数式编程和面向对象编程完美融合在一起了。
4.1 基本语法
def sum(x:Int,y:Int):Int={
x+y
}
def:定义函数的关键字 sum:函数名 x:int 参数名和参数类型,最后的int为返回类型
4.2函数的定义
1 )函数定义
( 1 )函数 1 :无参,无返回值
( 2 )函数 2 :无参,有返回值
( 3 )函数 3 :有参,无返回值
( 4 )函数 4 :有参,有返回值
( 5 )函数 5 :多参,无返回值
( 6 )函数 6 :多参,有返回值
2 )案例实操
object TestFunctionDeclare {
def main(args: Array[String]): Unit = {
// 函数 1 :无参,无返回值
def test1 (): Unit ={
println("无参,无返回值")
}
test1()
// 函数 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")
}
}
}
4.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)
}
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.4匿名函数
1 )说明
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x :表示输入参数类型 ;Int :表示输入参数类型 ;函数体: 表示具体代码逻辑
调用的方法:
匿名函数:(name:String,age:Int) => {println(name+" "+age)}
调用方法一:赋值法
val fun=(name:String,age:Int) => {println(name+" "+age)}
fun("admin",22)
方法二:把该匿名函数当成一个参数传给另一个函数
//(String, Int) => Unit为该函数的类型,可以在下行定义尾部输入.var 即可得到下面第二行
val fun= (name:String,age:Int) => {println(name+" "+age)}
val function: (String, Int) => Unit = (name:String,age:Int) => {println(name+" "+age)}
val fun=function
def f(func: (String, Int) => Unit):Unit={
func("abc",21)
}
f(fun)
等价于 f((name:String,age:Int) => {println(name+" "+age)})
匿名方法的简化:
( 1 )参数的类型可以省略,会根据形参进行自动的推导
( 2 )类型省略之后 ,发现只有一个参数,则圆括号可以省略 ;其他情况:没有参数和参
数超过 1 的永远不能省略圆括号。
( 3 )匿名函数如果只有一行,则大括号也可以省略
( 4 )如果参数只出现一次,则参数省略且后面参数可以用_代替
原始匿名方法: (name:String) => {println(name)}
( 1 )参数的类型可以省略,会根据形参进行自动的推导
val function: (String) => Unit = (name:String) => {println(name)}//本身就有类型说明String
故可以改为:
f((name) => {println(name)})
( 2 )类型省略之后 ,发现只有一个参数,则圆括号可以省略 ;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
f(name => {println(name+" "+age)})
( 3 )匿名函数如果只有一行,则大括号也可以省略
f(name => println(name))
( 4 )如果参数只出现一次,则参数、=> 省略且后面参数可以用_代替
f(println(_))
( 5 )如果可以推断当前传入的println是一个函数体,则可以直接省略(_)
f(println)
示例:
定义一个二元运算函数:只操作两个固定的数,但是具体运算通过参数传入
def twokey(fun : (Int,Int)=>Int):Int={
fun(3,9)
}
val add = (a : Int,b : Int)=> a + b
val minus = (a : Int,b : Int)=> a - b
println(twokey(add))
println(twokey(minus))
匿名函数简化
println(twokey((a,b)=> a + b))
println(twokey((a,b)=> a - b))
因为每个变量都只出现一次,可以用_代替,所以也可变为
println(twokey(_+_))
println(twokey(_-_)) 但是b-a就不行了 可以写成 -_+_
4.5 高阶函数
1 )函数可以作为值进行传递
def main(args:Array[String]):Unit={
def foo():Int = {
println("foo...")
1
}
//()表示参数类型,此时没有参数,Int表示返回类型
val f1:()=>Int=foo 等价于 val f1=foo _
println(f1) //结果为:chapter01.HelloWorld$$$Lambda$1/764977973@6debcae2 对象的引用
println(f1()) //结果为:foo... 1 函数被调用
对于没有参数的方法可以直接调用
foo()
也可以
foo
val s=foo()
println(s) //结果是调用foo函数体
//有参数
def f(a :Int):Int={
println("f被调用")
a+1
}
val f1=f _ //空格加下划线表明它是一个函数类型
val f2:Int=>Int=f f1=f2//或者直接指明函数类型
println(f1) //chapter01.HelloWorld$$$Lambda$1/764977973@5ba23b66
println(f1(5)) //f被调用 6
}
2 )函数可以作为参数进行传递
fff中传入op函数,op函数类型是(Int,Int)=>Int,fff中传入两个变量a,b最终得到Int类型
def fff(op:(Int,Int)=>Int,a:Int,b:Int):Int={
op(a,b)
}
def add(a:Int,b:Int):Int={
a+b
}
println(fff(add,6,9))
也可以直接写成匿名函数
println(fff((a,b)=>a-b,12,6))
println(fff((a,b)=>a*b,6,6))
println(fff(_+_,6,9))
3 )函数可以作为函数返回值返回
def f5():Int=>Unit={
def f6(a:Int):Unit={
println("f6被调用 "+a)
}
f6
}
println(f5()(5)) //结果:f6被调用 5 () 因为f6是空返回类型
println(f5()) //结果:chapter01.HelloWorld$$$Lambda$5/1908923184@2ff4f00f 没有参数传进返回的是f6的引用
例子:将数组进行处理,将操作抽象出来,处理完毕返回新的数组
val arr:Array[Int]=Array(12,17,34,45,56)
def ArrayOperation(array: Array[Int],op : Int=>Int):Array[Int]={
for (elem <- array) yield op(elem)
}
def add(elem:Int):Int={
elem+3
}
val newarr:Array[Int]=ArrayOperation(arr,add)
val newarr2:Array[Int]=ArrayOperation(arr,_*2) === (arr,elem=>elem*2)
println(newarr.mkString(","))
}
练习 2 : 定义一个函数 func ,它接收一个Int类型的参数,返回一个函数(记作f1)。
它返回的函数f1,接收一个 String 类型的参数,同样返回一个函数(记作f2)。
函数f2接收一个 Char 类型的参数,返回一个 Boolean 的值。
要求调用函数 func(0) (“”) (‘0’)得到返回值为 false ,其它情况均返回true。
def func(a:Int):String=>Char=>Boolean={ 看f1的输入输出
def f1(s:String):Char=>Boolean={ 看f2的输入输出
def f2(c:Char):Boolean={
if(a==0 && s=="" && c=='0')
false
else
true
}
f2
}
f1
}
println( func(0) ("") ('0'))
println( func(1) ("") ('0'))
匿名函数的简写
def func(a:Int):String=>Char=>Boolean={
(s:String)=>(c:Char)=>if(a==0 && s=="" && c=='0') false else true
}
函数的柯里化
def func2(a:Int)(b:String)(c:Char):Boolean={
if(a==0 && b=="" && c=='0') false else true
}
可以看到柯里化的写法简单明了,后面还会详细说明
4.6函数柯里化&闭包
闭包 :函数式编程的标配
1 )说明
闭包 :如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
函数柯里化 :把一个参数列表的多个参数,变成多个参数列表。
def add(a:Int,b:Int):Int={
a+b
}
//1.在大数据的情形下,很多数据是相同的,我们可以考虑固定一个加数,只传入一个参数
def add1(b:Int):Int={
4+b
}
def add2(b:Int):Int={
5+b
}
//2.进一步扩展,考虑拓展固定加数改变的情况,比如固定加数又有很多种情况
def add3(a:Int):Int=>Int={
def add4(b:Int):Int={
a+b
}
add4
}
println(add3(34)(5))
val addby4=add3(4)
println(addby4(7))
简化:
def add5(a:Int):Int=>Int={a+_}
柯里化的表达:
def add6(a:Int)(b:Int)={
a+b
}
println(add6(4)(7))
4.7控制抽象
1 )传值调用: 把计算后的值传递过去
def f0(a:Int):Unit={
println("a "+a)
println("a "+a)
}
def f1():Int={
println("f1被调用")
12
}
f0(f1())
结果:
f1被调用
a 12
a 12
2 )传名调用: 把代码传递过去,有几个参数就执行几次代码块
- 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
def f1():Int={
println("f1被调用")
12
}
def f0(a: =>Int)={
println("a "+a)
println("a "+a)
}
f0(f1())
结果为:
f1被调用
a 12
f1被调用
a 12
用闭包实现一个函数,将 代码块 (返回值为Boolean)作为参数传入,递归调用 参数类型=>Unit,返回值类型Unit
def mywhile(condition: =>Boolean): (=>Unit)=>Unit ={
condition传入条件,具体操作op:执行一个代码块,且它的返回值为Unit,内层循环的返回值也为Unit
def doloop(op: =>Unit):Unit={
如果当前的condition为真
if(condition){
op
mywhile(condition)(op)
}
}
doloop _
}
var n=6
mywhile(n>=1)({
println(n)
n=n-1
})
用匿名函数实现:因为函数的名称不重要,并且传入值和返回值都确定,所以都可以删掉,用=>表示它是一个lambda表达式
def mywhile(condition: =>Boolean): (=>Unit)=>Unit ={
op=>{
if(condition){
op
mywhile(condition)(op)
}
}
柯里化的实现:
def mywhile2(condition : =>Boolean)(op : =>Unit):Unit= {
if (condition) {
op
mywhile2(condition)(op)
}
}
var n=6
mywhile2(n>=1)({
println(n)
n=n-1
})
4.8 惰性加载
1 )说明
当函数返回值被声明为 lazy 时 ,函数的执行将被推迟 ,直到我们首次对此取值,该函 数才会执行。这种函数我们称之为惰性函数。
lazy val result: Int= sum(12, 23)
println("1.函数调用")
println("2.result= " + result)
def sum(a: Int, b: Int): Int = {
println("3.sum被调用")
a + b
}
结果:
1.函数调用
3.sum被调用
2.result= 35
5、面向对象
5.1 包对象
在 Scala 中可以为每个包定义一个同名的包对象 ,定义在包对象中的成员 ,作为其对
应包下所有 class 和 object 的共享变量 ,可以被直接访问。 类似全局变量,但是是在包里。
1 )定义
package object com{
val shareValue="share"
def shareMethod()={}
}
5.2 类和对象
类 :可以看成一个模板
对象 :表示具体的事物
5.2.1 定义类
1 )回顾: Java 中的类
如果类是 public 的,则必须和文件名一致。
一般 ,一个.java 有一个 public 类
注意 :Scala 中没有public ,一个.scala 中可以写多个类。
1 )基本语法
[修饰符] class 类名 {
类体
}
说明
( 1 )Scala 语法中,类并不声明为public ,所有这些类都具有公有可见性(即默认就是 public )
( 2 )一个 Scala 源文件可以包含多个类
5.2.2 属性
属性是类的一个组成部分
1 )基本语法
[修饰符] var|val 属性名称 [ :类型] = 属性值
注 :Bean 属性(@BeanPropetry),可以自动生成规范的 setXxx/getXxx 方法
var age: Int = _ // _表示给属性一个默认空值 必须为var类型
5.3 封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它 部分只有通过被授权的操作(成员方法),才能对数据进行操作。 Java 封装操作如下,
( 1 )将属性进行私有化
( 2 )提供一个公共的 set 方法,用于对属性赋值
( 3 )提供一个公共的 get 方法,用于获取属性的值
Scala 中的 public 属性,底层实际为 private ,并通过 get 方法(obj.field())和 set 方法 (obj.field_=(value))对其进行操作。所以 Scala 并不推荐将属性设为 private ,再为其设置 public 的 get 和 set 方法的做法。但由于很多Java 框架都利用反射调用 getXXX 和 setXXX 方 法,有时候为了和这些框架兼容,也会为 Scala 的属性设置 getXXX 和 setXXX 方法(通过 @BeanProperty 注解实现)。
5.4 访问权限
1 )说明
在 Java 中,访问权限分为: public ,private ,protected 和默认。在 Scala 中,你可以通
过类似的修饰符达到同样的效果。但是使用上有区别。
( 1 )Scala 中属性和方法的默认访问权限为public ,但 Scala 中无 public 关键字。
( 2 )private 为私有权限,只在类的内部和伴生对象中可用。
( 3 )protected 为受保护权限, Scala 中受保护权限比 Java 中更严格,同类、子类可以
访问,同包无法访问。
( 4 )private[包名]增加包访问权限,包名下的其他类也可以使用
5.5 方法
1 )基本语法
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))
}
}
5.6 创建对象
1 )基本语法
val | var 对象名 [ :类型] = new 类型()
2 )案例实操
( 1 )val 修饰对象,不能改变对象的引用( 即:内存地址) ,可以改变对象属性的值。
( 2 )var 修饰对象, 可以修改对象的引用和修改对象的属性值
( 3 )自动推导变量类型不能多态,所以多态需要显示声明
5.7 构造器
和 Java 一样, Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala 类的构造器包括: 主构造器和辅助构造器
1 )基本语法
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
说明 :
( 1 )辅助构造器 ,函数的名称 this ,可以有多个 ,编译器通过参数的个数及类型
来区分。
( 2 )辅助构造方法不能直接构建对象 ,必须直接或者间接调用主构造方法。
( 3 )构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
//( 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)
}
}
5.8 参数构造器
1 )说明
Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、 var 修饰、 val 修饰
( 1 )未用任何修饰符修饰,这个参数就是一个局部变量
( 2 )var 修饰参数,作为类的成员属性使用,可以修改
( 3 )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, "男")
//person.name 不能调用 可以在类中写一个打印方法,以此来调用
person.age = 19
println(person.age)
}
}
所以建议构造器直接明确修饰符
5.9 继承和多态
1 )基本语法
class 子类名 extends 父类名 { 类体 }
( 1 )子类继承父类的属性和方法
( 2 )scala 是单继承
父类的引用指向子类的对象 Java编译看左边,执行看右边,父类没有的方法不能调用,调用的是子类重写的方法。
Person p1 = new Man;
2 )案例实操
( 1 )子类继承父类的属性和方法
( 2 )继承的调用顺序:父类构造器->子类构造器
3 )动态绑定
Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定,静态绑定属性。
5.10 抽象类
5.10.1 抽象属性和抽象方法
1 )基本语法
( 1 )定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
( 2 )定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
( 3 )定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
2 )继承&重写
( 1 )如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明 为抽象类
( 2 )重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
( 3 )子类中调用父类的方法使用 super 关键字
( 4 )子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
子类对非抽象属性重写 ,父类非抽象属性只支持 val 类型,而不支持 var。
因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写
5.11 单例对象(伴生对象)
Scala语言是完全面向对象的语言 ,所以并没有静态的操作( 即在Scala中没有静态的概 念) 。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象 来模拟类对象 ,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象 ,这个类的所有 “静态”内容都可以放置在它的伴生对象中声明。
5.11.1 单例对象语法
1 )基本语法
object Person{
val country:String="China"
}
2 )说明
( 1 )单例对象采用 object 关键字声明
( 2 )单例对象对应的类称之为伴生类 ,伴生对象的名称应该和伴生类名一致。
( 3 )单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
//( 1 )伴生对象采用 object 关键字声明
object Person {
var country: String = "China"
def newPerson(name:String):Person=new Person(name)
}
//( 2 )伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class private Person {
var name: String = "bobo"
}
object Test {
def main(args: Array[String]): Unit = {
//( 3 )伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访 问。
println(Person.country)
如果不想在外面直接new创建一个对象的话,可以在伴生对象中写一个new方法,外界直接调用这个方法,把该类变为private
val p1=Person.newPerson("xiaohe")
}
}
5.11.2 apply 方法
1 )说明
( 1 )通过伴生对象的 apply 方法, 实现不使用 new 方法创建对象。
( 2 )如果想让主构造器变成私有的,可以在()之前加上private。
( 3 )apply 方法可以重载。
( 4 )Scala 中 obj(arg)的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用
以统一面向对象编程和函数式编程的风格。
( 5 )当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构 建对象时,调用的其实时伴生对象的 apply 方法。
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)
}
//注意:也可以创建其它类型对象,并不一定是伴生类对象
}
5.12 特质(Trait)
Scala 语言中,采用特质 trait (特征)来代替接口的概念 ,也就是说,多个类具有相同
的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法 ,一个类可
以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。
Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种
补充。
1 )基本语法
trait 特质名 {
trait 主体
}
2 )案例实操
trait PersonTrait {
// 声明属性
var name:String = _
// 声明方法
def eat():Unit={
}
// 抽象属性
var age:Int
// 抽象方法
def say():Unit
}
特质基本语法
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素, 所以在使用时,也采用了 extends 关键字 ,如果有多个特质或存在父类,那么需要采用 with
关键字连接。
1 )基本语法:
没有父类 :class 类名 extends 特质 1 with 特质 2 with 特质 3 …
有父类 :class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
2 )说明
( 1 )类和特质的关系:使用继承的关系。
( 2 )当一个类去继承特质时,第一个连接词是 extends ,后面是 with。
( 3 )如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。
3 )案例实操
( 1 )特质可以同时拥有抽象方法和具体方法
( 2 )一个类可以混入(mixin)多个特质
( 3 )所有的 Java 接口都可以当做 Scala 特质使用
( 4 )动态混入 :可灵活的扩展类的功能
( 4.1 )动态混入:创建对象时混入 trait ,而无需使类混入该 trait
比如唱歌,有些人不会唱歌,有些人会唱歌,那么在创建对象的时候,会唱歌的人就去使用唱歌这个trait
( 4.2 )如果混入的 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)
}
}
由于一个类可以混入(mixin)多个 trait ,且 trait 中可以有具体的属性和方法,若混入 的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。 冲突分为以下两种:
第一种,一个类( Sub )混入的两个 trait( TraitA ,TraitB )中具有相同的具体方法,且
两个 trait 之间没有任何关系,解决这类冲突问题,直接在类( Sub )中重写冲突方法。
super调用原则:从后往前
5.13 类型检查和转换
1 )说明
( 1 )obj.isInstanceOf[T] :判断 obj 是不是 T 类型。
( 2 )obj.asInstanceOf[T] :将 obj 强转成 T 类型。
( 3 )classOf获取对象的类名。
class Person{
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person
//( 1 )判断对象是否为某个类型的实例
val bool: Boolean = person.isInstanceOf[Person]
if ( bool ) {
//( 2 )将对象转换为某个类型的实例
val p1: Person = person.asInstanceOf[Person] println(p1)
}
//( 3 )获取类的信息
val pClass: Class[Person] = classOf[Person]
println(pClass)
}
}
6. 集合
6.1 集合简介
1 )Scala 的集合有三大类:序列 Seq、集 Set、 映射 Map ,所有的集合都扩展自 Iterable 特质。
2 )对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两 个包
不可变集合: scala.collection.immutable
可变集合: scala.collection.mutable
3 )Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而 不会对原对象进行修改。类似于 java 中的 String 对象
4 )可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似 于 java 中 StringBuilder 对象
6.2 数组
6.2.1 不可变数组
1 )第一种方式定义数组
定义 :val arr1 = new Array[Int](10)
( 1 )new 是关键字
( 2 )[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定 Any
( 3 )(10) ,表示数组的大小,确定后就不可以变化
( 3.1 )查看数组
println(arr01.mkString(","))
( 3.2 )普通遍历
for (i <- arr01) {
println(i)
}
( 3.3 )简化遍历
def printx(elem:Int): Unit = {
println(elem)
}
foreach里要传一个函数
arr01.foreach(printx)
// arr01.foreach((x)=>{println(x)})
// arr01.foreach(println(_))
arr01.foreach(println)
(3.4)循环遍历
for(i<- 0 until arr.length)等价于 i<-arr.indices
println(arr(i))
3 )第二种方式定义数组
val arr1 = Array(1, 2)
( 1 )在定义数组时,直接赋初始值
( 2 )使用 apply 方法(伴生对象)创建数组对象,使用的时候可以省略 .apply
6.2.2 可变数组
1 )定义变长数组
val arr01 = ArrayBuffer[Any](3, 2, 5)
( 1 )[Any]存放任意数据类型
( 2 )(3, 2, 5)初始化好的三个元素
( 3 )ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer
6.2.3 不可变数组与可变数组的转换
1 )说明
arr1.toBuffer //不可变数组转可变数组
arr2.toArray //可变数组转不可变数组
( 1 )arr2.toArray 返回结果才是一个不可变数组, arr2 本身没有变化
( 2 )arr1.toBuffer 返回结果才是一个可变数组, arr1 本身没有变化
6.3 列表 List
6.3.1 不可变 List
1 )说明
( 1 )List 默认为不可变集合
( 2 )创建一个 List(数据有顺序,可重复)
( 3 )遍历 List
( 4 )List 增加数据
( 5 )集合间合并:将一个整体拆成一个一个的个体,称为扁平化
( 6 )取指定数据
( 7 )空集合 Nil
( 1 )List 默认为不可变集合
( 2 )创建一个 List(数据有顺序,可重复)
val list: List[Int] = List(1,2,3,4,3)
=====val list=List(1,3,5,6)
( 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)
val list2=list.:+(10)//在列表后面+
( 5 )集合间合并:将一个整体拆成一个一个的个体,称为扁平化 val list3 = List(8,9)
//val list4 = list3::list1
val list4 = list3:::list1 合并3和1
( 6 )取指定数据
println(list(0))
( 3 )遍历 List
//list.foreach(println)
//list1.foreach(println)
//list3.foreach(println)
//list4.foreach(println)
list5.foreach(println)
6.3.2 可变 ListBuffer
1 )说明
( 1 )创建一个可变集合 ListBuffer
( 2 )向集合中添加数据
( 3 )打印集合数据
( 1 )创建一个可变集合
val buffer = ListBuffer (1,2,3,4)
( 2 )向集合中添加数据
buffer.+=(5)
buffer.append(6)
buffer.insert(1,2)
( 3 )打印集合数据
buffer.foreach (println)
( 4 )修改数据
buffer(1) = 6
buffer.update(1,7)
( 5 )删除数据
buffer.- (5)
buffer.-=(5)
buffer.remove(5)
6.4 Set 集合
默认情况下, Scala 使用的是不可变集合, 如果你想使用可变集合, 需要引用
scala.collection.mutable.Set 包
6.4.1 不可变 Set
1 )说明
( 1 )Set 默认是不可变集合,数据无序
( 2 )数据不可重复
( 3 )遍历集合
//( 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)}
6.4.2 可变 mutable.Set
1 )说明
( 1 )创建可变集合 mutable.Set
6.5 Map 集合
Scala 中的 Map 和 Java 类似,也是一个散列表 ,它存储的内容也是键值对(key-value)
映射
6.5.1 不可变 Map
1 )说明
( 1 )创建不可变集合 Map
( 2 )循环打印
( 3 )访问数据
( 4 )如果 key 不存在,返回 0
//( 1 )创建不可变集合 Map
val map = Map( "a"->1, "b"->2, "c"->3 )
//( 3 )访问数据
for (elem <- map.keys) {
// 使用 get 访问 map 集合的数据,会返回特殊类型 Option(选项): 有值( Some ),无值 (None),再次get即可获得数值
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)})
map.foreach(println)
6.5.2 可变 Map
1 )说明
( 1 )创建可变集合
( 2 )打印集合
( 3 )向集合增加数据
( 4 )删除数据
( 5 )修改数据
//( 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)
map("d") = 5
//( 2 )打印集合
map.foreach((kv)=>{println(kv)})
6.6.3 集合计算简单函数
1 )说明
( 1 )求和 .sum
( 2 )求乘积 .product
( 3 )最大值 .max
( 4 )最小值 .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.6.4 集合计算高级函数
1 )说明
( 1 )过滤
遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
( 2 )转化/映射(map)
将集合中的每一个元素映射到某一个函数
( 3 )扁平化
( 4 )扁平化+映射 注: flatMap 相当于先进行 map 操作,在进行 flatten 操作
集合中的每个元素的子元素映射到某个函数并返回新集合
( 5 )分组(group)
按照指定的规则对集合的元素进行分组
( 6 )简化(归约 )
( 7 )折叠
package chapter02
import scala.collection.mutable
class Person(name: String, var age: Int, val sex: String) {
}
object Test {
def main(args: Array[String]): Unit = {
val map1 = Map("a"->1, "b"->2, "c"->3)
val map2 = mutable.Map("a"->4, "b"->5, "d"->6)
//覆盖:println(map1++map2)
//以map2作为基准,map2必须为mutable
val map3=map1.foldLeft(map2)(
(mergedMap,kv)=>{
val key=kv._1
val value=kv._2
//判断当前的k是否在map1中,有就直接加没有就以0做计算
mergedMap(key)=mergedMap.getOrElse(key,0)+value
mergedMap
}
)
println(map3) //Map(b -> 7, d -> 6, a -> 5, c -> 3)
}
}
WordCount案例
package chapter02
object WordCount {
def main(args: Array[String]): Unit = {
val stringList = List("Hello Scala Hbase kafka", "Hello Scala Hbase", "Hello Scala", "Hello hsk")
//1.对字符串按照空格进行切分
val list1: List[Array[String]] =stringList.map(_.split(" "))
//2.扁平化处理
val list2: List[String] =list1.flatten
println(list2)
//1.+2.等价于
val list3=stringList.flatMap(_.split(" "))
//分组操作
val groupMap: Map[String, List[String]] = list3.groupBy(word => word)
println(groupMap)
//对分组后的List取长度,得到出现的单词次数
val map = groupMap.map(kv => (kv._1, kv._2.length))
println(map)
//将map转化为list,并排序取前三
val sortlist : List[(String,Int)] = map.toList.sortWith(_._2 > _._2)//根据第二个元素进行排序
println(sortlist)
println(sortlist.take(3))//输出前三名
println("===============================================================================")
val tuples = List(("Hello Scala Spark World", 4), ("Hello Scala Spark hsk", 3), ("Hello Scala", 2), ("Hello", 1))
//方法一:先展开
// val newtuplelist:List[String]=tuples.map(
// kv => (kv._1.trim+" ")* kv._2 //避免直接乘导致英文单词中间没有空格
// )
// val endlist =newtuplelist.flatMap(_.split(" ")).groupBy(word =>word).map(kv => (kv._1,kv._2.length))
// .toList.sortBy(_._2)
// println(endlist)
println("==============================================================================")
//方法二:基于预先统计结果进行转化
val preCountList : List[(String,Int)] =tuples.flatMap(
tuples =>{
var strings : Array[String] = tuples._1.split(" ") //对里面的元组进行切割
strings.map(word =>(word,tuples._2))
}
)
println(preCountList)//List((Hello,4), (Scala,4), (Spark,4), (World,4), (Hello,3), (Scala,3), (Spark,3), (hsk,3), (Hello,2), (Scala,2), (Hello,1))
val preCountMap : Map[String,List[(String,Int)]] = preCountList.groupBy(tuples => tuples._1)//把map中的单词根据tuples._1分组
println(preCountMap)//Map(Scala -> List((Scala,4), (Scala,3), (Scala,2)), Hello -> List((Hello,4), (Hello,3), (Hello,2), (Hello,1)), hsk -> List((hsk,3)), Spark -> List((Spark,4), (Spark,3)), World -> List((World,4)))
val countmap: Map[String,Int] = preCountMap.mapValues( //k不变只改变values
tuplelist =>tuplelist.map(_._2).sum //_.map(_._2).sum
)
println(countmap)//Map(Scala -> 9, Hello -> 10, hsk -> 3, Spark -> 7, World -> 4)
val countlist=countmap.toList.sortWith(_._2 > _._2)
println(countlist)//List((Hello,10), (Scala,9), (Spark,7), (World,4), (hsk,3))
}
}
第 7 章 模式匹配
Scala 中的模式匹配类似于 Java 中的 switch 语法
但是 scala 从语法中补充了更多的功能,所以更加强大。
7.1 基本语法
模式匹配语法中,采用 match 关键字声明,每个分支采用 case 声关键字进行明,当需
要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹
配不成功,继续执行下一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _分支,
类似于 Java 中 default 语句。
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)
1 )说明
( 1 )如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,
若此时没有 case _ 分支,那么会抛出 MatchError。
( 2 )每个 case 中,不需要使用 break 语句,自动中断 case。
( 3 )match case 语句可以匹配任何类型,而不只是字面量。
( 4 )=> 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行 ,可以
使用{}括起来,也可以不括。
7.2 模式守卫
1 )说明
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
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))
7.3 模式匹配类型
7.3.1 匹配常量
1 )说明
Scala 中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。
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 +"
}
需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T] ,也可使
用模式匹配实现同样的功能。
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")))
}
匹配数组
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)
}
7.4 变量声明中的模式匹配
package com.atguigu.chapter8
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")
}}
7.5 for表达式中的模式匹配
package com.atguigu.chapter8
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)
}
println("---------")// 遍历value = 0 的k-v,如果v不是0,过滤
for ((k, 0) <- map) {
println(k + "-->" + 0)
}println("----------")
for ((k, v) <- map if v >= 1) {
println(k + "-->" + v)
}}
}
第 9 章 隐式转换
当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用
于将类型进行转换,实现二次编译
9.1 隐式函数
1 )说明
隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。
2 )案例实操
需求:通过隐式转化为 Int 类型增加方法。
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 关键字声明的函数称之为隐式函数
implicit def convert(arg: Int): MyRichInt = {
new MyRichInt(arg)
}
def main(args: Array[String]): Unit = {
// 当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范 围内查找能调用对应功能的转换规则,这个调用过程是由编译器完成的,所以称之为隐式转换。也称之为自动转换
println(2.myMax(6))
//正常情况下我们不能直接2.myMax(),需要new一个对象才能使用,这时候隐式函数就出来了
}
}
9.2 隐式类
在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可
以扩展类的功能,在集合中隐式类会发挥重要的作用。
1 )隐式类说明
( 1 )其所带的构造参数有且只能有一个
( 2 )隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。
object TestImplicitClass {
implicit class MyRichInt(arg: Int) {
def myMax(i: if (arg <
}
def myMin(i: if (arg <
}
}
Int): Int = { i) i else arg
Int) = { i) arg else i
def main(args: Array[String]): Unit = { println(1.myMax(3))
}
}
9.3 隐式参数
普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时, 就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
1 )说明
( 1 )同一个作用域中,相同类型的隐式值只能有一个
( 2 )编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。
( 3 )隐式参数优先于默认参数
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 //隐式参数在底层是柯里化的表达,不用括号
}
}