1 Scala是一门怎样的语言,具有哪些优点
- 一致性 : 静态类型系统+面向对象+函数式编程
- 面向对象 : 所有的变量和方法都封装在对象中
- 函数式编程 :函数可以独立存在,可以定义一个函数作为 另外一个函数的返回值,也可以接受函数作为函数的参数
- 异步编程 : 函数式编程提倡变量不可变,使得异步编程变得十分容易
- 基于JVM : Scala会被编译成为Bytecode,所以Scala能无缝集成已有的Java类库
2 Scala语法基础
从Hello World说起
java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
scala
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, World!")
}
}注意
语句末尾的分号通常是可选的。分号是表达式分隔符,它们是推断的。Scala将行的结尾视为表达式的结尾,除非它可以推断表达式继续到下一行。
Scala程序处理从主方法开始,这是每个Scala程序的一个强制性部分。
主要方法未标记为静态。
主要方法是对自动实例化的单例对象的实例方法。
没有返回类型。实际上有Unit,这是类似于void,但它是由编译器推断。
我们可以通过在参数后面加一个冒号和类型来显式地指定返回类型:
def main(args: Array[String]) : Unit = {
}
Scala使用def关键字告诉编译器这是一个方法。在Scala中没有访问级别修改器。
Scala未指定公用修饰符,因为默认访问级别为public。
2.1 Scala-值和变量声明
- val声明的变量不可变,相当于java中的final
val a = 1
a = 2 // 出错啦
- var声明的变量可变
var a = 1
a = 2 // OK
- 在scala的类中,val会自动带有getter方法,var会自动带有getter和setter方法
2.2 Scala常用类型
Scala没有区分基本类型和包装类型,统一定义为class类。
1.toString() // 生成字符串1
7种数值类型+1种Boolean类型
- Byte -> RichByte
- Char -> RichChar
- Short -> RichShort
- Int -> RichInt
- Long -> RichLong
- Float -> RichFloat
- Double -> RichDouble
在基本数据类型上使用那些没有提供的方法时,scala会尝试“隐式转换”转换 成增强类型
Example
1.to(10) // 生成出Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
2.3 方法的定义和使用
- 方法定义
格式
def 方法名(参数名: 参数类型): 返回值类型 = { return xxx // return可省略
}
当返回值为unit时
def 方法名(参数名: 参数类型) {
// 方法体
}
无参数函数定义
def 方法名 {
// 方法体
- Example
def m1(a: Int, b: Int): Int = {
a + b
}
def m2() = 100
def m3 = 100
2.4 函数的定义和使用
- 函数定义
函数在scala中是一等公民
val 函数名:(参数类型1, … , 参数类型n)=>返回值类型 = (T1,…, Tn) => 函数体
val f0 : (Int) => Boolean = i => { i % 2 == 0 } val f1 : Int => Boolean = i => { i % 2 == 0 } val f2 : Int => Boolean = i => i % 2 == 0
val f3 : Int => Boolean = _ % 2 == 0
val 函数名 = (参数名1: 参数类型1, … , 参数名n: 参数类型n) =>函数体
// implicit approach
val add0 = (x: Int, y: Int) => { x + y }
val add1 = (x: Int, y: Int) => x + y
// explicit approach
val add2 : (Int, Int) => Int = (x,y) => { x + y }
val add3 : (Int, Int) => Int = (x,y) => x + y
- 函数必须有参数列表,否则报错
val f1 = => 100 // 错误
val f2 = () => 100 // 正确
2.5 函数与方法区别
- 方法不可以赋值给变量但是函数可以
def m1(a: Int, b: Int): Int = { a + b
}
val m = m1 // 错误:方法不可以赋值给一个变量
val m = m1 _ // 正确:方法自动转化为函数
m: (Int, Int) => Int = <function2>
- 对于一个无参数的方法是没有参数列表的,而对于函数是有一个空参数列表。
def x = println("Hi scala")
x: Unit // 没有参数列表
val y = x _
y: () => Unit = <function0> // 空的参数列表()
- 函数名后必须加括号才代表函数调用,否则为该函数本身,而方法名后不加括号为方法调用
y // 仅代表函数本身
res0: () => Unit = <function0> y() // 函数调用
Example - 方法计时器
object TimeTest {
def time[R](block: => R): R = { // 这里使用了call-by-name参数
val t0 = System.currentTimeMillis()
val result = block
val t1 = System.currentTimeMillis() println("Elapsed time = " + (t1 - t0) + "ms") result
}
def method(x: Int): Unit = {
println("print test = " + x)
}
def main(args: Array[String]): Unit = {
time(method(100))
}
}
2.6 循环和高级for循环
- 拥有与Java相同的while和do循环
while (x > 0) {
// do while
}
- for循环与Java不同
for (x <- 表达式) {
// do for
}
for (x <- 1 to n) {
// do for
}
for (x <- "Hello, World! ") {
// do for
}
val s = "Hello, World! “ for (x <- 0 until s.length) {
}
- for循环中不支持continue和break
- 支持守卫
for(i <- 1 until 10 reverse; if i % 2 == 0) { println(i)
}
- 支持多个生成器
for(i <- 1 to 3; j <- 1 to 3) { print((10 * i + j) + " ")
}
- 支持守卫yield关键词
val seq = for (i <- 1 to 10 if i % 2 == 0) yield i print(seq)
2.7 常见集合使用
Scala有三大类集合
- 序列Seq
val v1 = Vector(1, 2, 3, 4)
val list1 = List(1, 2, 3, 4)
val que1 = Queue(1, 2, 3)
- 集 Set
Set(1, 1, 2)
- 映射 Map
Map(1 -> 2)
Map("foo" -> "bar")
Map(1 -> "one", 2 -> "two")
几乎对所有集合都提供了可变和不可变的版本
Scala.collection.immutable.Map // 不可变集合
scala.collection.mutable.Map // 可变集合
可以对集合进行丰富的操作
map, flatMap, reduce, distinct, zip
2.8 常见集合操作
常见集合操作
map
在列表中的每个元素上计算一个函数,并且返回一个包含相同数目元素的列表。
val nums = List(1,2,3)
val squareNums1 = nums.map(num => num * num)
flatMap
结合了map和flatten的功能。接收一个可以处理嵌套列表的函数,把返回结果连接起来。
val nestedNumbers = List(List(1, 2), List(3, 4))
nestedNumbers.flatMap(x => x.map(Math.pow(_, 2)))
filter
移除任何使得传入的函数返回false的元素。
val nums = List(1,2,3,4)
nums.filter((i: Int) => i % 2 == 0)
zip
两个列表的元素合成一个由元组对组成的列表里
List(1, 2, 3).zip(List("a", "b", "c"))
partition
断言函数的返回值对列表进行拆分
List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).partition(_ % 2 == 0)
2.9 类的定义
- scala类中方法默认都是public
- 对var字段会生成getter和setter方法
- 对val字段只会生成getter方法
- 主构造器中的参数必须要被初始化
- scala
class Person(name: String, age: Int) {
var work: String = _
private val defaultSalary: Int = 10000
def getPersonInfo(): String = {
"Name = [" + name + "], Age = [" +
age + "] "
}
}
- java
public class Person { private final String name; private final int age; private String work;
private final int defaultSalary = 10000;
public Person(String name, int age){
super();
this.name = name;
this.age = age;
}
public String getPersonInfo() {
return (new StringBuilder())
.append("Name = [")
.append(name).append("], Age = [")
.append(BoxesRunTime.boxToInteger(age))
.append("]").toString();
}
public String work() {
return work;
}
public void work_$eq(String x$1) { work = x$1;
}
private int defaultSalary() {
return defaultSalary;
}
}
- 类构造函数
辅助构造器使用def this定义
每一个辅助构造器第一行必须调用其它辅 助构造器或者一个主构造器
println语句是主构造器的一部分class Person(name: String, age: Int) {
println("Name = [" + name + "], Age = [" + age + "] ") var work: String = _
def this(name: String, age: Int, work: String) { this(name, age)
this.work = work
}
def getPersonInfo(): String = {
"Name = [" + name + "], Age = [" + age + "] "
}
}
主构造器参数
生成的字段/方法
name: String
对象私有字段
val/var name: String
私有字段,公有的getter/setter方法
private val/var name: String
私有字段,私有的getter/setter方法
@BeanProperty val/var name: String
私有字段,公有Scala版和JavaBeans版的
getter和setter方法
- 类继承
- Scala继承类和java一样使用extends关键字
- 可以将类、字段或者方法声明为final,确保它们不能被重写
- 重写一个非抽象方法必须使用override关键词
- 可以将类定义为abstract作为抽象类,子类中重写超类的抽象方法时不 需要使用override关键词
- 调用超类与Java一致使用super关键词
- 只有主构造器才能调用超类的构造器
class Student(name: String, age: Int) extends Person(name, age) {
var score : Int = _
override def getPersonInfo: String = {
s"Name = [${name}], Age = [${age}], Score = [${score}]“
}
}
- 单例对象
class Account {
val id = Account.newUniqueId()
private var balance = 0.0
protected def deposit(amount: Double): Unit = {
balance += amount
}
}
object Account {
private var lastId = 0
private def newUniqueId() = {
lastId += 1
lastId
}
}
Scala中没有静态方法和静态字段,但是可以使用object达到同样的效果
子类可见,但是对包不可见,需要对包可见:申明protected[包名]
Account是伴生对象,所有的字段和方法都是静态的
3 异常处理
- java
try {
methodThatThrowsIOException();
} catch (IOException e) {
// do IOException
} catch (Throwable e) {
// do Throwable
} finally {
// do finally
}
scala
try {
methodThatMayThrowAnException()
} catch {
case e: IOException => // do IOException
case _: Throwable => // do Throwable
} finally {
// do finally
}
•如果在.map, .flatMap中遇到异常如何处理?
•Scala提供了scala.util.Try 类型更加优雅的处理异常
•如果成功返回Success
•如果抛出异常返回Failure并携带异常信息
import scala.util.{Try, Success, Failure}
def divideBy(x: Int, y: Int): Try[Int] = {
Try(x / y)
}
divideBy(1, 0) match {
case Success(i) => println(s"Success, value is: $i") case Failure(s) => println(s"Failed, message is: $s")
}
// Failed, message is: java.lang.ArithmeticException: / by zero
println(divideBy(1, 1).getOrElse(0)) // 1 println(divideBy(1, 0).getOrElse(0)) // 0
50.to(55).zip(0.to(5)).map(pair => divideBy(pair._1, pair._2).getOrElse(0))
// scala.collection.immutable.IndexedSeq[Int] = Vector(0, 51, 26, 17, 13, 11)
4,函数式编程思想
函数式编程关心的是数据的映射而命令式编程关心的是解决问题的步骤
函数式编程提倡:没有可变的变量,例如无论sqrt(x),这个函数的值只取决于函数的输入的值 ;没有类似于命令式编程中循环元素
4.1 函数式编程特点
- 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。
- 最主要的特征是,函数是第一等公民。
- 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法。
- 只有纯的、没有副作用的函数,才是合格的函数。
- 好处
不依赖于外部的状态,也不修改外部的状态,使得代码容易推理, 单元测试和调试变得十分容易
由于多个线程之前不共享状态,因此不会造成资源的竞争,可以更好的支持并发
- Example:命令式编程与函数式编程代码风格比较
命令式编程
class Node(
var value: Int,var left: Option[Node], var right: Option[Node])
object Node {
def invertTree(root: Option[Node]): Option[Node] = {
if (root isDefined) {
val left = root.get.left
val right = root.get.right root.get.left = invertTree(right) root.get.right = invertTree(left)
}
root
}
def main(args: Array[String]): Unit = {
val node4 = new Node(4, None, None)
val node5 = new Node(5, None, None)
val node6 = new Node(6, None, None)
val node7 = new Node(7, None, None)
val node2 = new Node(2, Some(node4), Some(node5))
val node3 = new Node(3, Some(node6), Some(node7))
val node1 = new Node(1, Some(node2), Some(node3)) invertTree(Some(node1))
println(node1)
}
}
函数式编程
class Node(val value: Int,
val left: Option[Node], val right: Option[Node])
object Node {
def invertTree(root: Option[Node]): Option[Node] = {
if (root isDefined) {
Some(new Node(root.get.value,
invertTree(root.get.right), invertTree(root.get.left)))
} else {
None
}
}
def main(args: Array[String]): Unit = {
val node4 = new Node(4, None, None)
val node5 = new Node(5, None, None)
val node6 = new Node(6, None, None)
val node7 = new Node(7, None, None)
val node2 = new Node(2, Some(node4), Some(node5))
val node3 = new Node(3, Some(node6), Some(node7))
val node1 = new Node(1, Some(node2), Some(node3))
val newTree = invertTree(Some(node1)).get
}
}
函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。
- 我们可以把"范畴"想象成是一个容器,里面包含两样东西。
值(value)
值的变形关系,也就是函数。
- 本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。
为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。
- 总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。
4.2 函数式编程有两个最基本的运算:合成和柯里化
函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。
任何具有map方法的数据结构,都可以当作函子的实现。一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
4.3函数式编程,实际上就是学习函子的各种运算。
1) of函子:
函数式编程一般约定,函子有一个of方法,用来生成新的容器。
下面就用of
方法替换掉new
。
Functor.of = function(val) { return new Functor(val); };
然后,前面的例子就可以改成下面这样。
Functor.of(2).map(function (two) { return two + 2; }); // Functor(4)
2)Maybe 函子:
函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。
Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。
Maybe.of(null).map(function (s) {
return s.toUpperCase();
});
// Maybe(null)
3)Either 函子:
条件运算if...else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。
Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。
var addOne = function (x) {
return x + 1;
};
Either.of(5, 6).map(addOne);
// Either(5, 7);
Either.of(1, null).map(addOne);
// Either(2, null);
4)ap 函子:
ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。
function add(x) {
return function (y) {
return x + y;
};
}
Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Ap(5)
上面代码中,函数add是柯里化以后的形式,一共需要两个参数。通过 ap 函子,我们就可以实现从两个容器之中取值。它还有另外一种写法。
Ap.of(add(2)).ap(Maybe.of(3));
5)Monad 函子:
Maybe.of(
Maybe.of(
Maybe.of({name: 'Mulburry', number: 8402})
)
)
上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。
函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。
Monad 函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。
class Monad extends Functor { join() { return this.val; } flatMap(f) { return this.map(f).join(); } }
上面代码中,如果函数f
返回的是一个函子,那么this.map(f)
就会生成一个嵌套的函子。所以,join
方法保证了flatMap
方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。
6) IO 操作
Monad 函子的重要应用,就是实现 I/O (输入输出)操作。
I/O 是不纯的操作,普通的函数式编程没法做,这时就需要把 IO 操作写成Monad
函子,通过它来完成。
var fs = require('fs'); var readFile = function(filename) { return new IO(function() { return fs.readFileSync(filename, 'utf-8'); }); }; var print = function(x) { return new IO(function() { console.log(x); return x; }); }
上面代码中,读取文件和打印本身都是不纯的操作,但是readFile
和print
却是纯函数,因为它们总是返回 IO 函子。
如果 IO 函子是一个Monad
,具有flatMap
方法,那么我们就可以像下面这样调用这两个函数。
readFile('./user.txt') .flatMap(print)
这就是神奇的地方,上面的代码完成了不纯的操作,但是因为flatMap
返回的还是一个 IO 函子,所以这个表达式是纯的。我们通过一个纯的表达式,完成带有副作用的操作,这就是 Monad 的作用。
由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,flatMap
方法被改名成chain
。
var tail = function(x) { return new IO(function() { return x[x.length - 1]; }); } readFile('./user.txt') .flatMap(tail) .flatMap(print) // 等同于 readFile('./user.txt') .chain(tail) .chain(print)
上面代码读取了文件user.txt
,然后选取最后一行输出。
scala附录演练
import org.apache.spark.SparkConf; import org.apache.spark.SparkContext; //定义一个类Person(属性,方法) class Person(val name: String, var age: Int) object base { //定义一个无返回值的方法 def func(s:String):Unit={ println(s) } def meth() = "Hello World" //单行方法代码块 def meth1():String = {"hi"} //返回值为String的方法代码块 def meth2():String = { //具体的函数体实现(代码块) val d = new java.util.Date() d.toString() } //删除前缀和后缀 def goodbye(name: String) = s"""xxxGoodbye, ${name}yyy xxxCome again!yyy""" .stripPrefix("xxx").stripSuffix("yyy") //主方法:Unit相当于void,无返回值类型 def main(args: Array[String]): Unit = { //System.setProperty("hadoop.home.dir", "D:\\hadoop");//设置hadoop环境 //val conf = new SparkConf().setAppName("Base").setMaster("spark://192.168.66.66:7077");//加载spark远程作业调度 //val spark=new SparkContext(conf);//声明一个sparkContext上下文 val data = Array(1, 2, 3, 4, 5); //定义一个不可变数组 func("hello");//调用方法 println(data(1))//输出单个数组元素 //遍历输出data数组 for (i<-data) { println(i) } //spark.stop(); for(i<- 1 to 10){ println(i) } for { i <- 1 to 10 j <- 1 to 10 } println(i* j) val p = new Person("Dean Wampler", 29) //声明一个类对象 println(p.name) //调用并输出类属性 println(p.age ) p.age = 30 println(p.age ) println(meth+"\n"+meth1()+"\n"+meth2())//函数调用无参数不需要带() /*定义一个不可变且返回值为String的代码块*/ val x3:String= { val d = new java.util.Date() d.toString() } println(x3) val x = !false //布尔类型 var xf=true //布尔型常量 val y='1' //单引号的字符常量 val z="scala" //双引号的字符串常量 val zx:String="spark" println(xf+""+x+"-"+y+s"-${z}"+"-"+zx) //s"-${z}"字符串插值 val xx: Byte = 30 //数字类型Byte . Short . Int . Long . Float . Double. val yy: Short = xx val zz: Double = yy println(xx+"-"+yy+"-"+zz) println(goodbye("www.w3cschool.cn")); //函数常量定义 val f1: (Int,String) => String = (i, s) => s+i //返回值为String val f2: Function2[Int,String,String] = (i, s) => s+i //前两个为参数,后一个为返回值 println(f1(1,"name")+"-"+f2(1,"siat")) //元组常量 //val t1: (Int,String) = (1, "two") //val t2: Tuple2[Int,String] = (1, "two") val t = ("Hello", 1, 2.3) //定义一个元组 println( "Print the whole tuple: " + t ) //输出整个元组 println( "Print the first item: " + t._1 ) //输出第一个元组元素 println( "Print the second item: " + t._2 ) //输出第二个元组元素 println( "Print the third item: " + t._3 ) //输出第三个元组元素 //定义三个元组,每个元组存储一个元素 val (t1, t2, t3) = ("World", "!", 0x22) println( t1 + ", " + t2 + ", " + t3 ) val (t4, t5, t6) = Tuple3("World", "!", 0x22) println( t4 + ", " + t5 + ", " + t6 ) //Scala没有null关键字 //范围Range println(1 to 5 ) //包括上限1,2,3,4,5 println(1 until 5 ) //不包括上限1,2,3,4 println(1 to 20 by 4 ) //从1到20每次进四个值包括下限1 val v2 = 10 to 1 by -3 //Int val v3 = 1L to 10L by 3 //Long val v4 = 1.1f to 10.3f by 3.1f //Float val v5 = 1.1f to 10.3f by 0.5f val v6 = 1.1 to 10.3 by 3.1 //Double val v7 = 'a' to 'g' by 3 //Char val v8 = BigInt(1) to BigInt(10) by 3 val v9 = BigDecimal(1.1) to BigDecimal(10.3) by 3.1 println(v2+"-"+v3+"-"+v4+""+v5+""+v6+""+v7+""+v8+""+v9) val tuple = (1, false, "Scala") val tuple2 ="title" -> "Beginning Scala" val third = tuple._3 println(tuple._2+"-"+tuple2._1+"-"+third) //Unit单元类型用于定义不返回数据的函数。它类似于Java中的void关键字。 } }
参考链接:http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html