Scala基础-1
Scala基础
Scala 入门
概述
为什么学习 Scala
- Spark—新一代内存级大数据计算框架,是大数据的重要内容。
- Spark就是使用Scala编写的。因此为了更好的学习Spark, 需要掌握Scala这门语言。
- Spark的兴起,带动Scala语言的发展!
Scala 发展历史
联邦理工学院的马丁·奥德斯基(Martin Odersky)于2001年开始设计Scala。
马丁·奥德斯基是编译器及编程的狂热爱好者,长时间的编程之后,希望发明一种语言,能够让写程序这样的基础工作变得高效,简单。所以当接触到JAVA语言后,对JAVA这门便携式,运行在网络,且存在垃圾回收的语言产生了极大的兴趣,所以决定将函数式编程语言的特点融合到JAVA中,由此发明了两种语言(Pizza & Scala)。
Pizza和Scala极大地推动了Java编程语言的发展。
JDK5.0 的泛型、增 强for循 环、自动类型转换等,都是从Pizza引入的新特性。
JDK8.0 的类型推断、Lambda表达式就是从Scala引入的特性。
JDK5.0和JDK8.0的编辑器就是马丁·奥德斯基写的,因此马丁·奥德斯基一个人的战斗力抵得上一个Java开发团队。
Scala 和 Java 关系
一般来说,学 Scala 的人,都会 Java,而 Scala 是基于 Java 的,因此我们需要将 Scala和 Java 以及 JVM 之间的关系搞清楚,否则学习 Scala 你会蒙圈。
Scala 语言特点
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的
静态类型编程语言(静态语言需要提前编译的如:Java、c、c++等,动态语言如:js)。
Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。
Scala单作为一门语言来看,非常的简洁高效。
Scala在设计时,马丁·奥德斯基是参考了Java的设计思想,可以说Scala是源于Java,同时马丁·奥德斯基也加入了自己的思想,将函数式编程语言的特点融合到JAVA中, 因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala和Java相同点和不同点,就可以快速的掌握Scala这门语言。
Scala 环境搭建
安装步骤
- 首先确保 JDK1.8 安装成功
- 下载对应的 Scala 安装文件 scala-2.12.11.zip
- 解压 scala-2.12.11.zip,我这里解压到 D:\Tools
- 配置 Scala 的环境变量
注意 1:解压路径不能有任何中文路径,最好不要有空格。
1
2放到了D:\Program Files (x86)\scala-2.12.11
然后报错:此时不应有 \scala-2.12.11\bin\..\lib\jline-2.14.6.jar。注意 2:环境变量要大写 SCALA_HOME
1
2
3
4
5
6配置sys
SCALA_HOME
D:\scala-2.12.11
配置path
%SCALA_HOME%\bin测试
需求:计算两数 a 和 b 的和。
步骤
在键盘上同时按 win+r 键,并在运行窗口输入 cmd 命令
输入 Scala 并按回车键,启动 Scala 环境。然后定义两个变量,并计算求和。
1
2
3
4
5
6
7
8
9
10
11scala> println("hello lvxiaoyi");
hello lvxiaoyi
scala> val a = 10;
a: Int = 10
scala> val b = 10;
b: Int = 10
scala> a + b;
res2: Int = 20
Scala 插件安装
默认情况下 IDEA 不支持 Scala 的开发,需要安装 Scala 插件。
插件离线安装步骤
建议将该插件 scala-intellij-bin-2017.2.6.zip 文件,放到 Scala 的安装目录D:\Tools\scala-2.12.11 下,方便管理。
打开 IDEA,在左上角找到 File->在下拉菜单中点击 Setting… ->点击 Plugins->点击右 下 角 Install plugin from disk… , 找 到 插 件 存 储 路 径
D:\Tools\scala-2.12.11\scala-intellij-bin-2017.2.6.zip,最后点击 ok。
插件在线安装
- 在搜索插件框里面输入 Scala->点击 Install->点击 ok->点击 apply。
- 重启 IDEA,再次来到 Scala 插件页面,已经变成 Uninstall。
HelloWorld 案例
创建 IDEA 项目工程
打开 IDEA->点击左侧的 Flie->选择 New->选择 Project…
创建一个 Maven 工程,并点击 next
GroupId 输入 com.atguigu->ArtifactId 输入 scala->点击 next->点击 Finish
指定项目工作目录空间
默认下,Maven 不支持 Scala 的开发,需要引入 Scala 框架。
在 scala0513 项目上,点击右键-> Add Framework Support… ->选择 Scala->点击 OK
注意:如果是第一次引入框架,Use libary 看不到,需要选择你的 Scala 安装目录,然后工具就会自动识别,就会显示 user libary。
创建项目的源文件目录
右键点击 main 目录->New->点击 Diretory -> 写个名字(比如 scala)。
右键点击 scala 目录->Mark Directory as->选择 Sources root,观察文件夹颜色发生变化。
在 scala 包下,创建包 com.atguigu.chapter01 包名和 Hello 类名,
右键点击 scala 目录->New->Package->输入 com.atguigu.chapter01->点击 OK。
右键点击 com.atguigu.chapter01->New->Scala Class->Kind 项选择 Object->Name 项输入
Hello。编写输出 Hello Scala 案例
在类中中输入 main,然后回车可以快速生成 main 方法; 在 main 方法中输入 println(“hello scala”)
1
2
3
4
5
6
7package top.lvxiaoyi.chapter01
object HelloWorld {
def main(args: Array[String]): Unit = {
println("hello lvxiaoyi");
}
}运行后,观察控制台打印输出:
hello lvxiaoyi
class 和 object 说明
对第一个程序进行说明
1 | package top.lvxiaoyi.chapter01 |
Scala完全面向对象,故Scala去掉了Java中非面向对象的元素,如static关键字,void类型
static
Scala无static关键字,由object实现类似静态方法的功能(类名.方法名)。
void
对于无返回值的函数,Scala定义其返回值类型为Unit类
Scala 程序反编译
在项目的 target 目录 Hello 文件上点击右键->Show in Explorer->看到 object 底层生成Hello$.class 和 Hello.class 两个文件
采用 Java 反编译工具 jd-gui.exe 反编译代码,将 Hello.class 拖到 jd-gui.exe 页面
Hello源代码
1
2
3
4
5object HelloWorld {
def main(args: Array[String]): Unit = {
println("hello lvxiaoyi");
}
}HelloWorld.class类
1
2
3
4
5
6
7
8package top.lvxiaoyi.chapter01
public final class HelloWorld
{
public static void main(String[] paramArrayOfString)
{
Hello$.MODULE$.main(paramArrayOfString);
}
}Hello$.class类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public final class HelloWorld$ {
public static final MODULE$;
static {
new ();
}
public void main(String[] args) {
Predef..MODULE$.println("hello lvxiaoyi");
}
private HelloWorld$() {
MODULE$ = this;
}
}HelloWorld中有个main函数,调用 HelloWorld$ 类的一个静态对象MODULES$
HelloWorld$.MODULE$. 对象是静态的,通过该对象调用HelloWorld$的main函数
关联 Scala 源码
在使用 Scala 过程中,为了搞清楚 Scala 底层的机制,需要查看源码,下面看看如何关联和查看 Scala 的源码包。
查看源码
例如查看 Array 源码。按住 ctrl 键->点击 Array->右上角出现 Attach Soures…
关联源码
- 将我们的源码包 scala-sources-2.12.11.tar.gz 拷贝到 D:\Tools\scala-2.12.11\lib 文件夹下,并解压为 scala-sources-2.12.11 文件夹
- 点击 Attach Sources…->选择 D:\scala-2.12.11\lib\scala-sources-2.12.11,这个文件夹,就可以看到源码了
官方编程指南
离线查看:解压 scala-docs-2.11.8.zip,可以获得 Scala 的 API 操作。
变量和数据类型
注释
Scala 注释使用和 Java 完全一样。
注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现
基本语法
单行注释://
多行注释:/* */
文档注释:/**
*
*/
案例实操
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package top.lvxiaoyi.chapter02
object TestNotes {
def main(args: Array[String]): Unit = {
//(1)单行注释://
println("dalang")
//(2)多行注释:/* */
/*
println("dalang")
println("jinlian")
*/
//(3)文档注释:/**
//*
//*/
/**
* println("qingge")
* println("qingge")
* println("qingge")
*/
}
}代码规范
使用一次 tab 操作,实现缩进,默认整体向右边移动,用 shift+tab 整体向左移
或者使用 ctrl + alt + L 来进行格式化
运算符两边习惯性各加一个空格。比如:2 + 4 * 5。
一行最长不超过 80 个字符,超过的请使用换行展示,尽量保持格式优雅
变量和常量(重点)
常量:在程序执行的过程中,其值不会被改变的变量
回顾:Java 变量和常量语法
1
2变量类型 变量名称 = 初始值 int a = 10
final 常量类型 常量名称 = 初始值 final int b = 20基本语法
1
2
3var 变量名 [: 变量类型] = 初始值 var i:Int = 10
val 常量名 [: 常量类型] = 初始值 val j:Int = 20
注意:能用常量的地方不用变量案例实操
声明变量时,类型可以省略,编译器自动推导,即类型推导
类型确定后,就不能修改,说明 Scala 是强数据类型语言。
变量声明时,必须要有初始值
在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package top.lvxiaoyi.chapter02
object TestVar {
def main(args: Array[String]): Unit = {
//(1)声明变量时,类型可以省略,编译器自动推导,即类型推导
var age = 18
age = 30
//(2)类型确定后,就不能修改,说明 Scala 是强数据类型语言。
// age = "tom" // 错误
//(3)变量声明时,必须要有初始值
// var name //错误
//(4)在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。
var num1 = 10 // 可变
val num2 = 20 // 不可变
num1 = 30 // 正确
//num2 = 100 //错误,因为 num2 是 val 修饰的
}
}var 修饰的对象引用可以改变,val 修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16object TestVar {
def main(args: Array[String]): Unit = {
// p1 是 var 修饰的,p1 的属性可以变,而且 p1 本身也可以变
var p1 = new Person()
p1.name = "dalang"
p1 = null
// p2 是 val 修饰的,那么 p2 本身就不可变(即 p2 的内存地址不能变),但是,p2 的属性是可以变,因为属性并没有用 val 修饰。
val p2 = new Person()
p2.name = "jinlian"
// p2 = null // 错误的,因为 p2 是 val 修饰的
}
}
class Person{
var name : String = "jinlian"
}
标识符的命名规范
Scala 对各种变量、方法、函数等命名时使用的字符序列称为标识符。即:凡是自己可
以起名字的地方都叫标识符。
命名规则
Scala 中的标识符声明,基本和 Java 是一致的,但是细节上会有所变化,有以下三种规
则:
以字母或者下划线开头,后接字母、数字、下划线
以操作符开头,且只包含操作符(+ - * / # !等)
用反引号
....
包括的任意字符串,即使是 Scala 关键字(39 个)也可以1
2
3
4
5
6
7
8package, import, class, object, trait, extends, with, type, for
private, protected, abstract, sealed, final, implicit, lazy, override
try, catch, finally, throw
if, else, match, case, do, while, for, return, yield
def, val, var
this, super
new
true, false, null
案例实操
需求:判断 hello、Hello12、1hello、h-b、x h、h_4、_ab、Int、_、+-/#!、+-/#!1、if、`if`,这些名字是否合法。
1 | object TestName { |
字符串输出
基本语法
- 字符串,通过+号连接
- printf 用法:字符串,通过%传值。
- 字符串模板(插值字符串):通过$获取变量值
案例实操
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package top.lvxiaoyi.chapter02
object TestCharType {
def main(args: Array[String]): Unit = {
var name: String = "jinlian"
var age: Int = 18
//(1)字符串,通过+号连接
println(name + " " + age)
//(2)printf 用法字符串,通过%传值。
printf("name=%s age=%d\n", name, age)
//(3)字符串,通过$引用
//多行字符串,在 Scala中,利用三个双引号包围多行字符串就可以实现。
//输入的内容,带有空格、\t 之类,导致每一行的开始位置不能整洁对齐。
//应用 scala 的 stripMargin 方法,在 scala 中 stripMargin 默认是“|”作为连接符,//在多行换行的行头前面加一个“|”符号即可。
val s =
"""
|select
| name,
| age
|from user
|where name="zhangsan"
""".stripMargin
println(s)
//如果需要对变量进行运算,那么可以加${}
val s1 =
s"""
|select
| name,
| age
|from user
|where name="$name" and age=${age + 2}
""".stripMargin
println(s1)
val s2 = s"name=$name"
println(s2)
}
}
键盘输入
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
案例实操
需求:可以从控制台接收用户信息,【姓名,年龄,薪水】。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package top.lvxiaoyi.chapter02
import scala.io.StdIn
object TestInput {
def main(args: Array[String]): Unit = {
// 1 输入姓名
println("input name:")
var name = StdIn.readLine()
// 2 输入年龄
println("input age:")
var age = StdIn.readShort()
// 3 输入薪水
println("input sal:")
var sal = StdIn.readDouble()
// 4 打印
println("name=" + name)
println("age=" + age)
println("sal=" + sal)
}
}
数据类型(重点)
Java数据类型
Java基本类型:char、byte、short、int、long、float、double、boolean
Java引用类型:(对象类型)
由于Java有基本类型,而且基本类型不是真正意义的对象,即使后面产生了基本类型的包装类,但是仍然存在基本数据类型,所以Java语言并不是真正意思的面向对象。
注意:Java中基本类型和引用类型没有共同的祖先。
Java基本类型的包装类:Character、Byte、Short、Integer、Long、Float、Double、Boolean
Scala数据类型
Scala中一切数据都是对象,都是Any的子类。
Scala中数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef),不管是值类型还是引用类型都是对象。
Scala数据类型仍然遵守,低精度的值类型向高精度值类型,自动转换(隐式转换)
Scala中的StringOps是对Java中的String增强
Unit:对应Java中的void,用于方法返回值的位置,表示方法没有返回值。Unit是 一个数据类型,只有一个对象就是()。Void不是数据类型,只是一个关键字
Null是一个类型,只 有一个对 象就 是null。它是所有引用类型(AnyRef)的子类。
Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使 用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。
整数类型(Byte、Short、Int、Long)
Scala 的整数类型就是用于存放整数值的,比如 12,30,3456 等等。
整型分类
数据类型 | 描述 |
---|---|
Byte [1] | 8 位有符号补码整数。数值区间为 -128 到 127 |
Short [2] | 16 位有符号补码整数。数值区间为 -32768 到 32767 |
Int [4] | 32 位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long [8] | 64 位有符号补码整数。数值区间为 -9223372036854775808 到9223372036854775807 = 2^64^-1 |
案例实操
Scala 各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala 程序的可移植性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package top.lvxiaoyi.chapter02
object TestDataType {
def main(args: Array[String]): Unit = {
// 正 确
var n1: Byte = 127
var n2: Byte = -128
// 错 误
// var n3:Byte = 128
// var n4:Byte = -129
}
}Scala 的整型,默认为Int 型,声明 Long 型,须后加‘l’或‘L
1
2
3
4
5
6
7
8
9object TestDataType {
def main(args: Array[String]): Unit = {
var n5 = 10
println(n5)
var n6 = 9223372036854775807L
println(n6)
}
}Scala 程序中变量常声明为Int 型,除非不足以表示大数,才使用Long
浮点类型(Float、Double)
Scala 的浮点类型可以表示一个小数,比如 123.4f,7.8,0.12 等等。
浮点型分类
数据类型 | 描述 |
---|---|
Float [4] | 32 位, IEEE 754 标准的单精度浮点数 |
Double [8] | 64 位 IEEE 754 标准的双精度浮点数 |
案例实操
Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。
1 | object TestDataType { |
字符类型(Char)
基本说明
字符类型可以表示单个字符,字符类型是 Char。
案例实操
- 字符常量是用单引号 ‘ ‘ 括起来的单个字符。
- \t :一个制表位,实现对齐的功能
- \n :换行符
- \\ :表示\
- \“ :表示”
1 | object TestCharType { |
布尔类型:Boolean
基本说明
- 布尔类型也叫 Boolean 类型,Booolean 类型数据只允许取值 true 和 false
- boolean 类型占 1 个字节。
案例实操
1 | package top.lvxiaoyi.chapter02 |
Unit 类型、Null 类型和 Nothing 类型(重点)
基本说明
数据类型 | 描述 |
---|---|
Unit | 表示无值,和其他语言中 void 等同。用作不返回任何结果的方法的结果类型。Unit 只有一个实例值,写成()。 |
Null | null , Null 类型只有一个实例值 null |
Nothing | Nothing 类型在 Scala 的类层级最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用 Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
案例实操
Unit 类型用来标识过程,也就是没有明确返回值的函数。
由此可见,Unit 类似于 Java 里的 void。Unit 只有一个实例——**( )**,这个实例也没有实质意义
1
2
3
4
5
6
7
8
9
10package top.lvxiaoyi.chapter02
object TestSpecialType {
def main(args: Array[String]): Unit = {
def sayOk: Unit = { // unit 表示没有返回值,即 void
}
println(sayOk)
}
}Null 类只有一个实例对象,Null 类似于 Java 中的 null 引用。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
1
2
3
4
5
6
7
8
9
10
11object TestDataType {
def main(args: Array[String]): Unit = {
//null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
var cat = new Cat();
cat = null // 正确
// var n1: Int = null // 错误
println("n1:" + n1)
}
}
class Cat {
}Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容。
1
2
3
4
5
6
7
8
9object TestSpecialType {
def main(args: Array[String]): Unit = {
def test(): Nothing = {
throw new Exception()
}
test
}
}
类型转换
扩展 Java 面试题(隐式类型转换):
1 | public class TypeTransition { |
数值类型自动转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:
基本说明
- 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成
精度大的那种数据类型,然后再进行计算。
把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
(byte,short)和 char 之间不会相互自动转换。
byte,short,char 他们三者可以计算,在计算时首先转换为 int 类型
案例实操
1 | object TestValueTransfer { |
注意:Scala 还提供了非常强大的隐式转换机制(隐式函数,隐式类等),我们放在高级部分专门用一个章节来讲解。
强制类型转换
基本说明
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
1 | Java : int num = (int)2.5 |
案例实操
- 将数据由高精度转换为低精度,就需要使用到强制转换
- 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
1 | object TestForceTransfer { |
数值类型和 String 类型间转换
基本说明
在程序开发中,我们经常需要将基本数值类型转成 String 类型。或者将 String 类型转成基本数值类型。
案例实操
- 基本类型转 String 类型(语法:将基本类型的值+”” 即可)
- String 类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
1 | object TestStringTransfer { |
注意事项
在将 String 类型转成基本数值类型时,要确保 String 类型能够转成有效的数据,比如我们可以把”123”,转成一个整数,但是不能把”hello”转成一个整数。
var n5:Int = “12.6”.toInt 会出现 NumberFormatException 异常。
扩展面试题
1
2
3
4
5
6
7
8object TestType {
def main (args : Array [String]) : Unit = {
/ /00000000 00000000 00000000 10000010//var n: Int = 128
var n : Int = 130
var b: Byte = n.toByte
//10000000为当前字节范围最小值,约定-128
//负数补码:符号位不变,其它位按位取反+1println(b)
}
运算符
Scala 运算符的使用和 Java 运算符的使用基本相同,只有个别细节上不同。
算术运算符
基本语法
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | b=4; -b | -4 |
+ | 加 | 5+5 | 10 |
- | 减 | 6-4 | 2 |
* | 乘 | 3*4 | 12 |
/ | 除 | 5/5 | 1 |
% | 取模(取余) | 7%5 | 2 |
+ | 字符串相加 | “He”+”llo” | “Hello” |
对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
对一个数取模 a%b,和 Java 的取模规则一样。
案例实操
1 | object TestArithmetic { |
关系运算符(比较运算符)
基本语法
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
== | 相等于 | 4==3 | false |
!= | 不等于 | 4!=3 | true |
< | 小于 | 4<3 | false |
> | 大于 | 4>3 | true |
<= | 小于等于 | 4<=3 | false |
>= | 大于等于 | 4>=3 | true |
案例实操
需求 1:
1
2
3
4
5
6
7
8
9
10
11
12
13object TestRelation {
def main(args: Array[String]): Unit = {
// 测试:>、>=、<=、<、==、!=
var a: Int = 2
var b: Int = 1
println(a > b) // true
println(a >= b) // true
println(a <= b) // false
println(a < b) // false
println("a==b" + (a == b)) // false
println(a != b) // true
}
}需求 2:Java 和 Scala 中关于==的区别
Java: ==比较两个变量本身的值,即两个对象在内存中的首地址;
equals 比较字符串中所包含的内容是否相同。1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
输出结果:
false
trueScala:==更加类似于 Java 中的 equals,参照 jd 工具
1
2
3
4
5
6
7
8
9def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2)
println(s1.eq(s2))
}
输出结果:
true
false
逻辑运算符
基本语法
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个 Boolean 值。
假定:变量 A 为 true,B 为 false
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑与 | (A && B) 运算结果为 false |
|| | 逻辑或 | (A || B) 运算结果为 true |
! | 逻辑非 | !(A && B) 运算结果为 true |
案例实操
1 | object TestLogic { |
赋值运算符
基本语法
赋值运算符就是将某个运算后的值,赋给指定的变量。
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值 给 C |
+= | 相加后再赋值 | C += A 等 于 C = C + A |
-= | 相减后再赋值 | C -= A 等 于 C = C - A |
*= | 相乘后再赋值 | C *= A 等 于 C = C * A |
/= | 相除后再赋值 | C /= A 等 于 C = C / A |
%= | 求余后再赋值 | C %= A 等 于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等 于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等 于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等 于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等 于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等 于 C = C | 2 |
注意:Scala 中没有++、–操作符,可以通过+=、-=来实现同样的效果;
案例实操
1 | object TestAssignment { |
位运算符
基本语法
下表中变量 a 为 60,b 为 13。
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | 按位或运算符 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
^ | 按位异或运算符 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
~ | 按位取反运算符 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
<< | 左移动运算符 | a << 2 输出结果 240 ,二进制解释: 0011 0000 |
>> | 右移动运算符 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
>>> | 无符号右移 | a >>>2 输出结果 15, 二进制解释: 0000 1111 |
案例实操
1 | object TestPosition { |
Scala 运算符本质
在 Scala 中其实是没有运算符的,所有运算符都是方法。
当调用对象的方法时,点.可以省略
如果函数参数只有一个,或者没有参数,()可以省略
1
2
3
4
5
6
7
8
9
10
11
12
13
14object TestOpt {
def main(args: Array[String]): Unit = {
// 标准的加法运算
val i:Int = 1.+(1)
// (1)当调用对象的方法时,.可以省略
val j:Int = 1 + (1)
// (2)如果函数参数只有一个,或者没有参数,()可以省略
val k:Int = 1 + 1
println(1.toString())
println(1 toString())
println(1 toString)
}
}
流程控制
分支控制 if-else
让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支
单分支
基本语法
1 | if (条件表达式) { |
说明:当条件表达式为 ture 时,就会执行{ }的代码。
案例实操
需求:输入人的年龄,如果该同志的年龄小于 18 岁,则输出“童年”
1 | object TestIfElse { |
双分支
基本语法
1 | if (条件表达式) { |
案例实操
需求:输入年龄,如果年龄小于 18 岁,则输出“童年”。否则,输出“成年”。
1 | object TestIfElse { |
多分支
基本语法
1 | if (条件表达式 1) { |
案例实操
需求 1:需求:输入年龄,如果年龄小于 18 岁,则输出“童年”。如果年龄大于等于 18 且小于等于 30,则输出“中年”,否则,输出“老年”。
1
2
3
4
5
6
7
8
9
10
11
12
13object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
if (age < 18){
println("童年")
}else if(age>=18 && age<30){
println("中年")
}else{
println("老年")
}
}
}需求 2:Scala 中 if else 表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
"老年"
}
println(res)
}
}需求 3:Scala 中返回值类型不一致,取它们共同的祖先类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
100
}
println(res)
}
}需求 4:Java 中的三元运算符可以用 if else 实现scala
如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if 只对最近的一行逻辑代码起作用。
1
2
3
4
5
6
7
8
9
10
11object TestIfElse {
def main(args: Array[String]): Unit = {
// Java
// int result = flag?1:0
// Scala
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18) "童年" else "成年" "不起作用"
println(res)
}
}
嵌套分支
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。嵌套分支不要超过 3 层。
基本语法
1 | if(){ |
案例实操
需求:如果输入的年龄小于 18,返回“童年”。如果输入的年龄大于等于 18,需要再判断:如果年龄大于等于 18 且小于 30,返回“中年”;如果其他,返回“老年”。
1 | object TestIfElse { |
Switch 分支结构
在 Scala 中没有 Switch,而是使用模式匹配来处理。
模式匹配涉及到的知识点较为综合,因此我们放在后面讲解。
For 循环控制
Scala 也为 for 循环这一常见的控制结构提供了非常多的特性,这些 for 循环的特性被称为 for 推导式或 for 表达式。
范围数据循环(To)
基本语法
1 | for(i <- 1 to 3){ |
- i 表示循环的变量,<- 规定 to
- i 将会从 1-3 循环,前后闭合
案例实操
需求:输出 5 句 “宋宋,告别海狗人参丸吧”
1 | object TestFor { |
范围数据循环(Until)
基本语法
1 | for(i <- 1 until 3) { |
这种方式和前面的区别在于 i 是从 1 到 3-1
即使前闭合后开的范围
案例实操
需求:输出 5 句 “宋宋,告别海狗人参丸吧”
1 | object TestFor { |
循环守卫
基本语法
1 | for(i <- 1 to 3 if i != 2) { |
说明:
循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为 true 则进入循环体内部,为 false 则跳过,类似于 continue。
上面的代码等价
1
2
3
4
5for (i <- 1 to 3){
if (i != 2) {
print(i + " ")
}
}
案例实操
需求:输出 1 到 5 中,不等于 3 的值
1 | object TestFor { |
循环步长
基本语法
1 | for (i <- 1 to 10 by 2) { |
说明:by 表示步长
案例实操
需求:输出 1 到 10 以内的所有奇数
1 | for (i <- 1 to 10 by 2) { |
输出结果
1 | i=1 |
嵌套循环
基本语法
1 | for(i <- 1 to 3; j <- 1 to 3) { |
说明:没有关键字,所以范围后一定要加;来隔断逻辑
上面的代码等价
1 | for (i <- 1 to 3) { |
引入变量
基本语法
1 | for(i <- 1 to 3; j = 4 - i) { |
说明:
- for 推导式一行中有多个表达式时,所以要加 ; 来隔断逻辑
- for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号,当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下
1 | for { |
案例实操
上面的代码等价于
1 | for (i <- 1 to 3) { |
循环返回值
基本语法
1 | val res = for(i <- 1 to 10) yield i |
说明:将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。
注意:开发中很少使用。
案例实操
需求:将原数据中所有值乘以 2,并把数据返回到一个新的集合中。
1 | object TestFor { |
输出结果:
1 | Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) |
倒序打印
说明:如果想倒序打印一组数据,可以用 reverse。
案例实操:
需求:倒序打印 10 到 1
1 | for(i <- 1 to 10 reverse){ |
While 和 do..While 循环控制
While 和 do..While 的使用和 Java 语言中用法相同。
While 循环控制
基本语法
1 | 循环变量初始化 |
说明:
循环条件是返回一个布尔值的表达式
while 循环是先判断再执行语句
与 for 语句不同,while 语句没有返回值,即整个 while 语句的结果是 Unit 类型()
因为 while 中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在 while 循环的外部,那么就等同于循环的内部对外部的变量造成了影响,所以不推荐使用,而是推荐使用 for 循环。
案例实操
需求:输出 10 句 “宋宋,喜欢海狗人参丸”
1 | object TestWhile { |
do..while 循环控制
基本语法
1 | 循环变量初始化; |
说明
- 循环条件是返回一个布尔值的表达式
- do..while 循环是先执行,再判断
案例实操
需求:输出 10 句 “宋宋,喜欢海狗人参丸”
1 | object TestWhile { |
循环中断
基本说明
Scala 内置控制结构特地去掉了 break 和 continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。Scala中使用breakable控制结构来实现 break 和 continue 功能。
案例实操
需求 1:采用异常的方式退出循环
1
2
3
4
5
6
7
8
9
10
11def main(args: Array[String]): Unit = {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw new RuntimeException
}
}catch {
case e =>
}
println("正常结束循环")
}需求 2:采用 Scala 自带的函数,退出循环
1
2
3
4
5
6
7
8
9
10import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
Breaks.breakable(
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) Breaks.break()
}
)
println("正常结束循环")
}需求 3:对 break 进行省略
1
2
3
4
5
6
7
8
9
10
11
12object TestBreak {
def main(args: Array[String]): Unit = {
breakable {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) break
}
}
println("正常结束循环")
}
}循环遍历 10 以内的所有数据,奇数打印,偶数跳过(continue)
1
2
3
4
5
6
7
8
9
10
11object TestBreak {
def main(args: Array[String]): Unit = {
for (elem <- 1 to 10) {
if (elem % 2 == 1) {
println(elem)
} else {
println("continue")
}
}
}
}
多重循环
基本说明
- 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while
均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过 3 层】 - 设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次。
案例实操
需求:打印出九九乘法表
1 | object TestWhile { |
函数式编程
是什么
面向对象编程
解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。
对象:用户
行为:登录、连接 JDBC、读取数据库
属性:用户名、密码
Scala 语言是一个完全面向对象编程语言。万物皆对象
对象的本质:对数据和行为的一个封装
函数式编程
解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如:请求->用户名、密码->连接 JDBC->读取数据库
Scala 语言是一个完全函数式编程语言。万物皆函数。
函数的本质:函数可以当做一个值进行传递
==在 Scala 中函数式编程和面向对象编程完美融合在一起了==
函数基础
函数基本语法
基本语法
案例实操
需求:定义一个函数,实现将传入的名称打印出来。
1 | object TestFunction { |
函数和方法的区别
核心概念
- 为完成某一功能的程序语句的集合,称为函数。
- 类中的函数称之方法。
案例实操
Scala 语言可以在任何的语法结构中声明任何的语法
函数没有重载和重写的概念;方法可以进行重载和重写
Scala 中函数可以嵌套定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29object TestFunction {
// (2)方法可以进行重载和重写,程序可以执行
def main(): Unit = {
}
def main(args: Array[String]): Unit = {
// (1)Scala 语言可以在任何的语法结构中声明任何的语法
import java.util.Date
new Date()
// (2)函数没有重载和重写的概念,程序报错
def test(): Unit = {
println("无参,无返回值")
}
test()
def test(name: String): Unit = {
println()
}
//(3)Scala 中函数可以嵌套定义
def test2(): Unit = {
def test3(name: String): Unit = {
println("函数可以嵌套定义")
}
}
}
}
函数定义
函数定义
函数 1:无参,无返回值
函数 2:无参,有返回值
函数 3:有参,无返回值
函数 4:有参,有返回值
函数 5:多参,无返回值
函数 6:多参,有返回值
案例实操
1 | package com.atguigu.chapter05 |
函数参数
- 可变参数
- 如果参数列表中存在多个参数,那么可变参数一般放置在最后
- 参数默认值,一般将有默认值的参数放置在参数列表的后面
- 带名参数
1 | object TestFunction { |
函数至简原则(重点)
函数至简原则:能省则省
至简原则细节
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
案例实操
1 | object TestFunction { |
函数高级
高阶函数
在 Scala 中,函数是一等公民。怎么体现的呢?
对于一个函数我们可以:定义函数、调用函数
1 | object TestFunction { |
但是其实函数还有更高阶的用法
函数可以作为值进行传递
1 | object TestFunction { |
函数可以作为参数进行传递
1 | def main (args: Array[String] ): Unit = { |
函数可以作为函数返回值返回
1 | def main(args: Array[String]): Unit = { |
匿名函数
说明
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
案例实操
需求 1:传递的函数有一个参数
传递匿名函数至简原则:
参数的类型可以省略,会根据形参进行自动的推导
类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
匿名函数如果只有一行,则大括号也可以省略
如果参数只出现一次,则参数省略且后面参数可以用_代替
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36def main(args: Array[String]): Unit = {
// (1)定义一个函数:参数包含数据和逻辑函数
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
// (2)定义逻辑函数
def op(ele: Int): Int = {
ele + 1
}
// (3)标准函数调用
val arr = operation(Array(1, 2, 3, 4), op)
println(arr.mkString(","))
// (4)采用匿名函数
val arr1 = operation(Array(1, 2, 3, 4), (ele: Int) => {
ele + 1
})
println(arr1.mkString(","))
// (4.1)参数的类型可以省略,会根据形参进行自动的推导;
val arr2 = operation(Array(1, 2, 3, 4), (ele) => {
ele + 1
})
println(arr2.mkString(","))
// (4.2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1 的永远不能省略圆括号。
val arr3 = operation(Array(1, 2, 3, 4), ele => {
ele + 1
})
println(arr3.mkString(","))
// (4.3) 匿名函数如果只有一行,则大括号也可以省略
val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
println(arr4.mkString(","))
//(4.4)如果参数只出现一次,则参数省略且后面参数可以用_代替
val arr5 = operation(Array(1, 2, 3, 4), _ + 1)
println(arr5.mkString(","))
}
传递的函数有两个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17object TestFunction {
def main(args: Array[String]): Unit = {
def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int = {
op(a, b)
}
// (1)标准版
println(calculator(2, 3, (x: Int, y: Int) => {
x + y
}))
// (2)如果只有一行,则大括号也可以省略
println(calculator(2, 3, (x: Int, y: Int) => x + y))
// (3)参数的类型可以省略,会根据形参进行自动的推导;
println(calculator(2, 3, (x, y) => x + y))
// (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
println(calculator(2, 3, _ + _))
}
}扩展练习
练习 1:定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。
要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。
练习 2: 定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。
要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。
高阶函数案例
需求:模拟 Map 映射、Filter 过滤、Reduce 聚合
1 | object TestFunction { |
函数柯里化&闭包
闭包:函数式编程的标配
说明
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
案例实操
闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26object TestFunction {
def main(args: Array[String]): Unit = {
def f1() = {
var a: Int = 10
def f2(b: Int) = {
a + b
}
f2 _
}
// 在调用时,f1 函数执行完毕后,局部变量 a 应该随着栈空间释放掉
val f = f1()
// 但是在此处,变量 a 其实并没有释放,而是包含在了 f2 函数的内部,形成了闭合的效果
println(f(3))
println(f1()(3))
// 函数柯里化,其实就是将复杂的参数逻辑变得简单化,函数柯里化一定存在闭包
def f3()(b: Int) = {
a + b
}
println(f3()(3))
}
}
递归
说明
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
案例实操
1 | object TestFunction { |
控制抽象
值调用:把计算后的值传递过去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15object TestControl {
def main(args: Array[String]): Unit = {
def f = () => {
println("f...")
10
}
foo(f())
}
def foo(a: Int): Unit = {
println(a)
println(a)
}
}名调用:把代码传递过去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object TestControl {
def main(args: Array[String]): Unit = {
def f = () => {
println("f...")
10
}
foo(f())
}
//def foo(a: Int):Unit = {
def foo(a: => Int): Unit = { //注意这里变量 a 没有小括号了
println(a)
println(a)
}
}
输出结果:
f...
10
f...
10注意:Java 只有值调用;Scala 既有值调用,又有名调用。
案例实操
1 | object TestFunction { |
自定义一个 While 循环
1 | object TestFunction { |
惰性加载
说明
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数
案例实操
1 | def main(args: Array[String]): Unit = { |
输出结果
1 | ---------------- |
注意:lazy 不能修饰 var 类型的变量