本文适合有C、C++、Java等编程语言基础的人查看。
毕业设计所逼,我课题是《基于spark的实时音乐推荐系统》,而Spark是用Scala开发的,使用Scala必然最合适。虽然Spark提供了Java、Python等的API,但貌似效果不如Scala。
且Scala自身较为独特,和Java还有很深的渊源,毕竟可以说Scala和Java8.0都出自一人之手。
自己找了个网课,开干。
xxx
注意,不同的Scala版本,可能会有不同的运行结果。选择版本时,要兼顾Spark等其它框架、软件的要求
后文所有内容都将基于以下版本展开,最新情况请参考官方文档
补充:于2021年4月2日将IDEA切换为IntelliJ IDEA 2020.3.3 (Ultimate Edition)
补充:于2021年4月5日将Scala由2.11.8切换为2.13.5
若在idea内无法下载,需要前往idea官网下载后再安装,注意插件版本问题!!! 插件需要根据idea版本选择
xxxx
注释
对于方法(函数)的注释,写好方法后,在其上一行键入/**
并按回车,其注释模板会自动补全:
/**
* @example
* 输入10和20 输出30
* @param number1 数字1
* @param number2 数字2
* @return 两数之和
*/
def plus(number1: Int, number2: Int): Int = {
return number1 + number2;
}
文档生成
在powershell中(保证路径无误):
scaladoc.bat -d d:\scala\mydoc\ TestScala.scala
var name = "A_Nan"
var age = 20
// 三种输出方式
println("1. hello" + name + age)
printf("2. hello name: %s\tage: %d\n", name, age)
println(s"3. hello name: $name\tage: ${age + 20}")
结果:
var
生成变量
var age: Int = 10
var age2 = 20 //可省略数据类型 自动推导
var num: Double = 10.9
var name: String = "hello"
var isPass: Boolean = true
var score: Float = 11.8f
val
生成常量,不可再次修改,该种生成方法效率更高(因为在底层不用考虑线程安全问题)
Scala设计者推荐使用val
例如:一般生成对象后就不再重新指定对象,因此生成对象时可使用val
val Dog = new Dog //生成val对象 提高效率
Dog.name = "hello" //对象的属性可正常修改
Dog.name = "123"
class Dog {
var name: String = ""
}
这样就有一个好处——变量有大量方法可以使用:
注:无参方法可省略括号
var age: Int = 10
age.toString()
age.toString//无参方法可省略括号
var i = 10 //Int
var j = 10l //Long
var k = 10L //Long
注意:
这里报错的直接原因不是数字大小超过了变量number
的范围,而是因为默认情况下数字均是int类型,错误发生在赋值号之后,将数字改为Long即可:
Long最大值和最小值:
println("MinLong:" + Long.MinValue)
println("MaxLong: " + Long.MaxValue)
处理大数据会有专用类型,Long的范围远远不够!
默认为Double,声明为Float要在数字后加“f”或“F”。
浮点型常量表示形式(两种):
十进制形式:5.12, 512.0f,. 512(必须要有小数点)
科学计数法:5.12e2, 5.12E-2
字符常量是用单引号括起来的单个字符。
例如:var ch = 'a'
也允许使用转义字符
例如:var ch = '\n' //表示换行符
可以直接给char变量赋予整数,会按照Unicode编码保存
char可以进行运算
var ch: Char = 'a'
var number: Int = 10 + ch + 'b'
println("number: " + number)
结果:
以下为错误赋值:
Scala提供了非常强大的隐式转换机制(隐式函数,隐式类等),请看高级部分
将容量大的数据类型转为容量小的数据类型,但可能会造成精度降低或溢出。
var num: Int = 2.7.toInt //使用强转方法
var str: String = 123 + ""
使用String的.toXxx
方法
var str: String = "1234"
var num: Int = str.toInt
var num_double: Double = str.toDouble
注意:
转换时确保能够将String转为有效数据,例如:
"hello"
转为整数"12.2"
转为Int
var `true` = 123
var num1: Int = 10 / 3
var num2: Double = 10 / 3
var num3: Double = 10.0 / 3
println("num1: " + num1)
println("num2: " + num2)
println("num3: " + num3)
println("num3: " + num3.formatted("%.2f"))
var num = 1
num += 1 //代替++
num -= 1 //代替--
生成的值均为Boolean(true或false)
规则同C、Java,此处不做介绍
&&
||
!
package com.test.scala01
import scala.io.StdIn
object Test2 {
def main(args: Array[String]): Unit = {
val name: String = StdIn.readLine()
println(name)
}
}
整体类似于Java,从前到后、从上到下执行
变量是向前引用,但对象、方法无限制
val num = 12
if(num > 10) {
print("yes")
}
val num = 122
if (num > 1000) {
print("yes")
} else {
print("no")
}
val num = 122
if (num == 100) {
print("100")
} else if (num == 111) {
print("111")
} else {
print("no")
}
同Java、C等语言,此处不做介绍,推荐嵌套不要超过三层
在Scala中用模式匹配来处理,该方法需要知识较多,后文进行介绍
在if else
中:
若大括号内只有一行代码,那么大括号可以省略
Scala中任何表达式都有返回值,包括if else
表达式,if else
的返回内容取决于满足条件的代码体的最后一行内容,以下为特殊情况:
val num = if (12 == 100) {
200
}
print(num)
这里小括号()
表示返回值为Unit
,即空
Scala中没有三元运算符,因为可以用if else
代替:
val num = if (12 > 100) 12 else 100
for
第一种:
for (i <- 1 to 5) {
//[1, 5]
println(i) //一共循环五次
}
结果:
第二种:
for (i <- 1 until 5) {
//[1,5)
println(i) //循环4次
}
在for
循环中引入变量:
可以在原括号内的后面加上分号;
后再创建新的变量,让编程更灵活
for (i <- 1 until 5; j = 6 - i) {
//分号不可省略
println(j)
}
运行结果:
注意,在Scala的for
循环中,分号;
的意义和C、Java等语言中for
循环的分号不一样了,以至于Scala的for循环嵌套可以这么写:
for (i <- 1 to 3; j <- 1 until 3) {
println("i=" + i + " j=" + j)
}
运行结果如下:
这种写法相当于:
for (i <- 1 to 3) {
for (j <- 1 until 3) {
println("i=" + i + " j=" + j)
}
}
在实际的使用中,还是这种原始的方法更为灵活。说实话,把两个循环条件写一个括号里没求用,花里胡哨反而影响代码阅读。。。。。。
注意1:
{}
和()
对于for来说都可以
for (i <- 1 to 5) {
//两个for循环功能相同
print(i)
}
for {
i <- 1 to 5} {
//两个for循环功能相同
print(i)
}
不成文规定(非强制性):for
推导式仅包含单一表达式用小括号,多个表达式用大括号
当使用大括号{}
换行写表达式时,分号可省略
for {
i <- 1 to 10
j = 2 * i
} {
println(j)
}
注意2:
要想理解1 to 10
,不妨先想想以下代码可否执行?结果是怎样?
print(1 to 10)
运行结果:
有意思吧,Range()
这个东西用处多多,下面还会提到。
注意3:
for
循环步长的控制
Range()
for (i <- Range(1, 10, 2)) {
//范围[1, 10),步长为2
println(i)
}
reverse
查一下单词意思就明白了
reverse
适用于List
:
val list = List(1, 2, 3)
println(list)//1, 2, 3
println(list.reverse)//3, 2, 1
因此,如果要反着循环,可以通过如下实现:
使用reverse
for (i <- 0 to 10 reverse) {
println(i)
}
当然,前文提到了0 to 10
就是Range(0, 11)
,因此也可以这么写:
for (i <- (0 to 10).reverse) {
println(i)
}
或:
for (i <- Range(0, 11).reverse) {
println(i)
}
当然,通过减法运算也可实现一样的效果
for (i <- 0 to 10) {
println(10 - i)
}
如同if
语句一样,for
循环语句也有返回值,但要借助yield
:
val num = for (i <- 1 to 3) yield i;
print(num)
结果如下:
这就表明,yield
返回的对象是Vector
,也就是会把每次循环得到的变量i
放入Vector
中,循环结束后将Vector
赋值给变量num
。同时,在yield
后面可以是一个代码块,这样就会有更强的灵活性:
val num = for (i <- 1 to 3) yield {
if (i * i > 2 * i) {
i * i
} else {
2 * i
}
};
print(num)
结果如下:
这种特性,正体现了Scala中的一个重要特点,就是Scala擅长将一个集合中的每一个元素都进行处理后,返回给新的集合。
循环守卫也叫循环保护式、条件判断式、守卫。保护式为true则进入循环体,为false则跳过,类似C语言中的continue
例如:
for (i <- 1 until 5 if i != 2) {
println(i)
}
该代码一共会循环四次,其中i等于2时跳过
故也可以通过此方法来实现循环步长的控制:
for (i <- 1 to 10 if i % 2 != 0) {
println(i)
}
同C、Java等语言相似:
if
语句不同,while
语句本身没有值,即整个while
语句的结果是Unit
类型的,即()
while
循环的外部声明变量,所以会造成循环内部对外部的变量造成了影响,而这是与Scala的设计理念相违背的,因此更推荐使用for
循环例子如下:
var i = 0
while (i < 10) {
i += 1
print("hello:" + i)
}
同样不推荐使用,理由同上
例子:
var i = 0
do {
i += 1
println(i + " hello")
} while (i < 5)
在Scala中是没有break
和continue
这两个关键字的,所以无法像C、Java等语言一样通过它们来实现循环控制。这么做是为了更好地适应函数化编程。
在Scala中,虽然没有break
关键字,但是存在break函数,即break()
。注意,关键字和函数是不一样的。
在使用break()
时,会在循环中抛出一个异常:
package com.test.scala01
import util.control.Breaks._
object Test2 {
def main(args: Array[String]): Unit = {
while (true) {
break();//此处抛出异常
}
}
}
其实,大家看到这应该就有些眉目了,Scala是通过异常处理这种方式来实现和break关键字
一样的功能。所以,既然抛出了异常,就要异常处理。
当然,这里需要使用breakable()
来接收异常,这是一个高阶函数
,高阶函数可以接受代码段作为参数:
package com.test.scala01
import util.control.Breaks._
object Test2 {
def main(args: Array[String]): Unit = {
breakable(
while (true) {
break();
}
)
}
}
这样异常就被捕获了:
不过在breakable()
的小括号中写好几行代码怪怪的,所以也可以用大括号:
package com.test.scala01
import util.control.Breaks._
object Test2 {
def main(args: Array[String]): Unit = {
breakable {
while (true) {
break();
}
}
}
}
查看breakable()
源码就能看出这种方式实现的原理:
图中的op
就是自己写的代码块,在程序运行时,直接把这些括号内的代码都作为参数传至try语句
的后面,通过try-catch
来实现循环中断。
注意:不是所有的函数的小括号都可以换为大括号!!
while循环
没有守卫。。。)if语句
加以解决注意:函数式编程和函数是两个概念
def 函数名 ([参数:参数类型],······)[[:返回值类型]=] {
语句······
return 返回值
}
def
(definition)[参数名:参数类型],···
表示函数的输入(也就是参数列表)可以有也可以没有。如果有,多个参数使用逗号间隔返回值有多种情况:
:返回值类型=
=
空
return
,默认以执行到最后一行的结果作为返回值Unit
,那么不论return
什么东西,返回值一定为Unit
,即空
,也就是()
如果写了return
,那么就必须要指明返回类型,而不能只写=
让编译器推导类型
错误写法(浅灰色内容是IDEA的自动类型显示,不是代码部分):
正确的写法:
顾名思义,可变参数是指向函数中传入的参数的数量是可以变化的,底层是通过集合(类似于Java中的数组)来进行接收参数的。
用*
表示此参数为可变参数
def main(args: Array[String]): Unit = {
printString("aaa", "bbb", "ccc")