Scala速学

一、Scala简介

Scala是一门现代的多范式编程语言,平滑地集成了面向对象和函数式语言的特性,旨在以简练、优雅的方式来表达常用编程模式。Scala的设计吸收借鉴了许多种编程语言的思想,只有很少量特点是Scala自己独有的。Scala语言的名称来自于“可伸展的语言”,从写个小脚本到建立个大系统的编程任务均可胜任。Scala运行于Java平台(JVM,Java 虚拟机)上,并兼容现有的Java程序,Scala代码可以调用Java方法,访问Java字段,继承Java类和实现Java接口。在面向对象方面,Scala是一门非常纯粹的面向对象编程语言,也就是说,在Scala中,每个值都是对象,每个操作都是方法调用。

Spark的设计目的之一就是使程序编写更快更容易,这也是Spark选择Scala的原因所在。总体而言,Scala具有以下突出的优点:  Scala具备强大的并发性,支持函数式编程,可以更好地支持分布式系统;  Scala语法简洁,能提供优雅的API;  Scala兼容Java,运行速度快,且能融合到Hadoop生态圈中。

二、IntelliJ IDEA 安装scala插件

首先在 IDEA 中安装scala插件

然后下载sdk(选择需要的版本)

下载完后就可以新建scala项目了,选择jdk和sdk

新建一个Object写一个hello world

三、scala基础

1.申明值和变量

Scala有两种类型的变量,一种是val,是不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值;另一种是var,是可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值。 如下:

2.基本数据类型和操作

Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean。和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串

这里要明确什么是“字面量”?字面量包括整数字面量、浮点数字面量、布尔型字面量、字符字面量、字符串字面量、符号字面量、函数字面量和元组字面量。举例如下:

Scala允许对“字面量”直接执行方法,比如:

在Scala中,可以使用加(+)、减(-) 、乘(*) 、除(/) 、余数(%)等操作符,而且,这些操作符就是方法。例如,5 + 3和(5).+(3)是等价的.

需要注意的是,和Java不同,在Scala中并没有提供++和–-操作符,当需要递增和递减时,可以采用如下+= 和-=

3.Range

在执行for循环时,我们经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现。Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等。 写法如下:

4.打印语句

类似c语言,有print , println ,printlnf
写法如下:

5.控制结构

if语句是许多编程语言中都会用到的控制结构。在Scala中,执行if语句和java类似。 写法如下:

	if(true){
		println(true)
	}else{
		println(false)
	}

Scala中也有和Java类似的while循环语句和for循环语句。 写法如下:

	var n = -1
	while(n<=0){
		println(n)
		n+=1
	}
	for(i <- 1 to 0 by -1){
		println(i)
	}

6.数据结构

6.1 数组

数组是编程中经常用到的数据结构,一般包括定长数组和变长数组。本教程旨在快速掌握最基础和常用的知识,因此,只介绍定长数组。 定常数组写法如下: val intValueArr = new ArrayInt //声明一个长度为3的整型数组,每个数组元素初始化为0 intValueArr(0) = 12 //给第1个数组元素赋值为12 intValueArr(1) = 45 //给第2个数组元素赋值为45 intValueArr(2) = 33 //给第3个数组元素赋值为33

scala中也有和java类似的List,当然你也可以使用java的List,使用时需要主要类型 写法如下: val list1 = List(1,2,3) //::可以连接元素与List,:::可以连接List与List

	val list2 = 0::list1:::list1//结果为ArrayBuffer(0, 1, 2, 3, 1, 2, 3)

	println(list2.toBuffer)
	val javaList = new util.ArrayList[String]()
	javaList.add("1")
	javaList.add("2")
6.2 元组

元组是不同类型的值的聚集。元组和列表不同,列表中各个元素必须是相同类型,而元组可以包含不同类型的元素。 写法如下:

	val tuple = ("BigData",2015,45.0)
6.3 Set

scala中有类似java中的HashSet的set,写法如下:

	var set = Set(1,2,3,3,4)
	set += 1
	set += 5
	println(set.toBuffer)//结果为ArrayBuffer(5, 1, 2, 3, 4)
6.4 Map

scala中也有类似java的Map,写法如下:

	var map = Map("1" ->3,"2" -> 2,"3" -> 1)
	map += ("4" -> 0)
	println(map.toBuffer)//结果为:ArrayBuffer((1,3), (2,2), (3,1), (4,0))
	println(map("4"))//结果为:0
6.5 类

scala中也有类似java中的class 的class 写法如下:

	class People{
		private val secret: String = "xxx"
		var name: String = "Jone"
		val age: Int = 30
	
		def run(): Unit ={
			println(s"${name} is running!")
		}
	
		def getSecret(): String ={
			secret
		}
	}

使用时类似java:

	val pepple = new People()
	pepple.name = "Jack"
	pepple.run()//结果为:Jack is running!
	println(pepple.getSecret())//结果为xxx
6.6 特质

Java中提供了接口,允许一个类实现任意数量的接口。在Scala中没有接口的概念,而是提供了“特质(trait)”,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质,是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,却可以实现多个特质,从而重用特质中的方法和字段,实现了多重继承。

特质的定义和类的定义非常相似,有区别的是,特质定义使用关键字trait。 写法如下:

	trait CarId{
	var id: Int
		def currentId(): Int     //定义了一个抽象方法
	}

上面定义了一个特质,里面包含一个抽象字段id和抽象方法currentId。注意,抽象方法不需要使用abstract关键字,特质中没有方法体的方法,默认就是抽象方法。

特质定义好以后,就可以使用extends或with关键字把特质混入类中。 写法如下:

	class BYDCarId extends CarId{ //使用extends关键字
	override var id = 10000 //BYD汽车编号从10000开始
		def currentId(): Int = {id += 1; id} //返回汽车编号
	}
	class BMWCarId extends CarId{ //使用extends关键字
	override var id = 20000 //BMW汽车编号从20000开始
		def currentId(): Int = {id += 1; id} //返回汽车编号
	}

###### 6.7 Object Scala并没有提供Java那样的静态方法或静态字段,但是,可以采用object关键字实现单例对象,具备和Java静态方法同样的功能。 下面是单例对象的定义:

	object Person {
		private var lastId = 0  //一个人的身份编号
		def newPersonId() = {
			lastId +=1
			lastId
		}
	}

从上面的定义可以看出,单例对象的定义和类的定义很相似,明显的区分是,用object关键字,而不是用class关键字。 假设有一个班级人员管理系统,每当新来一个班级成员,就给分配一个身份编号。当第一个人加入班级时,你就可以调用Person.newPersonId()获得身份编号。

在Java中,我们经常需要用到同时包含实例方法和静态方法的类,在Scala中可以通过伴生对象来实现。当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”。类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。 示例如下:

	class Person {
		private val id = Person.newPersonId() //调用了伴生对象中的方法
		private var name = ""
		def this(name: String) {
			this()
			this.name = name
		}
		def info() { printf("The id of %s is %d.\n",name,id)}
	}
	object Person {
		private var lastId = 0  //一个人的身份编号
		private def newPersonId() = {
			lastId +=1
			lastId
		}
		def main(args: Array[String]){
			val person1 = new Person("Ziyu")
			val person2 = new Person("Minxing")
			person1.info()
			person2.info()      
		}
	}

从上面结果可以看出,伴生对象中定义的newPersonId()实际上就实现了Java中静态(static)方法的功能,所以,实例化对象person1调用newPersonId()返回的值是1,实例化对象person2调用newPersonId()返回的值是2。我们说过,Scala源代码编译后都会变成JVM字节码,实际上,在编译上面的源代码文件以后,在Scala里面的class和object在Java层面都会被合二为一,class里面的成员成了实例成员,object成员成了static成员。

7.模式匹配

Java中有switch-case语句,但是,只能按顺序匹配简单的数据类型和表达式。相对而言,Scala中的模式匹配的功能则要强大得多,可以应用到switch语句、类型检查、“解构”等多种场合。 Scala的模式匹配最常用于match语句中。下面是一个简单的整型值的匹配实例。

	val colorNum = 1
	val colorStr = colorNum match {
		case 1 => "red"
		case 2 => "green"
		case 3 => "yellow"
		case _ => "Not Allowed" 
	} 
println(colorStr)

Scala可以对表达式的类型进行匹配。写法如下:

	for (elem <- List(9,12.3,"Spark","Hadoop",'Hello)){
		val str  = elem match{
			case i: Int => i + " is an int value."
			case d: Double => d + " is a double value."
			case "Spark"=> "Spark is found."
			case s: String => s + " is a string value."
			case _ => "This is an unexpected value."
		}
	println(str)    
	}

8.匿名函数、Lambda表达式

传统类型的函数,定义的语法和我们之前介绍过的定义“类中的方法”类似(实际上,定义函数最常用的方法是作为某个对象的成员,这种函数被称为方法),写法如下:

	def counter(value: Int): Int = { value += 1}

函数的也可以作为函数的入参,示例:

	def sum(f: Int => Int, a: Int, b: Int): Int ={ 
	if(a > b) 0 else f(a) + sum(f, a+1, b)
	} 在调用以上函数时需要传入一个函数,此时便可以传入一个没有定义的匿名函数,示例如下:

	var sumValue = sum(a => a+1 ,2, 3)

入参: a => a+1 便是一个匿名函数,这种结构的匿名函数我们经常称为“Lambda表达式”。“Lambda表达式”的形式如下: (参数) => {表达式} //如果参数只有一个,参数的圆括号可以省略,如果表达式只有一行,花括号可以省略

为了让函数字面量更加简洁,我们可以使用下划线作为一个或多个参数的占位符,只要每个参数在函数字面量内仅出现一次。 例如在调用刚才的sum()方法是可以这么写:

	var sumValue = sum(_+1 ,2, 3)

9.集合操作

9.1遍历

List,Set,Tuple 等 数据结构最常用的操作就是遍历操作了 List,Set等的遍历都可以用foreach方法遍历,写法如下:

	val list = List(1, 2, 3, 4, 5)
	list.foreach(elem => println(elem)) //本行语句甚至可以简写为list.foreach(println),或者写成:list foreach println

Map的遍历方法,写法如下:

	val mapx = Map (1 ->"a",2 -> "b" ,3 -> "c")
	mapx foreach {case(k,v) => println(k+":"+v)}
	mapx.foreach( kv => println("key:"+kv._1+",value:"+kv._2) )
	mapx foreach {kv => println("key:"+kv._1+",value:"+kv._2)}
9.2map操作

map操作是针对集合的典型变换操作,它将某个函数应用到集合中的每个元素,并产生一个结果集合。 下面有个示例:

	val list = List(1, -2, 3, -4, 5)
	val list_positive = list.map( v => {
		var res = v
		if(v<0){
			res= -v
		}
		res
	})
	println(list_positive.toBuffer)//结果为:ArrayBuffer(1, 2, 3, 4, 5)

flatMap是map的一种扩展。在flatMap中,我们会传入一个函数,该函数对每个输入都会返回一个集合(而不是一个元素),然后,flatMap把生成的多个集合“拍扁”成为一个集合。 示例如下:

	val l = List("1,2,3,4","5","6,7,8","9")
	val res = l.flatMap(v => v match {
		case str:String => str.split(",")
	})

	println(res.toBuffer)//结果为ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9) 以上代码会把l的每个元素都进行split然后把结果连成一个list
9.3filter操作

在实际编程中,我们经常会用到一种操作,遍历一个集合并从中获取满足指定条件的元素组成一个新的集合。Scala中可以通过filter操作来实现。 用法如下:

	val list = List(1, -2, 3, -4, 5)
	val result = list.filter(v => v>0).sum
	println(result)//结果为9

以上代码就是把list中所有的大于0 的数进行sum

9.4reduce

在Scala中,我们可以使用reduce这种二元操作对集合中的元素进行归约。 reduce包含reduceLeft和reduceRight两种操作,前者从集合的头部开始操作,后者从集合的尾部开始操作。 写法如下:

	val list = List(1, -2, 3, -4, 5)
	val result_left = list.reduceLeft(_-_)
	val result_right = list.reduceRight(_-_)
	println(result_left)//结果为-1
	println(result_right)//结果为15

以上代码中,reduceLeft是从list的左侧开始往右进行相减,而reduceRight则是从最右侧开始往左进行相减。