工具篇-Scala基础

1. Scala开发为何须要Java环境?

Scala须要Java虚拟机把程序编译成字节码运行。html

2. Scala中全部类的超类及空返回值

Java中全部类的超类是Object,Scala中全部类的超类是Any;es6

Java中的空返回值为Void,Scala中的空返回值是Unit。另外Scala还有Null,null,Nil,Nothing,None,Unit几种空,具体讲解能够参见:http://www.javashuo.com/article/p-bzmvnpow-cg.html编程

3. Scala中的to和util

相同点:Scala中to和util都返回Range类型数组

不一样点:返回的数据范围不一样,例如:网络

1 val to1 = 1 to 10
2 val to2 = 1 to 10 by 2
3 val until1 = 1 until 10
4 val until2 = 1 until 10 by 2
5 println(to1) 6 println(to2) 7 println(until1) 8 println(until2)

输出:app

1 Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 2 Range(1, 3, 5, 7, 9) 3 Range(1, 2, 3, 4, 5, 6, 7, 8, 9) 4 Range(1, 3, 5, 7, 9)

4. Scala中的Val和Var声明的变量

Scala的类文件中不写set和get方法,val声明的变量为不可变变量,默认只有get方法,var声明的是可变变量,默认有set和get方法。编辑器

5. Scala中的方法和函数

Scala的方法跟Java中相似,它是类的一部分,好比Scala中的操做符(+,-等)都是方法,方法一般用def定义,定义格式以下:ide

1 def methodName ([参数1:参数类型1,参数2:参数类型2,...]) : [return type] = { 2  method body 3 return [expr] 4 }

其中,方法能够没有参数,返回值也能够是空Unit,相似于Java中的Void;函数

而函数是一个继承了Trait类的对象,函数必须有参数,函数一般用val定义,定义格式以下:工具

1 val functionName = (参数1:参数类型1,参数2:参数类型2,...) => functionBody

另外,方法能够转为函数:

1 f1 = m1 _ 

6. 匿名函数

什么是:一个函数只使用一次,并无屡次调用,即没有名字的话就是匿名函数。

应用场景:

  • 赋给变量;
  • 做为参数传给函数。

举例:

1 //匿名函数的使用场景1,做为参数,传入给高阶函数
2 Array(3,2,1,5).map{(x: Int) => x + 2} 3 
4 //匿名函数的使用场景2,直接赋值给变量
5 val kafkaName = (name: String) => println("--kafka-->" + name)

7. 高阶函数

使用函数值做为参数,或者返回值为函数值的“函数”和“方法”,均称之为“高阶函数”。例如Scala集合类(collections)的高阶函数map:

 1 (1 to 9).map("^" * _).foreach(println _)  2 /**打印三角形  3 ^  4 ^^  5 ^^^  6 ^^^^  7 ^^^^^  8 ^^^^^^  9 ^^^^^^^ 10 ^^^^^^^^ 11 ^^^^^^^^^ 12 */

返回值为函数值的例子能够参考:高阶函数

8. 柯里化

官方定义:Currying指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。这里的柯里化函数实际上也是一个高阶函数。

例如对于原始函数(不知道为啥叫函数)

1 def add(x:Int,y:Int)=x+y

有变形

1 def add(x:Int)(y:Int) = x + y

实际上给定一个参数 x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。

1 def add(x:Int)=(y:Int)=>x+y

这样咱们就能够定义多个函数,例如给定x=1

1 scala> val second=add(1) 2 second: Int => Int = <function1>
3 
4 scala> second(2) 5 res1: Int = 3

再好比给定x=2

1  scala> val third=add(2) 2  third: Int => Int = <function1>
3  
4  scala> third(2) 5  ress: Int = 4

9. Scala中的for循环和yield

for循环和yield使用的原理是:

  • 每一次for循环,yield 会产生一个值记录下来 (内部实现上,像是一个缓冲区),当循环结束后, 会返回全部yield值组成的集合;
  • 返回集合的类型与被遍历的集合类型是一致的。

例1:

1 scala> for (e <- a) yield e * 2
2 res6: Array[Int] = Array(2, 4, 6, 8, 10)

固然,也能够在for循环中加上'if'表达式,如例2:

1 scala> val a = Array(1, 2, 3, 4, 5) 2 a: Array[Int] = Array(1, 2, 3, 4, 5) 3 4 scala> for (e <- a if e > 2) yield e 5 res1: Array[Int] = Array(3, 4, 5)

10. Scala中偏函数

偏函数只接受输入参数的子集,例以下面collect函数的例子,Int=>41 / i这个匿名函数只针对非零输入41才有定义,而对0没有定义。

1 scala> List(41, 0) collect { case i: Int ⇒ 41 / i } 2 res1: List[Int] = List(1)

其实偏函数能够显式地定义为:

1 val fraction = new PartialFunction[Int, Int] { 2     def apply(d: Int) = 42 / d 3     def isDefinedAt(d: Int) = d != 0
4 }

isDefinedAt方法会自动过滤传入参数,大致是这么个意思,具体可参考:https://www.cnblogs.com/daoyou/articles/3894182.html

11. 单例对象

若是某个方法或域与某个类的特定对象无关,而是只与类相关,把它归于单例对象singleton objects,即由object关键字声明的类型:

1 object X { 2     // 至关于Java中静态代码块
3     val n = 2; 4     // 至关于Java中静态方法
5     def f = n * 10
6 }

由于它是单例对象,因此在使用的时候,不须要New,直接调X.f便可获得返回值20,对Java程序猿来讲,这就是static的做用,在Scala中,全部本来Java中用static修饰的属性和方法或者代码块都应该移到单例对象里, 因此scala建立一个单例对象(包含main方法)来为程序的执行提供入口点, 若是单例对象继承App,App内部已经有一个main方法,所以不用main也能够执行:

1 object ObjectDemo extends App { 2     println("hello") 3 }

单例对象能够扩展类和接口。单例对象有些时候是做为伴随对象companion object出现的,即它是另外一个同名类的伴随对象

12. 伴生对象

当一个单例对象与类具备相同的名称时,这个单例对象被称做是这个类的伴生对象,一样,这个类被称做是这个单例对象的伴生类
类和它的伴生对象能够互相访问私有特性,类和它的伴生对象必须定义在同一个源文件中,若是Scala中不用New直接调用,通常是调用了类的伴生对象,经典Demo以下:

 1 /**
 2  * Created by root  3  * Description : 伴生对象和伴生类  4  *  5  * 伴生类ObjectAndClassTest的构造函数定义为private,虽然这不是必须的,却能够有效防止外部实例化ObjectAndClassTest类,使得ObjectAndClassTest类只能供对应伴生对象使用;  6  * 每一个类均可以有伴生对象,伴生类与伴生对象写在同一个文件中;  7  * 在伴生类中,能够访问伴生对象的private字段ObjectAndClassTest.specialty  8  * 而在伴生对象中,也能够访问伴生类的private方法:objectAndClassTest.getSpecialtyName()  9  * 最后,在外部不用实例化,直接经过伴生对象访问方法:ObjectAndClassTest.printSpectalty() 10  * 11   */
12 class  ObjectAndClassTest private(val name:String) { 13 
14   private def getSpecialtyName() = "name = " + name + ";specialty = " + ObjectAndClassTest.specialty 15 } 16 
17 object ObjectAndClassTest{ 18     private val specialty = "software development"
19     private val objectAndClassTest = new ObjectAndClassTest("xiaoming") 20     def printSpectalty() = println(objectAndClassTest.getSpecialtyName()) 21 
22 } 23 
24 
25 object Test{ 26   def main(args: Array[String]): Unit = { 27  ObjectAndClassTest.printSpectalty() 28  } 29 }

另外,当一个单例对象没有与类具备相同的名称,这个单例对象被称做是独立对象
独立对象通常做为Scala应用的入口点,或相关功能方法的工具类。

13. Apply方法

一般,在一个类的伴生对象中定义Apply方法,在生成这个类的对象时,不用New关键字默认调用Apply方法,好比Map、Array等集合类。Apply方法能够理解为注入,在构造类的时候调用,主要用来解决复杂对象的初始化问题,Scala的Apply有两种方式,一种是伴生类的Apply,另外一种是伴生对象的Apply。示例参考Scala基础-对象:单例对象、伴生对象、apply方法,以下:

 1 class ApplyTest {  2   def apply() = println("这是伴生类的apply方法")  3 }  4 object ApplyTest {  5   def apply() = {  6     println("这是伴生对象的apply方法")  7      new ApplyTest  8  }  9 } 10 // 独立对象,做为程序入口
11 object ApplyInit{ 12   def main(args: Array[String]): Unit = { 13     // 经过伴生对象的apply方法,进行实例化
14     val applyTest=ApplyTest() 15     // 这是伴生类的apply方法
16  applyTest() 17  } 18 }

结果:

1 这是伴生对象的apply方法 2 这是伴生类的apply方法

14. Unapply方法

unapply方法的入参对应你想要进行匹配的对象,出参则是解构后的元素。好比,求一个数的开方根

1 object Square { 2   def unapply(z: Double): Option[Double] = Some(math.sqrt(z)) 3 }

咱们不用显式的调用,在match case中使用时编辑器会帮咱们调用

1 val number: Double = 36.0
2 number match { 3   case Square(n) => println(s"square root of $number is $n") 4   case _ => println("nothing matched") 5 }

上边的大体步骤是

一、调用unapply,传入number

二、接收返回值并判断返回值是None,仍是Some

三、若是是Some,则将其解开,并将其中的值赋值给n(就是case Square(n)中的n)

固然,Unapplyseq和Unapply差很少,也减小了咱们Java编程里必须写的代码,定义以下

1 object Names { 2   def unapplySeq(str: String): Option[Seq[String]] = { 3     if (str.contains(",")) Some(str.split(",")) 4     else None 5  } 6 }

15. Lazy关键字

特性:一个变量声明为lazy,则只有第一次调用时才会进行初始化,以后不再会被计算,所以lazy修饰的变量必须同时是val修饰的不可变变量;

使用场景:

  • 某些变量初始化比较耗时,则可使用lazy;如网络IO,磁盘IO等,marathon源码与spark源码中大量使用了这种特性;
  • 构造顺序问题,好比:
1 trait Person{ 2  val name: String 3   val nameLen = name.length 4 } 5 
6 class Student extends Person{ 7   override val name: String = "Tom"
8 }

在main函数中new一个Student,会报空指针异常,这是由于父类的constructor先与子类执行,那么在执行val nameLen = name.length的时候name尚未被赋值,因此才会致使空指针。解决办法就是在nameLen前加lazy,延迟初始化。

1 lazy val nameLen = name.length

原理:Scala也是编译成字节码跑在Jvm上的,而Jvm的字节码指令并无提供对lazy这种语义的支持,因此推断lazy只是一个语法糖,就是编译器在编译期将变量的初始化过程替换为Double Check Lock,相似Java中的懒汉式单例模式初始化。

16. case class

Scala中的case class和普通的class的区别是:

  • 初始化的时候能够不用new,普通类必定须要加new;
  • toString的实现更漂亮;
  • 默认实现了equals 和hashCode
  • 默认是可序列化,也就是实现了Serializable ;
  • case class构造函数的参数是public,咱们能够直接访问类的成员;
  • 支持模式匹配;这是定义case class的惟一理由。

17. match case(模式匹配)

Scala的match case和Java中switch case有区别,Scala中模式匹配能够匹配值、集合、类型以及有值和没值,另外每一个分支不须要break,自动退出。

  • 变量值的匹配,好比字符串匹配
1 变量 match{ 2       case "aa" => println("0") 3       case "bb" => println("1") 4       case "cc" => println("2") 5       case _ => println("null")  //表示全部的都没有匹配到执行这里
6 }
  • 增长if守卫,进行双重过滤
1 变量1 match{ 2        case "aa" => println("0") 3        case "bb" => println("1") 4        case "cc" => println("2") 5        case _ if 变量2=="dd" => println("3") 6        case _ => println("null")  //表示全部的都没有匹配到执行这里
7  }
  • 匹配类型
1 变量 match{ 2       case x:Int if(x>3) => println("Int") 3       case y:String  => println("String") 4       case z:Double => println("Double") 5       case flag:Boolean => println("Boolean") 6       case _ => println("null")  //表示全部的都没有匹配到执行这里
7 }

或者

1 try{ 2       println(3/0) 3 }catch { 4      case e:IOException  => println("IOException") 5      case e:Exception  => println("Exception") 6 }finally{ 7      println("程序结束!") 8 }
  • 变量赋值(对于_这种不知足前面分支的状况,能够对其进行赋值处理)
1 score match { 2         case "A"=>println("excellent") 3         case "B"=>println("good") 4         case "C"=>println("soso") 5         case _score =>println("you need work harder,your score only "+_score)  //变量赋值
6 }
  • 匹配数组和序列
 1 //匹配数组
 2 val arr3=Array(0,1,2,3,4,5)  3 arr3 match{  4       case Array(0,x,y) => println(x+"--"+y) //匹配以0开头,后面两个元素的数组
 5       case Array(0) => println("only 0")     //匹配只有一个元素的数组
 6       case Array(0,_*) => println("many") //匹配以0开头的数组,_表示任意元素,*表示一到多个元素
 7 }  8 
 9 //匹配序列
10 val lst1=List(3,1,-1) 11 lst1 match{ 12       case 0::Nil => println("only 0")    //匹配只有一个元素0的序列
13       case x::y::Nil =>println(x+"--"+y) //匹配有两个元素的序列
14       case 0::tail => println("0 is head ") //匹配以0开头的序列
15       case _ => println("nothing ") 16 }
  • case class
 1 变量 match {  2       case SubmitTask(id,name) => println(id,name)  //这里能将样例类中的参数提取出来,是是由于有unapply方法
 3       case HeartBeat(time) => println(time)  4       case CheckTimeOutTask => println("CheckTimeOutTask")  5 }  6 
 7 //多例样例类
 8 case class SubmitTask(id: String, name: String)  9 //多例样例类
10 case class HeartBeat(time: Long) 11 //单例样例类
12 case object CheckTimeOutTask
  • Option(有值是Some,没值是None)
1 grade match { 2         case Some(grade) => println("your grade is " + grade) 3         case None => println("Sorry, your grade information is not in the system") 4 }

18. 隐式转换

  • 何时调用隐式转换

调用一个方法时会首先从全局中寻找,当寻找不到或者方法的参数类型不匹配时才使用隐式转换,隐式转换只能定义在Object中。

直接上例子:1 to 10能够写成1.to(10),咱们知道1是Int类型的变量,因此按道理Int类中会有一个to的方法,但事实上咱们在Int类型中根本就没有找到to方法,为啥还能正常运行呢?

在Scala交互命令行中执行命令:implicits -v,能够查看系统为咱们自动引入的各类默认隐式转换,其中就有一个

1 implicit def intWrapper(x: Int): runtime.RichInt

这个隐式方法可以把Int类型的变量转换成runtime.RichInt变量,而RichInt 中确实存在 to 方法

能够看到隐式转换很神奇,但我的水平有限我仍是以为能少用就少用。

19. Scala中Array、ArrayBuffer

Scala中Array与Java中相似,也是长度不可改变的数组。此外,Scala数组的底层其实是Java数组。例如字符串、整型数组在底层就是Java的String[]、Int[]。

1 // 数组初始化后,长度就固定下来了
2 val a = new Array[Int](10) 3 a(0) = 1
4 
5 // 能够直接使用Array()建立数组,元素类型自动推断
6 val a = Array("leo", 30) 7 a(0) = "hi"

Array中的一些操做以下:

 1 // 数组元素求和
 2 val a = Array(1, 2, 3, 4, 5)  3 val sum = a.sum  4 // 获取数组最大值
 5 val max = a.max  6 // 对数组进行排序
 7 scala.util.Sorting.quickSort(a)  8 // 获取数组中全部元素内容
 9 a.mkString 10 a.mkString(", ") 11 a.mkString("<", ",", ">") 12 // toString函数
13 a.toString 14 b.toString

在Scala中,若是须要相似于Java中的ArrayList这种长度可变的集合类,则可使用ArrayBuffer

 1 // 若是不想每次都使用全限定名,则能够预先导入ArrayBuffer类
 2 import scala.collection.mutable.ArrayBuffer  3 // 使用ArrayBuffer()的方式能够建立一个空的ArrayBuffer
 4 val b = ArrayBuffer[Int]()  5 // 使用+=操做符,能够添加一个元素,或者多个元素  6 // 这个语法必需要谨记在心!由于spark源码里大量使用了这种集合操做语法!
 7 b += 1
 8 b += (2, 3, 4, 5)  9 // 使用++=操做符,能够添加其余集合中的全部元素
10 b ++= Array(6, 7, 8, 9, 10) 11 // 使用trimEnd()函数,能够从尾部截断指定个数的元素
12 b.trimEnd(5) 13 // 使用insert()函数能够在指定位置插入元素 14 // 可是这种操做效率很低,由于须要移动指定位置后的全部元素
15 b.insert(5, 6) 16 b.insert(6, 7, 8, 9, 10) 17 // 使用remove()函数能够移除指定位置的元素
18 b.remove(1) 19 b.remove(1, 3) 20 // Array与ArrayBuffer能够互相进行转换
21 b.toArray 22 a.toBuffer

Array和ArrayBuffer的遍历

 1 // 使用for循环和until遍历Array / ArrayBuffer  2 // 使until是RichInt提供的函数
 3 for (i <- 0 until b.length)  4  println(b(i))  5 // 跳跃遍历Array / ArrayBuffer
 6 for(i <- 0 until (b.length, 2))  7  println(b(i))  8 // 从尾部遍历Array / ArrayBuffer
 9 for(i <- (0 until b.length).reverse) 10  println(b(i)) 11 // 使用“加强for循环”遍历Array / ArrayBuffer
12 for (e <- b) 13   println(e)

这一部分具体能够参考:https://www.cnblogs.com/HeQiangJava/p/6706951.html  

20. 字符串插值

Scala为咱们提供了三种字符串插值的方式,分别是 s, f 和  raw。它们都是定义在 StringContext 中的方法。

  • s字符串插值器

能够解析字符串中的变量,能够调用方法,还能进行计算。实际调用的是StringContext中的s方法。

 1 val name = "Unmi"
 2 println(s"Hello $name")   //Hello Unmi
 3 println(s"Hello ${name}qq) //Hello Unmiqq 界定变量用大括号{},s"Hello $nameqq" 会试图解析变量nameqq
 4 println(s"1 + 1 = ${1 + 1} //1 + 1 = 2,能计算值
 5  
 6 class Person(val name: String){  7     def say(what: String) = s"say $what"
 8 }  9 val person = new Person("Unmi") 10 println(s"Hello ${person.name}, ${person.say(person.name)}")   //Hello Unmi, say Unmi, 这个比较复杂

用大括号能够界定变量,{}里能够求值,还可使用属性和调用方法。

  • f字符串插值器

它除s的功能外(不指定格式就和s同样),还能进行格式化输出,在变量后用%指定输出格式。实际调用的是StringContext中的f方法。

1 val height = 1.9d
2 val name = "James"
3 println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall
  • raw 字符串插值

raw 能让字符串原本来本的输出来,而不是产生控制效果,如对\n,\t 等的输出。实际调用的是StringContext中的raw方法。

1 scala> println("a\nb\tc") 2 a 3 b c 4  
5 scala> println(raw"a\nb\tc") 6 a\nb\tc

 

Apply

相关文章
相关标签/搜索