深度解析Scala -----------语法精细篇
1.安装Scala:
1.将安装包解压
2.配置环境变量 E:\program\scala-2.11.8
系统变量中配置:变量名:SCAL_HOME
变量值:E:\program\scala-2.11.8
确定
编辑环境变量中:新建 ==>%SCALA_HOME%/bin===>确定
注意:如果在配置完了Scala,需要从新打开cmd.exe
2.配置IDEA
2.1点击Configure--->Plugins----->Install plugin from disk...----->需要先下载scala-intellij-bin-2017.2.6.zip--->ok
---------->安装完后 :Restart IntelliJ IDEA(重新启动IDEA)
Create New Project------->Scala------->(SBT(需要FQ)此时还是用)Maven--------->Next
-------->GroupId:com.dzc ArtifactId:scala_syllabus-------->Next-------->Finsh 完成之后
点击:Enable Auto-Import
2.2.在main----->New ------->Directory------>scala(文件夹)------>此时这个文件夹标记为---->
Mark Directory as-------->Sources Root----->下滑最后几行------>选中Scala
在use library:scala-sdk-2.11.8(如果没有这个的话)如下操作
点击:Create...------->Select JAR‘s fro the new Scala SDK-------->在Sources中勾选
引用javaScala的Framework
点击项目scala_syllabus右键------->Add Framework Support -----》Browse..(浏览)---->找到安装目录
备注:点击Configure..是可以配置scala配置的
以上完了后 回到Project目录下 ------>scala----->New------>Sacla Class------>Create New Class:
Name:HelloWorld
Kind:选择Object
Scala安装和配置会详细跟大家分享,单独的
2.3 语法:
object HelloWorld{
//主函数
def main(args:Array[String]):Unit={
println("Hello Scala");
}
}
2.4常用类型:
Scala有8种数据类型:Byte,Char,Short,Int,Long,Float,Double以及Boolean
| ID | 名称 | 字节大小 |
| :--: | :-----: | :-----------: |
| 1 | Boolean | true或者false |
| 2 | Byte | 8位,有符号 |
| 3 | Short | 16位,有符号 |
| 4 | Int | 32位,有符号 |
| 5 | Long | 64位,有符号 |
| 6 | Char | 16位,无符号 |
| 7 | Float | 32位,单精度浮点数 |
| 8 | Double | 64位,双精度浮点数 |
| 9 | String | 其实就是由Char数组组成 |
Scala常用类型以及继承关系
2.5常用类型结构图
Scala中。所有的值都是类对象,而所有的类,包括值类型,都最中继承自一个同一的根类型Any.统一类型,是Scala的又一大特点。更特别的是Sca;a中还定义了机构底层类(BottomClass),比如Null和Nothing.
1)Null是所有引用类型的子类型,而Nothing是所有类型的子类型。Null类只有一个实例对象,null。类似于java中的null引用。null类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。
2)Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
3)Unit类型用来标识过程,也就是没有明确返回值的函数。由此可见,Unit类似于Java里的void。Unit只又一个实例()。这个实例也没有实质的意义。(引图)
2.6算术操作符重载
+-*/%可以完成和Java中相同的工作,但是有一点区别,他们都是方法。你几乎可以用任何符号来为方法命名。
举列子:
scala> 1 + 2 等同于: scala> 1.+(2) val a=10 var b=20 b=30 println(a+","+b) println(a+b) //40 println(a.+(b)) //40 模板借用 c
注意了:Scala中没有++,--操作符,需要通过+=,-=来实现同一的效果。
2.7调用函数与方法
在scala中,一般情况下我们不会可以的去区分函数与方法发区别。
1)调用函数,求方根
package basic //import scal.math.sqrt 如果在引用代表整个包可以引用 //导包导的就是一个对象 import scala.math._ //._是导math里面所有的对象 import scala.math.hashCode //可以直接指定你要导的具体对象是谁,hashCode是一个方法也是具体的一个对象,导包导的就是对象 object UserFunction{ def main(args:Array[String]):Unit={ val a=100; import scala.math.sqrt println(sqrt(a))//方法有返回值的就是函数,这个叫做开方函数 } }
注意了:函数是一定要有返回值的。方法可以没有返回值的。但是形式是一样的
方法强调的是过程,函数强调的是结果
scala>import scala.math_
scala>sqrt(100)
2)调用方法,静态方法(scala中没有静态方法这个概念,需要通过伴生类对象来实现(Object))
注意:静态方法可以直接类对象去调
scala>"Hello World".distinct //此时这个 Object叫做UserFunction的伴生类对象 object UserFunction{ def main(args:Array[String]):Unit={ val a=100; println(sqrt(a)) //1.静态方法调用 println(BigInt.probablePrime(16,scala.util.Random)) } }
3)调用方法,非静态方法,使用对象调用
注意:非静态对象必须根据类实例化去调用
scala>"HelloWorld".distinct scala>"Hello World".distinct //此时这个 Object叫做UserFunction的伴生类对象 object UserFunction{ def main(args:Array[String]):Unit={ //1.非静态方法调用 println("HelloWorld".distinct) //去重了 HeloWrd //如果这个方法或者这个函数没有参数的话,后面的小括号可以省略() } }
注意了:在静态方法中调用是单例,类的实例化对象是new的实例。
静态方法可以直接用类去调,非静态方法的调用必须是根据类实例化出来的对象去才能去调用。
4)apply与update方法
scala "Hello"(4) 等同于 "Hello".apply(4) Array(1,2,3) 等同于 Array.apply(1,2,3) 如: println("Hello"(4)) //拿到第四个字符 o println("Hello".apply(4)) //定义数组 int[] arr=new int[]{1,2,3} 等价于 val arr=Array(1,2,3) //将定义里面的数组打印出来的方法是mkString() (" ")这个是前行分割符 println(Array(1,2,3).mkString(" "))
apply方法是调用时可以省略方法名的方法。用于构造和获取元素:
apply():实例化方法。是将散列的数据传到一个集合里面去封装然后生成一个对象。
scala ====apply源码解析 //传入一个下标 index return 一个char所在的下标 override def apply(index:Int):Char=repr charAt index override def slice(from:Int,until:Int):String={ val start=if(from:Int,until:Int):String={ val start=if(from <0)0 else from if(until <=start || start >=repr.length) return "" val end = if(until >length)length else until repr.substring(start,end) } }
update():把一个对象里的数据取出来(与apply()方法相反)
object HelloWorld{ //主函数 def main(args:Array[String]):Unit={ //update val arr2=Array(1,2,3) arr2.update(0,2) println(arr2.mkString(" ") //2 2 3 arr2(2)=4 println(arr2.mkString(" ")) //2 2 4 } }
在StringOps中你会发现一个 def apply(n: Int): Char方法定义。update方法也是调用时可以省略方法名的方法,用于元素的更新:
scala arr(4) = 5 等同于 arr.update(4,5) 如: val arr1 = new Array[Int](5) arr1(1) = 2 等价于 arr1.update(1, 2) println(arr1.mkString(","))
2.8 option类型(Some , None)
option的应用场景: { "name":"List" "age":"22" } map.put(name,lion) map.put(age,22) map.get("sex") //这样得到的数据是null //然后对这个数据的任何的方法调用都会是空指针异常 //在开发中之前定义好的字段没有值,也必须要将值返回回去。只要这个字段有值,就给返回,没有值就不返回。 //这样就会导致你无法确定这个值是否返回。需要全部都做一次非空判断
option的应用场景:在开发之前
scala map.get("name") ->Some("Lion")-> .get() 如何将数据拿出来.get() map.get("sex") -> None //标识集合里面没有这个元素是一个空集合
scala val map1=Map("name"->"Lison","age"->"22") println(map1.get("name").get) println(map1.get("sex"))//Map中建议用get()取值 println(map1("sex"))
Scala为了单个只提供了对象的包装器,标识为那种可能存在也可能不存在的值,他只有两个有序的子类对象,一个是Some,表示某个值,另外一个是None,表为为空,通过Option的使用,避免了使用null,空字符串等方式来表示缺少某个值的做法。
Option的小结:当我们对Scala的集合进行数据读取操作的时候,如果你读取的地方有数据就把数据封装程一个Some以一个集合返回过去。如果没有数据就返回一个None。不会出现没有数据返回(null的现象)
如:
scala val map1 = Map("Alice" -> 20, "Bob" -> 30) println(map1.get("Alice")) println(map1.get("Jone"))
3.控制结构和函数
3.1if else表达式
(1.scala中没有三目运算符,因为根本不需要。2scala中if else表达式是有返回值的)难点和重点
如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型)。
例如:if else返回类型一样
val a3 = 10 val a4 = 20 if(a3 > 20){ "a3大于20" }else{ "a3小于20" } println(a4) boolean flag=true; int status=flag ? 0:1; //scala if有返回值 val status=if(flag) 0 else 1 //java中没有返回值 var status=-1; if(flag){ status=0; }else{ status=1; }
//if else 举例: def main(args:Array[String]):Unit={ //1.if else val a=10; //if else返回的是什么类型desc,desc就是什么类型。动态推断 val desc=if(a>=10){ "a大于等于10" }else{ 100 } println(desc) }
//值类型确认 var a1=10 val a2=10 //Scala中什么时候加分号 ;当你一行里面存在多条语句的时候需要加 ; val a3: Int=30;//此时初始化后,不能再赋值的情况。此时是显示了指定的值,后面的值必须符合这个类型
例如:if else返回类型不一样
val a5 = if(a3 > 20){ "a3大于20" } println(a5)
如果缺少一个判断,什么都没有返回,但是Scaka认为任何表达式都回有值,对于空值,使用Unit类,写做(),叫做无用占位符,相当于java中的void。
注意
行尾的位置不需要分号,只要能够从上线文判断出语句的终止即可。但是如果在单行中写多个语句,则需要分号分割。在Scala中,{}快包含列表达式,其结果也是一个表达式。块中最后一个表达式的只就是块的值。
3.2while表达式
Scala提供 和Java一样的While和do循环,与If语句不同,While语句本身没有值,即这个WHile语句的结果是Unit类型的()。
//退出循环 java for1: for(int i=0;i<=10;i++){ for2: for(int n=0;n<=10;n++){ break for1; } } boolean flag=true; while(flag){ if(a>=10){ flag=false; //所有语言通用的格式 //break; } }
1)while循环
var n = 1; val while1 = while(n <= 10){ n += 1 } println(while1) println(n)
//循环的终止 import scala.util.control.Breaks //在Scala里面调用无参的函数的时候,可以省略() val looper=new Breaks var count=0; looper.breakable{ while(count <=100){ count +=1; println(count) if(count==10){ resultString="a等于10" //locoper.break }else{} } println(desc) println(resuktString) } looper breakable() //breakable()是一个函数
2)while循环的中断
import scala.util.control.Breaks val loop = new Breaks loop.breakable{ while(n <= 20){ n += 1; if(n == 18){ loop.break() } } } println(n) 在Scala中的while... do ... while while的返回值是空括号。 没有返回值的 val result=while(a<10){ println("a的值是:"+a) a+=1 10 } println(result)
注意:scala并没有提供break和continue语句来退出循环,如果需要break,可以通过几种方法来做
1.使用Boolean 型的控制变量 2.使用嵌套函数,从函数中return 3.使用Breaks对象的break方法。
3.3 for表达式
Scala也为for循环这一常见的控制结构提供了非常多的特性,这些for循环的特性被称为for推导式(for comprehension)或for表达式(for expression)。
1)for示列1:to左右两边为前闭后比的访问
for(i <- 1 to 3; j <- 1 to 3){ print(i * j + " ") } println()
2)for示列2:until左右两边的为前团后开的访问
for(i <- 1 until 3; j <- 1 until 3) { print(i * j + " ") } println()
3)for示列3:引入保护式(也称条件判断式)该语句只打印1 3.保护式满足为true则进入循环内部,满足为false则跳过,类似于continue
for(i <- 1 to 3 if i != 2) { print(i + " ") } println()
4)for示列4:引入变量
for(i <- 1 to 3; j = 4 - i) { print(j + " ") } println()
5)for示列5:将遍历过程中吹的结构返回到一个,使用yield关键字
val for5 = for(i <- 1 to 10) yield i println(for5)
6)for示列6:使用花括号{}代替小括号()
for{ i <- 1 to 3 j = 4 - i} print(i * j + " ") println()
注意:
{}和()对于for表达式来说都可以。for 推导式有一个不成文的约定:当for 推导式仅包含单一表达式时使用原括号,当其包含多个表达式时使用大括号。值得注意的是,使用原括号时,早前版本的Scala 要求表达式之间必须使用分号。
3.4函数
Scala定义函数的标志格式为:
def 函数名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {函数体}
1):函数示列1:返回Unit类型的函数
def shout1(content: String) = { println(content) }
2):函数示列2:返回Unit类型的函数,但是没有显式指定返回类型。(当然也可以返回非Unit类型的值)
def shout2(content: String) = { println(content) }
3):函数示列3:返回值类型有多种可能,此时也可以省略Unit
def shout3(content: String) = { if(content.length >= 3) content + "喵喵喵~" else 3 }
4):函数示列4:带有默认值参数的函数,调用该函数时,可以只给无默认值的参数传递值,也可以都传递,新值会覆盖默认值;传递参数时如果不按照定义顺序,则可以通过参数来指定.
def shout4(content: String, leg: Int = 4) = { println(content + "," + leg) }
5):函数示列5:变长参数(不确定格式参数,类似Java的...)
def sum(args: Int*) = { var result = 0 for(arg <- args) result += arg result }
6):递归函数6:递归函数在使用时必须有明确的返回值类型,不指定会报错
def factorial(n: Int): Int = { if(n <= 0) 1 else n * factorial(n - 1) } 递归实现阶乘 def factorial(n :Int)={ if(n==1) n else //报错原因解析:n显示指定了Int类型的,在执行* 方法的时候会调Int的对象的乘方法 n *factorial(n-1) //此时代码会出错 } //正确代码 def factorial(n :Int):Int={ if(n==1) n else //报错原因解析:n显示指定了Int类型的,在执行* 方法的时候会调Int的对象的乘方法 factorial(n-1)*n //此时代码会出错 } println(factorial(3)) //6 //最标准的函数 def play2(a1:Int,a2:Int):String ={ String.valueOf(a1 + a2) } println(play2(10,20)) //30 //2 def play2(a1:Int,a2:Int)={ a1 + a2 } println(play2(10,10)) //3 def play3(a1:Int,a2:Int)=a1 + a2 println(play2(5,5)) //5指定了形参的值,可选参数 def play4(a1:Int,a2:Int)={ a1 + a2 } println(play4(1)) //11 println(play4(1,1)) //2 给函数的参数指定默认值 //5.变长 def play5(args:Int*)={ for(i <-args){ print(i) } } print(play5(1,2,3,4,5)) //1 2 3 4 5 () 为什么会有这个是因为有print 这个没有什么返回值类型,就返回()了 def play2(a1:Int,a2 :Int)={ return a1+a2 //函数在定义的时候,没有显示的指定。函数的返回值类型就不能显示return } //匿名函数一 def play7(a1:Int,a2:Int):Int={ 0 } //匿名函数二 def play7(a1:Int,a2:Int):Int=0 //匿名函数三 def play7(a1:Int,a2:Int)=0 //匿名函数四 (a1:Int,a2:Int)=0 //匿名函数五 ()=0 //这个函数没有意义,没法用 //函数的参数是一个整体,函数的参数是无参的,函数的参数是0 val play7=()=0 //将函数的对象赋值给play7这个变量,play7是变量引用的是函数变量的内存地址 println(play7)//play7引用的是<function0> println(play7()) //打印0 //函数 元组(Tuple),元组是一个集合。元组的数据存储形式是什么样的 //<function0>是不能传函数的 //<function ~~>是代表能传几个参数,在这里Tuple最大能传22个参数。
匿名函数小结:
1.匿名函数右边的函数体应该使用 =>符号来指定
2.匿名函数的函数体,可以是一个包含多行的diamond的代码块
3.函数的类型,为参数个数的类型
强调一下:能够描述函数定义的这个对象 new Studnet。new 的是Class对象,类是一个对象。想创建对象必须用类。必须用类对象创建。想创建类对象,必须用更大范围的去创建。
前提:
1.所有东西都是对象
2.所有对象的创建都依赖比它更大的范围去创建
注意:
1).Scala可以通过=右边的表达式 推断出函数的返回类型。如果函数体需要多个表达式,可以用代码块{}。
2).可以把return 当做 函数版本的break语句。
3).递归函数一定要指定返回类型。
4).变长参数通过* 来指定,所有参数会转化为一个seq序列。
3.5过程
我们将函数的返回类型为Unit的函数称值为过程.
过程和函数的区别:方法强调过程,函数强调结果
1).定义过程示列1:
def shout1(content: String) : Unit = { println(content) }
2).定义过程示列2:
def shout1(content: String) = { println(content) }
3).定义过程示列3:
def shout1(content: String) { println(content) } //没有等号的定义过程 def play6(msg:String){ } println(play6("haha")) //()打印出来的东西是空白的,如果连等号都没有它返回的就是一个空,他是一个绝对的过程 //绝对过程 def play6_1(msg:String):Unit={ //Unit显示出来就是绝对过程,只能返回这个过程 }
注意:
这只是逻辑上的细分,如果因为该概念导致了理解上的混淆,可以暂时直接跳过过程这样的描述 。毕竟过程,在某种意义上也是函数。
函数
完整定义方式:def 函数名(参数名1:参数类型1,参数名2:参数类型2):返回类型={函数题}
如何定义函数总结:
** 定义一个函数:最完整的
** 定义一个函数:没有显示的返回值类型
** 定义一个函数:带有可选参数
** 定义一个函数:有显示的返回值类型,尝试return
定义过程:
--没有=号
--显示的返回值类型为Unit
--隐式的返回值类型是Unit
*定义用于递归调用的函数
--必须有显示返回值类型
**定义匿名函数
--函数体:==>指定
--函数体可以为多行代码,使用{}包裹
--函数没有名字,但需要赋值给一个变量,否则没有意义。
3.6懒值
当val被声明为lazy时,他的初始将被推迟,直到我们首次对此取值,适用于初始化开心角度的场景.
1)lazy示列:通过lazy关键字的适用与否,来观察执行过程
object Lazy { def init(): String = { println("init方法执行") "嘿嘿嘿,我来了~" } def main(args: Array[String]): Unit = { lazy val msg = init() println("lazy方法没有执行") println(msg) } } package opera object Lazy{ def main(args:Array[String]):Unit={ def play1(a1:Int)={ println("play1方法被执行") } //为什么不打印<function1>? //<function1>是如何实现的呢? //是用匿名内部类实现的,lazy是延后执行(内存优化用得到),真正 lazy val l1=play(10) //val l1=play(10)这里拿到的是值对象,把l1的执行结果返回到赋值给l1 println("l1变量定义完毕") println(l1) } }
3.7异常(在Scala里面所有的异常都不用显示捕获)
在Java里面异常分两种:
--Error:
--Throwable:所有异常都继承Throwable
---Exception
---异常捕获的顺序:(从上到下)
---finally:最终无论是否有异常,都会执行
当碰到异常情况时,方法抛出一个异常,终止方法本身的执行,异常传递到其调用者,调用者可以处理该异常,也可以升级到它的调用者。运行系统会一直这样升级异常,直到有调用者能处理它。 如果一直没有处理,则终止整个程序。
Scala的异常的工作机制和Java一样,但是Scala没有“checked”异常,你不需要声明说函数或者方法可能会抛出某种异常。受检异常在编译器被检查,java必须声明方法所会抛出的异常类型。
抛出异常:用throw关键字,抛出一个异常对象。所有异常都市Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。
捕捉异常:在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句.
异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。
finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。
1)异常示列:异常捕获的顺序:(从上到下)
object ExceptionSyllabus {
def divider(x: Int, y: Int): Float= {
if(y == 0) throw new Exception("0作为了除数")
else x / y
}
def main(args: Array[String]): Unit = {
try {
println(divider(10, 3))
} catch {
case ex: Exception => println("捕获了异常:" + ex)
} finally {}
}
}
package opera
object ExceptionSyllabbus{
if(y ==0)throw new RuntimeException("0不能作为除数")
else
x/y
}
def main(args:Array[String]):Unit={
try{
divider(10,0)
}catch{
case ex:RuntimeException =>println("成功捕获到异常:"+ex.getMessage)
}finally{}
}
3.8数据结构
3.8.1 数据结构特点
Scala同时支持可变集合和不可变集合,不可变集合从不可变,可以安全的并发访问。
两个主要的包:
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
Scala优先采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。
不可变集合和可变集合如何区分:引用的时候,需要导包来区分。
1)不可变集合继承层次:
2)可变集合继承层次:
package opera object InterableSyllabus{ def main(args:Array[String]):Unit={ val arr1=new Array[Int](10) val arr2=Array(1,2,3) arr2(2)=10 println(arr2.mkString(" ")) //int[] arr2={1,2,3} // int[] arr2=new int[]{1,2,3} val arr3=arr2 :+arr1s val arr4=arr2 :+99 println(arr4.mkString(" ")) // 1 2 3 99 println(arr3.mkString(" ")) //1 2 3 000 00 000 00 println(arr2.mkString(" ")) //1 2 3s println(arr1.mkString(" ")) //000 00 000 00 } }
3.8.2数组 Array
1)定长数组
//定义 val arr1 = new Array[Int](10) //赋值 arr1(1) = 7 或: //定义 val arr1 = Array(1, 2)
2)变长数组
//定义 val arr2 = ArrayBuffer[Int]() //追加值 arr2.append(7) //重新赋值 arr2(0) = 7
3)定长数组与变长数组的转换
arr1.toBuffer arr2.toArray
4)多维数组
//定义 val arr3 = Array.ofDim[Double](3,4) //赋值 arr3(1)(1) = 11.11
5)与Java数组的互转
Scala数组转Java数组
val arr4 = ArrayBuffer("1", "2", "3") //Scala to Java import scala.collection.JavaConversions.bufferAsJavaList val javaArr = new ProcessBuilder(arr4) println(javaArr.command())
Java数组转Scala数组:
import scala.collection.JavaConversions.asScalaBuffer import scala.collection.mutable.Buffer val scalaArr: Buffer[String] = javaArr.command() println(scalaArr)
6)数组的遍历
for(x <- arr1) { println(x) }
3.8.3 元组Tuple
元组也是可以理解为一个容器,可以存放各种相同或者不同类型的数据。
1)元组的创建
val tuple1 = (1, 2, 3, "heiheihei") println(tuple1)
2)元组数据的访问,注意元组元素的访问有下划线,并且访问下标从1开始,而不是0
val value1 = tuple1._4 println(value1)
3)元组的遍历
方式1:
for (elem <- tuple1.productIterator) { print(elem) } println()
方式2:
tuple1.productIterator.foreach(i => println(i)) tuple1.productIterator.foreach(print(_))
3.8.4 列表List
如果List列表为空,则适用Nil来表示。
1)创建List
2)访问List元素
3)List元素的追加
4)List的创建与追加,符号“::”,注意观察取得Nil和不去掉Nil的区别
val list4 = 1 :: 2 :: 3 :: list1 :: Nil println(list4)
3.8.5 队列Queue
队列数据存取符合先进先出策略
1)队列的创建
import scala.collection.mutable val q1 = new mutable.Queue[Int] println(q1)
2)队列元素的追加
q1 += 1; println(q1)
3)向队列种追加List
q1 ++= List(2, 3, 4) println(q1)
4)按照进入队列的顺序删除元素
q1.dequeue() println(q1)
5)塞入数据
q1.enqueue(9, 8, 7) println(q1)
6)返回队列的第一个元素
println(q1.head)
7)返回队列最后一个元素
println(q1.last)
8)返回除了第一个以外的元素
println(q1.tail)
3.8.6映射Map
这个地方的学习,就类比Java的map集合学习即可。
1)构造不可变映射
val map1 = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)
2)构造可变映射
val map2 = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)
3)空的映射
val map3 = new scala.collection.mutable.HashMap[String, Int]
4对偶元组
val map4 = Map(("Alice", 10), ("Bob", 20), ("Kotlin", 30))
5)取值
如果映射种没有值,则会抛出异常,使用contains方法检查示范存在key。如果通过映射.get(键)这样的调用返回一个Option对象,要么是Some,要么是None。
val value1 = map1("Alice")//建议使用get方法得到map中的元素 println(value1)
6)更新值
map2("Alice") = 99 println(map2("Alice")) 或: map2 += ("Bob" -> 99) map2 -= ("Alice", "Kotlin") println(map2) 或: val map5 = map2 + ("AAA" -> 10, "BBB" -> 20) println(map5)
7)遍历
for ((k, v) <- map1) println(k + " is mapped to " + v) for (v <- map1.keys) println(v) for (v <- map1.values) println(v) for(v <- map1) prinln(v)
3.8.7 集 Set
集是不重复元素的结合。集不保留顺序,默认是以哈希集实现。
如果相应按照已拍下的顺序来访问集中的元素,可以使用SortedSet(已排序数据集),已排序的数据集是用红黑树实现的。
默认情况下,Scala使用的是不可变集合,如果你向使用可变集合,需要引用scala.collection。mutable.Set包.
//Set //创建 val set1=Set(1,2,3,4) println(set1) val set2=co;;ection.mutable.Set println(set2) //取值 用下标取值无意义 println(set1(4)) println(Set2) set2.add(4) set2+=5 //删除 set2.remove(2) //如果删除一个不存在的话他会不执行这个操作 set2-=1 println(set2) for(s <-set2){ println(s) }
1)Set不可变集合的创建
val set = Set(1, 2, 3) println(set)
2)Set可变集合的创建,如果import了可变集合,那么后续使用默认也是可变集合
mutableSet.add(4) mutableSet += 6 // 注意该方法返回一个新的Set集合,而非在原有的基础上进行添加 mutableSet.+(5)
3)可变集合的元素添加
mutableSet.add(4) mutableSet += 6 // 注意该方法返回一个新的Set集合,而非在原有的基础上进行添加 mutableSet.+(5)
4)可变集合的元素删除
mutableSet -= 1 mutableSet.remove(2) println(mutableSet)f
5)遍历
for(x <- mutableSet) { println(x) }
6)Set更多常用操作
| 序号 | 方法 | 描述 |
| :--: | :----------------------------: | :----------------------------: |
| 1 | def +(elem: A): Set[A] | 为集合添加新元素,并创建一个新的集合,除非元素已存在 |
| 2 | def -(elem: A): Set[A] | 移除集合中的元素,并创建一个新的集合 |
| 3 | def contains(elem: A): Boolean | 如果元素在集合中存在,返回 true,否则返回 false。 |
| 4 | def &(that: Set[A]): Set[A] | 返回两个集合的交集 |
| 5 | def &~(that: Set[A]): Set[A] | 返回两个集合的差集 |
| 6 | def ++(elems: A): Set[A] | 合并两个集合 |
| 7 | def ++(elems: A): Set[A] | 返回丢弃前n个元素新集合 |
| 8 | def dropRight(n: Int): Set[A] | 返回丢弃最后n个元素新集合 |
| 9 | def dropRight(n: Int): Set[A] | 从左向右丢弃元素,直到条件p不成立 |
| 10 | def max: A | 查找最大元素 |
| 11 | def min: A | 查找最小元素 |
| 12 | def take(n: Int): Set[A] | 返回前 n 个元素 |
3.8.8 集合元素与函数的映射
//集合中的元素与函数 val list1=List("Alice","Bob","Kotlin") def f1(x:String):String={ x.toUupperCase } val lsitll_2=list1.map(11) val listll_2=list11.map(f1) println(lsit11_2) list1.map(x=>x.toUpperCase) println(list1) def play(f:(Int,String)=>Int)={ } [map源码解析] final override def map[B,That](f:A=>B)(implicit bf:CanBuildFrom[List[A],B,That]):That={ if(bf eq List.ReusableCBF){ //判断值是否为空 if(this eq Nil)Nil.asInstanceOf[That]else{ val h=new ::[B](f(head),Nil) var t: ::[B]=h var rest=tail //遍历集合中的每一个元素,然后对f元素的调用f(rest.head),Nil) while(rest ne Nil){ val nx=new ::(f(rest.head),Nil) t.t1=nx t=nx rest=rest.tail } h.asInstanceOf([That]) } } else super.map(f) }
1)map:将集合中的每一个元素映射到某一个函数
val names = List("Alice", "Bob", "Nick") println(names.map(_.toUpperCase))
2)flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子集元素映射到某个函数并返回新的集合
val names = List("Alice", "Bob", "Nick") println(names.flatMap(_.toUpperCase()))
3.8.9化简,折叠,扫描
1)折叠,化简:将二元函数引用用集合中的函数
val list = List(1, 2, 3, 4, 5) val i1 = list.reduceLeft(_ - _) //折叠法则 val i2 = list.reduceRight(_ - _) println(i1) println(i2)
.reduceLefft(-)这个函数的执行逻辑如图所示:
2)折叠,化简:fold
fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。可以把reduceLeft看做简化版的foldLeft。相关函数:fold,foldLeft,foldRight,可以参考reduce的相关方法理解。
val list2 = List(1, 9, 2, 8) val i4 = list2.fold(5)((sum, y) => sum + y) println(i4) //25 val myFunction=(x:Int)=>(y:Int)=>x+y println(myFunction(10)) //<funcition1>
foldRight
val list3 = List(1, 9, 2, 8) val i5 = list3.foldRight(100)(_ - _) println(i5)
foldLeft和foldRight有一种缩写方法对应分别是:/:和:
列如:foldLeftval list4 = List(1, 9, 2, 8) val i6 = (0 /: list4)(_ - _) println(i6)
3)统计一句话中,各个文字出现的次数
val sentence = "一首现代诗《笑里藏刀》:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈刀哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈" //m + (“一” -> 1, “首” -> 1, “哈” -> 1) val i7 = (Map[Char, Int]() /: sentence)((m, c) => m + (c -> (m.getOrElse(c, 0) + 1))) println(i7)
4)折叠,化简,扫描
这个理解需要结合上面的知识点,扫描,即对某个集合的所有元素做fold操作,但是会把产生的所有中间结构放置于一个集合中保存。
val i8 = (1 to 10).scanLeft(0)(_ + _) println(i8)
3.8.10拉链
val list1 = List("15837312345", "13737312345", "13811332299") val list2 = List(17, 87) println(i1) val zip1=List("12323423",123123) val zip2=List("孙悟空","猪八戒") println(zip1 zip zip2) println(zipResultList) val zipMap=mutable.Map[String,String]() for(e< -zipResultList){ zipMap.add(e) } println(zipMap)
3.8.11迭代器
你可以通过iterator方法从集合获得一个迭代器,通过while循环和for表达式对集合进行遍历。
val iterator = List(1, 2, 3, 4, 5).iterator while (iterator.hasNext) { println(iterator.next()) } 或: for(enum <- iterator) { println(enum) }
3.8.12 流 Stream
stream是一个集合。这个集合,可以用于存放,无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则。
1)使用#::得到一个Stream
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
2)传递一个值,并打印stream集合
val tenOrMore = numsForm(10) println(tenOrMore)
3)tail的每一次使用,都回动态的向stream集合按照规则生产新的元素
println(tenOrMore.tail) println(tenOrMore)
4)使用map映射stream的元素并镜像一些计算
println(numsForm(5).map(x => x * x))
3.8.13视图View(数据结构)
Stream的懒执行行为,你可以对其他集合应用view方法来得到类似的效果,该方法产出一
个其方法总是被懒执行的集合。但是view不会缓存数据,每次都要重新计算.
例如:我们找到10万以内,所有数字倒序排列还是它本身的数字。
View 强调的是过程,什么时间需要什么时间生成。先产生先用 val mumStream=numsForm() //numStream->(0,?) val s1=numStream.tail val s2=s1.tail println(s1) println(s2) val viewSquares = (1 to 100000) .view .map(x => { // println(x) x.toLong * x.toLong }).filter(x => { x.toString == x.toString.reverse }) println(viewSquares(3)) for(x <- viewSquares){ print(x + ",") } val view=(1L to 100000000L).view.map(x=>x).filter(x=>x.toString.reverse==x.toString) println(view.mkString(" ")) 总结;Steam会缓存数据view不会,相同点是两者都是懒值性 //stream def numsForm(initNum:BigInt):Stream[BigInt]={ initNum#::numsForm(initNum+1) } val numStream=numsForm(0)//numStream->(0,?) val s1=numStream.tail // val s2=s1,tail println(s1) println(s2) println(numStream.tail.tail.tail) //view val view=()
3.8.14线程安全的集合
1)所有线程安全的集合都是以Synchronized开头的集合,例如:
SynchronizedBuffer SynchronizedMap SynchronizedPriorityQueue SynchronizedQueue SynchronizedSet SynchronizedStack
3.8.15 并行集合
Scala为了充分使用多核CPU,提供了冰箱集合(有别于签名的串行集合),用于多喝环境的并行计算:主要用到的算法有:
Divide and conquer:分治算法,Scala通过splitters,combiner等抽象层来实现,主要原理是江计算工作分解很多任务,分发给一下处理器去完成,并江它们处理结构合并返回
Work stealin:算法,主要用于任务调度复制均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样大道尽早感悟的目的。
1)打印1~5
(1 to 5).foreach(println(_)) println() (1 to 5).par.foreach(println(_))
2)查看并行集合中原始访问的线程
val result1 = (0 to 10000).map{case _ => Thread.currentThread.getName}.distinct val result2 = (0 to 10000).par.map{case _ => Thread.currentThread.getName}.distinct println(result1) println(result2)
3.8.16操作符
这部分内容没有表可以去理解和记忆
1)如果想在变量名,类名等定义中使用语法关键字(保留字),可以配合反引号反引号:
val `val` = 42
2)这种形式叫中置操作符,A操作符B等同于A.操作(B)
3)后置操作符,A操作符等于A.操作符,如果操作符定义的时候不带()则调用时不能加括号
4)前置操作符,+,-,!,~等操作符A等同于A.unary_操作符
5)赋值操作符,A操作符=B等同于A=A操作符 B
3.9模式匹配
不需要break
使用case_ 进行通配(默认匹配)
3.9.1 .switch
1)与default等效的时捕获所有的case_模式.如果没有模式匹配,抛出MatchError,每个case中,不用break语句。可以在match中使用任何类型,而不仅仅是数字.
var result = 0; val op : Char = ‘-‘ op match { case ‘+‘ => result = 1 case ‘-‘ => result = -1 case _ => result = 0 } println(result)
3.9.2 .守卫
1)像if表达式一样,match也提供守卫功能,守卫可以是任何Boolean条件
for (ch <- "+-3!") { var sign = 0 var digit = 0 ch match { case ‘+‘ => sign = 1 case ‘-‘ => sign = -1 case _ if ch.toString.equals("3") => digit = 3 case _ => sign = 0 } println(ch + " " + sign + " " + digit) }
3.9.3 .模式中的变量
val str = "+-3!" for (i <- str.indices) { var sign = 0 var digit = 0 str(i) match { case ‘+‘ => sign = 1 case ‘-‘ => sign = -1 case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10) case _ => } println(str(i) + " " + sign + " " + digit) }
3.9.4 .类型模式
1)可以匹配对象的任意类型,但是不能直接匹配泛型类型,这样描述比较抽象,看下面的例子:
这样做的意义在于,避免了使用isInstanceOf和asInstaneOf方法。
val a = 8 val obj = if(a == 1) 1 else if(a == 2) "2" else if(a == 3) BigInt(3) else if(a == 4) Map("aa" -> 1) else if(a == 5) Map(1 -> "aa") else if(a == 6) Array(1, 2, 3) else if(a == 7) Array("aa", 1) else if(a == 8) Array("aa") val r1 = obj match { case x: Int => x case s: String => s.toInt case BigInt => -1 //不能这么匹配 case _: BigInt => Int.MaxValue case m: Map[String, Int] => "Map[String, Int]类型的Map集合" case m: Map[_, _] => "Map集合" case a: Array[Int] => "It‘s an Array[Int]" case a: Array[String] => "It‘s an Array[String]" case a: Array[_] => "It‘s an array of something other than Int" case _ => 0 } println(r1 + ", " + r1.getClass.getName)
注意:Map类型的泛型在匹配的时候,会自动删除泛型类型,只会匹配大盘Map类型,而不会精确道Map里面的泛型类型
3.9.5 .匹配数组,列表,元组
Array(0)匹配只有一个元素且为0的数组Array(x,y)匹配数组有两个元素,并将两个元素赋值为x和y。
Array(0,_*)匹配数组以0开始.
1)匹配数组
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))) { val result = arr match { case Array(0) => "0" case Array(x, y) => x + " " + y case Array(x, y, z) => x + " " + y + " " + z case Array(0, _*) => "0..." case _ => "something else" } println(result) }
2)匹配列表
与匹配数组相似,同样可以应用于列表
for (lst <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) { val result = lst match { case 0 :: Nil => "0" case x :: y :: Nil => x + " " + y case 0 :: tail => "0 ..." case _ => "something else" } println(result) }
3)匹配元组
同样可以应用元祖
for (pair <- Array((0, 1), (1, 0), (1, 1))) { val result = pair match { case (0, _) => "0 ..." case (y, 0) => y + " 0" case _ => "neither is 0" } println(result) }
3.9.6.提取器
模式匹配,什么才算是匹配
1)unapply
调用unapply,传入number
接受返回值并判断返回值是None,还是Some
如果是Some,则将其解开,并将其中的值赋值给n(就是case Square(n)中的n)
创建object Square: object Square { def unapply(z: Double): Option[Double] = Some(math.sqrt(z)) } 模式匹配使用: val number: Double = 36.0 number match { case Square(n) => println(s"square root of $number is $n") case _ => println("nothing matched") } def match5()={ val n=36.0 n match{ case Square(result)=>println(s"${n}+",s"+${RESULT}") } }
2)unapplySeq
调用unapplySeq,传入namesString
接受返回值,并判断返回值是None,还是Some
如果是Some,则将其解开
判断解开之后得到的sequence中的元素的格式是否是三个
如果是三个,则把三个元素分别取出,赋值给first,second和third
调用unapplySeq,传入
创建object Names: object Names { def unapplySeq(str: String): Option[Seq[String]] = { if (str.contains(",")) Some(str.split(",")) else None } } 模式匹配使用: val namesString = "Alice,Bob,Thomas" namesString match { case Names(first, second, third) => { println("the string contains three people‘s names") println(s"$first $second $third") } case _ => println("nothing matched") }
3.9.7.变量声明中的模式
match中每一个case都可以单独提取出来,意思是一样的,如下:
val (x, y) = (1, 2) val (q, r) = BigInt(10) /% 3 val arr = Array(1, 7, 2, 9) val Array(first, second, _*) = arr println(first, second)
3.9.8 .for 表达式中的模式
import scala.collection.JavaConverters._ for ((k, v) <- System.getProperties.asScala) println(k + " -> " + v) for ((k, "") <- System.getProperties.asScala) println(k) for ((k, v) <- System.getProperties.asScala if v == "") println(k)
注意:for中陪陪会自动忽略失败的匹配
3.9.9.样例类
主构造器跟着类名,辅助构造器在类题里面的。的辅助构造器必须 ,
样例首先是类,除此之外它是为模型匹配而优化的类,样例类用casse 关键字进行声明:
1)样例类的创建
package unit6 abstract class Amount case class Dollar(value: Double) extends Amount case class Currency(value: Double, unit: String) extends Amount case object Nothing extends Amount
2)当我们有一个类型为Amount的对象时,我们可以用模式匹配来匹配他的类型,并及那个属性值绑定到变量:
> for (amt <- Array(Dollar(1000.0), Currency(1000.0, "EUR"), Nothing)) { val result = amt match { case Dollar(v) => "$" + v case Currency(_, u) => u case Nothing => "" } println(amt + ": " + result) }
当你声明样例类是,如下几件失去会自动发生:
--构造其中的每一个参数都称为val --除非它被显示地声明为var(不建议这样做)
--在半对象中提供apply方法让你不用new 关键字就能 构造出相应的对象,比如Dollar(29.95)或Currency(29.95,"EUR")
--将生成toString,equals,hashCode和copy方法一一除非显示地给出这些方法的定义。
除上述外,样例类和其他类型完全一样。你可以添加方法和字段,扩展它们。
3.9.10.Copy方法和带名参数
copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性.
val amt = Currency(29.95, "EUR") val price = amt.copy(value = 19.95) println(amt) println(price) println(amt.copy(unit = "CHF"))
3.9.11.Case语句的中置(缀)表达式
什么是中置表达式
如果unapply方法产生一个元组,你可以在case语句使用中置表示法。
List(1, 7, 2, 9) match { case first :: second :: rest => println(first + second + rest.length) case _ => 0 } 当把多个中置表达式放在一起的时候: result match {case p + q + r=>...} //case +(+(p,q),r) 如果是以冒号结尾的操作符 case p:q:r //case ::(p,::(q,r)) //样例 当把多个中置表达式放在一起的时候: result match {case p + q + r=>...} //case +(+(p,q),r) 如果是以冒号结尾的操作符 case p:q:r //case ::(p,::(q,r)) List(1,2,3,4,6) //也可以写成 1+:2+:......
3.9.12.匹配嵌套结构
1)创建样例类
abstract class Item case class Article(description: String, price: Double) extends Item case class Bundle(description: String, discount: Double, item: Item*) extends Item
2)匹配嵌套结构
val sale = Bundle("愚人节大甩卖系列", 10, Article("《九阴真经》", 40), Bundle("从出门一条狗到装备全发光的修炼之路系列", 20, Article("《如何快速捡起地上的装备》", 80), Article("《名字起得太长躲在树后容易被地方发现》",30)))
3)将descr绑定到第一给Article的描述
val result1 = sale match { case Bundle(_, _, Article(descr, _), _*) => descr } println(result1)
4)通过@表示放将嵌套的值绑定到变量。_*绑定剩余Item到rest
val result2 = sale match { case Bundle(_, _, art @ Article(_, _), rest @ _*) => (art, rest) } println(result2)
5)不使用_*绑定剩余Item到rest
val result3 = sale match { case Bundle(_, _, art @ Article(_, _), rest) => (art, rest) } println(result3)
6)计算某个Item价格的函数,并调用
def price(it: Item): Double = { it match { case Article(_, p) => p case Bundle(_, disc, [email protected]_*) => its.map(price _).sum - disc } } println(SwitchBaseSyllabus.price(sale))
3.9.13密封类
如果想让 case 类的所有子类都必须在声明该类的相同的文件中定义,可以将样例类的同样超类声明为sealed,叫做密封类,迷蒙就是外部用户不能再其他文件中定义子类
3.9.14.模拟枚举
样例类可以模拟出枚举类型
1).创建密封样例类(不密封也可以,在这里只是为了一下sealed关键字)
package unit6 sealed abstract class TrafficLightColor case object Red extends TrafficLightColor case object Yellow extends TrafficLightColor case object Green extends TrafficLightColor
2)模拟枚举
for (color <- Array(Red, Yellow, Green)) println( color match { case Red => "stop" case Yellow => "slowly" case Green => "go" })
3.9.15.偏函数
偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算
scala val f: PartialFunction[Char, Int] = { case ‘+‘ => 1 case ‘-‘ => -1 } println(f(‘-‘)) println(f.isDefinedAt(‘0‘)) println(f(‘+‘)) // println(f(‘0‘))
##### 再深入探讨一点点:
我们敌营一个将List集合里面数据+1的偏函数:
val f1 = new PartialFunction[Any, Int] { def apply(any: Any) = any.asInstanceOf[Int] + 1 def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false } val rf1 = List(1, 3, 5, "seven") collect f1 println(rf1) 如上的功能,等同于: def f2: PartialFunction[Any, Int] = { case i: Int => i + 1 } val rf2 = List(1, 3, 5, "seven") collect f2 println(rf2)
4.高阶函数(Higher-Order Funtion)
什么是高阶函数?高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果.
高阶函数是干嘛的?操作其他函数的函数
4.1.作为参数的函数
1)函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型)=>返回类型
def plus(x: Int) = 3 + x val result1 = Array(1, 2, 3, 4).map(plus(_)) println(result1.mkString(","))
注意: 带有一个参数的函数的类型是function1,带有两个是function2,以此类推
4.2.匿名函数
即没有名字的函数,可以通过函数表达式来设置匿名函数
val triple = (x: Double) => 3 * x println(triple(3))
4.3.高阶函数
能够接受函数作为参数的函数,叫做高阶函数.1)高阶函数的使用
def highOrderFunction1(f: Double => Double) = f(10) def minus7(x: Double) = x - 7 val result2 = highOrderFunction1(minus7) println(result2)
2)高阶函数同样可以返回函数类型
def minusxy(x: Int) = (y: Int) => x - y val result3 = minusxy(3)(5) println(result3
4.4. 参数(类型)推断
// 传入函数表达式 highOrderFunction1((x: Double) => 3 * x) // 参数推断省去类型信息 highOrderFunction1((x) => 3 * x) // 单个参数可以省去括号 highOrderFunction1(x => 3 * x) // 如果变量旨在=>右边只出现一次,可以用_来代替 highOrderFunction1(3 * _)
4.5.闭包
闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来。
def minusxy(x: Int) = (y: Int) => x - y
这就是一个闭包:
1)匿名函数(y:Int)=>x-y嵌套minusxy函数中。
2)匿名函数(y:Int)=x-y 使用了该匿名函数之外的变量x
3)函数minusxy返回了引用了局部变量的匿名函数
再举一例:
def minusxy(x: Int) = (y: Int) => x - y val f1 = minusxy(10) val f2 = minusxy(10) println(f1(3) + f2(3))
4.6.柯里化:
函数编程中,接受多个参数的函数都可以转换为接受单个参数的函数,这个转化过程就叫柯里化。柯里化就是证明了函数只有些一个参数而已。
1)柯里化式例:
def mul(x: Int, y: Int) = x * y println(mul(10, 10)) def mulCurry(x: Int) = (y: Int) => x * y println(mulCurry(10)(9)) def mulCurry2(x: Int)(y:Int) = x * y println(mulCurry2(10)(8))
2)柯里化的应用:
//比较两个字符串在忽略大小写的情况小是否相等,注意,这里是两个任务: 1.全部转大写(或小写) 2.比较是否相等 针对这两个操作,我们用一个函数去处理的思想,其实无意间也变成了两个函数处理的思想. val a = Array("Hello", "World") val b = Array("hello", "world") println(a.corresponds(b)(_.equalsIgnoreCase(_))) ==================================================== 其中corres函数的源码如下: def corresponds[B](that: GenSeq[B])(p: (A,B) => Boolean): Boolean = { val i = this.iterator val j = that.iterator while (i.hasNext && j.hasNext) if (!p(i.next(), j.next())) return false !i.hasNext && !j.hasNext }
注意: 不要设立柯里化存在的意义这样的命题,柯里化,是面向函数思想的必然产生结果。
4.7控制抽象
控制抽象是一类函数:
1.参数是函数
2.函数参数没有输入值也没有返回值。
函数表示变量和应变量的映射关系。
1)使用示例:
def runInThread(f1: () => Unit): Unit = { new Thread { override def run(): Unit = { f1() } }.start() } runInThread { () => println("干活咯!") Thread.sleep(5000) println("干完咯!") }
同一个线程,可以动态的向里面塞不同的任务去执行。
可以再简化一下,省略(),看如下形式:
def runInThread(f1: => Unit): Unit = { new Thread { override def run(): Unit = { f1 } }.start() } runInThread { println("干活咯!") Thread.sleep(5000) println("干完咯!") }
2)进阶用法:实现类似while的unil函数
def until(condition: => Boolean)(block: => Unit) { if (!condition) { block until(condition)(block) } } var x = 10 until(x == 0) { x -= 1 println(x) }
4.8. 类
4.8.1.简单类和无参方法
1)类的定义可以通过class关键字实现,如下:
package unit7 class Dog { private var leg = 4 def shout(content: String) { println(content) } def currentLeg = leg }
使用这个类
val dog = new Dog dog shout "汪汪汪" println(dog currentLeg)
注意:在Scala中,类并不声明为public,一个Scala源文件可以包含多个类。所有这些类都具有公有可见性。调用无参方法时,可以加(),也可以不加;如果方法定义中不带括号,那么调用时就不能带括
4.8.2.Getter Setter方法
1)对于scala类中的每一个属性,编译后,会有一个私有的字段和想拥有的getter,setter方法生成:
//getter println(dog leg) //setter dog.leg_=(10) println(dog currentLeg)
当然了,你也可以不使用自动生成的方式,自己定义getter和setter方法
class Dog2 { private var _leg = 4 def leg = _leg def leg_=(newLeg: Int) { _leg = newLeg } }
使用之:
val dog = new Dog dog shout "汪汪汪" println(dog currentLeg)
注意: 自己手动创建变量的getter和setter方法需要遵循以下原则:
1)字段属性名以“_”作为前置,如:__leg
2)getter方法定义为:def leg=leg
3)setter方法定义时,方法名为属性名去掉前缀,后缀是:“leg=”,如例子所示
4.8.3.对象私有字段
1)变量:workDetails 在封装包professionl中的任何类中可访问。
封闭包:friends的任何类都可以被society包中任何类访问.
变量:secrets只能在实例方法的隐式对象(this)中访问.
package unit7 //当前下面所有的内容都属于这个包里面的 package society { package professional { class Executive { private[professional] var workDetails = null private[society] var friends = null private[this] var secrets = null def help(another: Executive) { println(another.workDetails) // println(another.secrets) 报错:访问不到 } } } } 4.8.4.Bean属性
JavaBeans规定定义了Java的属性是像getXXX()和SetXXX()的方法。许多Java工具都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样的方法会自动生成。
1)创建一个Bean,使用@BeanProperty注解标识某个属性变量
package unit7 import scala.beans.BeanProperty class Person { @BeanProperty var name: String = _ }
2)通过getName,setName访问属性
val person=new Person person.setName("Nick") person.getName println(person.name) 特别提示: Person将会生成四个方法: --name:String --name_=(newValue:String): Unit --getName():String --setName(newValue:String):Unit
4.8.3.1.Bean属性
JavaBeans规范定义了Java的属性是像getXXX()和setXXX()的方法。许多Java工具都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样的方法会自动生成。
1)创建一个Bean,使用@BeanProperty注解标识某个属性变量
package unit7 import scala.beans.BeanProperty class Person { @BeanProperty var name: String = _ }
2)通过getName,SetName访问属性
val person = new Person person.setName("Nick") person.getName println(person.name)
注意
Person将会生成四个方法:
--name:String
--name_=(newValue:String): Unit
--getName():String
--setName(newValue:String):Unit
4.8.4.构器器
Scala中构造器分为主构造器和辅助构造器
1)主构造的参数 直接放置于类名
定义类: class ClassConstructor (var name: String, private var price: Double){ def myPrintln = println(name + "," + price) } 执行: val classConstructor = new ClassConstructor("《傲慢与偏见》", 20.5) classConstructor.myPrintln
2)主构造器会执行定义中的所有语句
定义类: class ClassConstructor2(val name: String = "", val price: Double = 0) { println(name + "," + price) } 执行: val classConstructor2 = new ClassConstructor2("aa", 20) val classConstructor2_2 = new ClassConstructor2()
3)通过private 设置的主构造器的私有属性
4)如果不带val和var的参数至少被一个方法使用,该参数将自动升级为字段,这时,name和price就变成了类的不可变字段,而且这两个字段是对象私有的,这类似于private[this]val字段的效果。
否则,该参数将不被保存为字段,即实例化该对象时传入的参数值,不会被保留在实例化后的对象之后
序号 主构造器参数 生成的字段/方法 1 name:String 对象私有字段。如果没有方法使用name,则没有该字段 2 private val/var name:String 私有字段,私有的getter和setter方法 3 var name:String 私有字段,公有的getter和setter方法 4 @BeanProperty val/var name:String 私有字段,公有的Scala版和Java版的getter和setter方法 如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象
class Person private(){.....}
5)辅助构造器名称为this,通过不同参数进行区分,每一个辅助构造器都必须以主构造器或者已经定义的辅助构造器调用开始
class Person { private var name = "" private var age = 0 def this(name: String) { this() this.name = name } def this(name: String, age: Int) { this(name) this.age = age } def description = name + " is " + age + " years old" }
6)嵌套类
即,在class中,再定义一个class.依此类推
Java中的内部类从属外部类。Scala中内部类从属实例。
1)创建一个嵌套类,模拟局域网的聊天场景
import scala.collection.mutable.ArrayBuffer //嵌套类 class Network { class Member(val name: String) { val contacts = new ArrayBuffer[Member] } private val members = new ArrayBuffer[Member] def join(name: String) = { val m = new Member(name) members += m m } }
2)使用该嵌套类
//创建两个局域网 val chatter1=new Network val chatter2=new Network //Fred 和 Wilma加入局域网1 val fred = chatter1.join("Fred") val wilma = chatter1.join("Wilma") //Barney加入局域网2 val barney = chatter2.join("Barney") //Fred将同属于局域网1中的Wilma添加为联系人 fred.contacts += wilma //fred.contacts += barney //这样做是不行的,Fred和Barney不属于同一个局域网,即,Fred和Barney不是同一个class Member实例化出来的对象 在Scala中,每个实例都有它自己的Member类,就和他们有自己的members字段一样。也就是说,chatter1.Member和chatter2.Member是不同的两个类。也就是所谓的:路径依赖类型,此处需要详细解释之。 如果想让members接受所有实例的Member,一般有两种办法: 1) 将Member作为Network的伴生对象存在 创建类: import scala.collection.mutable.ArrayBuffer // 伴生对象 class Network2 { private val members = new ArrayBuffer[Network2.Member] def join(name: String) = { val m = new Network2.Member(name) members += m m } def description = "该局域网中的联系人:" + (for (m <- members) yield m.description).mkString(", ") } object Network2 { class Member(val name: String) { val contacts = new ArrayBuffer[Member] def description = name + "的联系人:" + (for (c <- contacts) yield c.name).mkString(" ") } } 使用: val chatter3 = new Network2 val chatter4 = new Network2 //Fred 和 Wilma加入局域网1 val fred2 = chatter3.join("Fred") val wilma2 = chatter3.join("Wilma") //Barney加入局域网2 val barney2 = chatter4.join("Barney") //Fred将同属于局域网3中的Wilma添加为联系人 fred2.contacts += wilma2 //Fred将不同属于局域网3中,属于局域网4中的的Wilma添加为联系人 fred2.contacts += barney2 println(chatter3.description) println(chatter4.description) println(fred2.description) println(wilma2.description) println(barney2.description) 2) 使用类型投影,注意留意关键符号:“#” 创建类: import scala.collection.mutable.ArrayBuffer //投影 class Network3 { class Member(val name: String) { val contacts = new ArrayBuffer[Network3#Member] } private val members = new ArrayBuffer[Member] def join(name: String) = { val m = new Member(name) members += m m } } 使用: val chatter5 = new Network3 val chatter6 = new Network3 //Fred 和 Wilma加入局域网1 val fred3 = chatter5.join("Fred") val wilma3 = chatter5.join("Wilma") //Barney加入局域网2 val barney3 = chatter6.join("Barney") fred3.contacts += wilma3 尖叫提示:与Java一样,在嵌套类中,如果想得到外部类的实例化对象的引用,可以使用“外部类.this”的方式得到。 九 对象 9.1 单例对象 Scala中没有静态方法和静态字段,可以用object这个语法结构来达到同样的目的。 object Dog { println("已初始化...") private var leg = 0 def plus() = { leg += 1 leg } } 对象的构造器在该对象第一次使用时调用。如果对象没有使用过,他的构造器也不会被执行。 对象基本具有类的所有特性,就是一点,你不能设置构造器的参数。 9.2 伴生对象 Java中的类可以既有实例方法又有静态方法,Scala中可以通过伴生对象进行实现。如下: class Cat { val hair = Cat.growHair private var name = "" def changeName(name: String) = { this.name = name } def describe = println("hair:" + hair + "name:" + name) } object Cat { private var hair = 0 private def growHair = { hair += 1 hair } } 测试: val cat1 = new Cat val cat2 = new Cat cat1.changeName("黑猫") cat2.changeName("白猫") cat1.describe cat2.describe 提示:类和它的伴生对象可以相互访问私有特性,他们必须存在同一个源文件中。必须同名
4.8.5.Apply方法:
1)apply方法一般都声明在伴生类对象中,可以用来实例化伴生类的对象:
class Man private(val sex: String, name: String) { def describe = { println("Sex:" + sex + "name:" + name) } } object Man { def apply(name: String) = { new Man("男", name) } } 测试 val man1 = Man("Nick") val man2 = Man("Thomas") man1.describe man2.describe
2)叶可以用来实现单例模式,我们只需要对上尚书 例子稍加 改进
class Man private(val sex: String, name: String) { def describe = { println("Sex:" + sex + "name:" + name) } } object Man { var instance: Man = null def apply(name: String) = { if(instance == null) {
测试
val man1 = Man("Nick") val man2 = Man("Thomas") man1.describe man2.describe ?```scala
4.8.6 应用程序对象
每一个Scala应用程序都需要从一个对象的main方法开始执行,这个方法的类型为Array[String]=>Unit:
object Hello { def main(args: Array[String]) { println("Hello, World!") } } 或者扩展一个App特质 object Hello extends App { if (args.length > 0) println("Hello, " + args(0)) else println("Hello, World!") }
4.8.7.枚举
Scala中 没有枚举类型,定义一个扩展Enumeration类的对象,并以value调用初始化枚举中的所有可能指:
object TrafficLightColor extends Enumeration { val Red = Value(0, "Stop") val Yellow = Value(1, "Slow") val Green = Value(2, "Go") }
测试
println(TrafficLightColor.Red) println(TrafficLightColor.Red.id) println(TrafficLightColor.Yellow) println(TrafficLightColor.Yellow.id) println(TrafficLightColor.Green) println(TrafficLightColor.Green.id)
4.9.包和引用
4.9.1.包/作用域
在Java和Scala中管理项目可以使用包结构,C和C#使用命名空间。
对于package,有如下几种形式:
1)形式体现
package com.nick.impatient.people class Person{ val name = "Nick" def play(message: String): Unit ={ } }
等同于
package com.nick.impatient package people class Person{ val name = "Nick" def play(message: String): Unit ={ } }
等同于
package com.nick.impatient{// com和com.nick的成员在这里不可见 package people{ class Person{ val name = "Nick" def play(message: String): Unit ={ } } } }
注意:位于文件顶部不带花括号的包声明在整个文件范围内有效。
总结:
1.包也可以 像内部类的那样嵌套作用于原则:可以直接向上访问。即,子package中直接访问父 package中的内容。(即:作用域)
2.包对象可以持有函数和变量
3.引入语句可以引入包,类和对象
4.源文件的目录和包之间并没有强制的关联关系
5.可以在同一个Scala文件中,声明多并列的package
6.包名可以相对也可以绝对。比如,访问Ar‘ra‘yBuffer的绝对路径是:
root.scala.collection.mutable.ArrayBuffer
4.9.2.包对象
包对象可以包含类,对象和特质trait,但不能包含函数或变量的定义。很不幸,这是Java虚拟机的局限。吧工具函数或常量添加到包而不是某个Utils对象,这是更加合理的做法。包对象的出现正是为了解决这个局限。每个包都可以由一个包对象。你需要在父包中定义它,且名称域子包一样。
package com.nick.impatient package object people { val defaultName = "Nick" } package people { class Person { var name = defaultName // 从包对象拿到的常置 } }
4.9.3包可见性
在Java中,没有被声明为public ,private 或者protected 的类成员在包含该类的包中可见。在Scala中,你可以通过修饰符达到同样的效果。以下方法在它自己的包可见:
package com.nick.impatient.people class Person { private[people] def description="人的名字:" + name }
当然,也可以将可见延展到上层包:
private[impatient] def description="人的名字:" + name
4.9.4. 引入
在Scala中,import 语句可以出现在任何地方,并不仅限于文件顶部。import语句的效果一直延伸到该语句的块末尾:这何有的用的特性,尤其是对于通配引入而言。从多个源引入大量名称总是让人担心。事实上,有些Java程序员特别不喜欢通配引入,以至于从不使用这个特性,而是让IDE帮他生成一长串引入语句。通过将引入放置在需要这些引入的地方,你可以大幅减少可能名称冲突。
4.9.5.重命名和隐藏方法
1)重新命名
这样一来,JavaHashMap就是Java.util.HashMap,而HashMap则对应
scala.collection.mutable.HashMap
import java.util.{ HashMap=>JavaHashMap, List} import scala.collection.mutable._
2)隐藏
选取器HashMap=>_将隐藏某个成员而不是重命名它。这仅在你需要引入其他成员是有用:
现在,HashMap很明确的便指向了scala.collection.mutable.HashMap,因为java.util.HashMap被隐藏起来了.
注意了:
每个Scala程序都隐式地如下代码开始:
importjava.lang._
importscala._
importPredef._
和java程序一样,java.lang总是被引入。接下来,scala包被引入,不过方式有些特殊。不像所有其他引入,这个引入被允许可以覆盖之前的引入。举类来说,scala.StringBufilder覆盖java.lang.StringBuilder而不是与之冲突。最后,Predef对象被引入。它包含了相当多有的函数,这些同样可以被放置在Scala包对象中,不过Predef在Scala还没有加入包对象之前就存在了
由于scala包默认被引入,对于那些以Scala开头的包,你完全不需要写这个前缀。
collection.mutable.HashMap>上述代码可以这样写
scala.collection.mutable.HashMap
5.继 承
5.1继承类
和java一样使用extends关键字,在定义中给出子类需要而没有的字段和方法,或者重写超类的方法。
注意
如果类声明为final,他将不能被继承。如果单个方法声明为final,将不能被重写
5.2.重写方法
重写一个非抽象发拿高分需要用override修饰符,调用超类的方法使用supper关键字
5.3.类型检查和转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
1)classOf[String]就如果Java的String class
2)obj.isInstanceOf[T]就如果Java的obj instanceof T
3)obj.asInstanceOf[T]就如果Java的(T)obj
5.4.受包含的字段和方法
protected在scala中比Java要严格一点,即,只要继承关系才可以访问,同一个包下,也是不可以的
5.5.超类的构造
类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须以对先前定义的辅助构造器或主构造器的调用开始。子类的辅助构造器最终都回调用主构造器,只有主构造器可以调用超累的构造器。辅助构造器永远都不可以直接调用超累的构造器。在Sca‘la中的构造器中你不能调用super(params).
5.6.重名字段
子类改写或者抽象父类的字段,通过一些方式
注意提示了
1.def只能重写另一个def
2.val只能重写另一个val或不带参数的def
3.var 只能重写另一个抽象的var
什么是抽象var?
5.7.匿名子类
和java一样,你可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类:
使用
5.8.抽象类
可以 通过abstract关键字标记不能实例化的类。方法不用标记abstract,只要省的方法体即可。类可以拥有抽象字段,抽象字段就是没有初始值的字段。
注意 :子类 实现抽象方法不需要override
5.9.构造顺序和提前定义
当子类重写了父类的方法或者字段后,父类又依赖这些字段或者方法初始化,这个时候就会出现问题。比如:
此时的构造顺序为:
1)Ant的构造器在做它自己的构造之前,调用Creature的构造器
2)Creature的构造器将它的range字段设为10
3)Createure的构造器为了初始化env数组,调用range()取值器
4)该方法被重写以输出(还未初始化的)Ant类的range字段值
5)range方法返回0,(这是对象被分配空间时所有整型字段的初始值)
6)env被设为长度为0的数组。
7)Ant构造器继续指向,将其range字段设为2
那么env的大小? 0
解决方案3种
1)可以将val声明为final,这样子类不可改写。
2)可以将超类种将val声明为lazy,这样安全但并高效
3)还可以使用提前定义语法,可以在超类的构造器指向之前初始化子类的val字段:
5.10.Scala继承层级
在Scala种,所有其他类都是AnyRef的子类,类似Java的Object。
AnyVal和AnyRef都扩展Any类。Any类是根节点。
Any定义了isInstanceOf,asInstanceOf方法,以及哈希方法等。
Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量。
Nothing类型没有实例。它对于泛型结构是由用处的,举列:空列表的Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。
6.特质
6.1.不允许多重集成
所有的面向对象的语言都不允许直接的多重继承,因为会出现"deadly diamond of death"问题。Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现多个特质。
6.2.当结构使用特质
特质种没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质。
Logger with Cloneable with Sevializable是一个整体,extends这个整体。
所有的java接口都可以当Scala特质使用
6.3.带有具体实现的特质
特质种的方法并不一定是抽象的:
6.4.带有特质的对象,动态混入
在构建对象是混入某个具体的特质,覆盖调抽象方法,提供具体实现:
6.5.叠加在一起的特质
super并不是指继承关系,而是指加载顺序。
继承多个相同父特质的类,会从右到左一次调用特质的方法。Super指的继承特质左边的特质,从源码是无法判断super。method会指向哪里的方法,如果想要调用具体特质的方法,可以指定:super[ConsoleLogger].log(...)其中的泛型必须是必须是该特质的直接超类类型。
6.6.在特质种重写抽象方法
6.7.当做富接口使用的特质
即该特质种既有抽象方法,又又非抽象方法
6.8.特质中的具体字段
特质中 可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类具有了该字段,字段不是继承 ,而是简单的加入类。是自己的字段。
6.9.特质中的抽象字段
特质中未被初始的字段在具体的子类中必须被重写。
6.10.特质构造顺序
特质也是有构造器的,构造器中的内容由”字段的初始化“和一些其他语句构成
步骤总结:
1.调用当前类的超类构造去
2.第一个特质的父各种构造器
3.第一特质构造器
4.第二个特质构造器的父特质构造器由于指向完成,所以不再执行
5.第二个特质构造器
6.当前类构造器
6.11.初始化特质中的字段
特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造器参数是特质于类之间唯一的奇数差别。除此之外,特质可以具备类的所有特性,比如具体的和抽象的字段,以及超类。现在有如下情景:我们想通过特质来实现日志数据的输出,输出到某一个文件中
如果想修复如上错误,可以:
1)使用"提前定义"
或这样提前定义:
2)使用lazy
6.12 扩展类的特质
总结:
1.特质可以继承自类,以用来扩展该类的一些功能
2.所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3.如果混入该特质的类,已经继承了另外一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。
例如:
1)特质可以继承自类,以用来扩展该类的一些功能
2)所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3)如果混入该特质的类,已经继承了另一个类,不就矛盾?注意,只要继承的那个类是特质超类的子类即可。
6.13.自身类型
主要是为了解决特质的循环依赖问题,同时可以确保特质不扩展某个类的情况下,依赖可以做到限制混入该特质的类的类型。
这样一来,在该特质中,可以随意调用”自身类型“中各种方法。
7.注解
### 注解就是标签
标签是用来标记某些代码需要特殊处理的。
处理的手动可以在代码允许时操作,也可以在编译期操作。l
7.1.什么可以被注解
1)可以为类,方法,字段局部变量,参数,表达式,类型参数以及各种类型定义添加注解
2)构造器注解,需要在主构造器之前,类名之后,且需要加括号,如果注解有参数,则写在注解括号里。
3)为表达式添加注解,在表达式后添加冒号
4)泛型添加注解
5)实际添加注解
7.2.注解参数
Java注解可以有带名参数:
Java注解的参数类型只能是:
数值型的字面量
字符串
类字面量
Java枚举
其他注解
上述类型的数值(但不能数组的数组)
Scala注解可以是任何类型,但只有少数几个Scala注解利用了这个增加的灵活性。
7.3.注解实现
你可以实现自己的注解,但是更多的是使用Scala和java提供的注解
注意必须扩展Annotation特质:
7.4.针对Java的注解
1)Java修饰符:对于那些不是很常用的Java特性,Scala使用注解,而不是修饰符关键字。
2)标记接口:Scala注解@cloneable和@remote 而不是 Cloneable和Java.rmi.Remote“标记接口”来标记可被克隆的对象和远程的对象。
3)受检异常:和Scal不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。
即:Java编译器需要在编译时就指定read方法可以抛IOException异常,否则Java会聚集捕获该异常。
7.5.由于优化的注解
尾递归的优化:
def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story()}
注意:进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回
非尾递归:
def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story(),小和尚听了,找了块豆腐撞死了}
注意:下一个函数结束一行此函数还有后续,所以必须保存本身的环境以工处理返回值。递归调用有时候能被转化程循环,这样能
object Util { def sum(xs: Seq[Int]): BigInt = { if (xs.isEmpty) 0 else xs.head + sum(xs.tail) } ... }
上面的sum方法无法被优化,因为计算过程中最后一步是加法,而不是递归调用。调整后的代码: def sum2(xs: Seq[Int], partial: BigInt): BigInt = { if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial) } def sum2(xs: Seq[Int], partial: BigInt): BigInt = { if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial) } Scala编译器会自动对sum2应用“尾递归”优化。如果你调用sum(1 to 1000000) 将会发生一个栈溢出错误。不过sum2(1 to 1000000, 0) 将会得到正确的结果。 尽管Scala编译器会尝试使用尾递归优化,但有时候某些不太明显的原因会造成它无法这样做。如果你想编译器无法进行优化时报错,则应该给你的方法加上@tailrec注解。 尖叫提示:对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会将执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。Scala有一个名为TailCalls的工具对象,帮助我们轻松实现蹦床: import scala.util.control.TailCalls._ def evenLength(xs: Seq[Int]): TailRec[Boolean] = { if(xs.isEmpty) done(true) else tailcall(oddLength(xs.tail)) } def oddLength(xs: Seq[Int]): TailRec[Boolean] = { if(xs.isEmpty) done(false) else tailcall(evenLength(xs.tail)) } // 获得TailRec对象获取最终结果,可以用result方法 evenLength(1 to 1000000).result
和Scala不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。**
受检异常:和Scala**不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。
受检异常:和Scala不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws**注解来生成正确的签名。
原文地址:https://www.cnblogs.com/SteveDZC/p/9751640.html