文章目录
- 1. 变量和数据类型
- 1.1 变量和常量
- 1.2 字符串
- 1.3 数据类型
- 1.4 伴生对象与伴生类
- 1.5 代码块
- 1.6 Unit、null、Nothing
- 1.7 强制转换
- 1.8 == 与 equals
- 2. 控制语句
- 2.1 分支语句
- 2.2 循环语句
- (1)for循环
- (2)while/do-while循环
- (3) 循环中断
- 3. 函数式编程
- 3.1 方法 vs 函数
- 3.2 方法
- (1) 方法的定义
- (2) 方法声明
- (3) 方法参数
- (4) 方法至简原则
- 3.3 函数
- (1) 函数的声明
- (2) 匿名函数及其至简原则
- (3) 高阶函数
- 3.5 函数柯里化&闭包
- 3.6 递归 & 尾递归
1. 变量和数据类型
1.1 变量和常量
-
语法
- 定义变量:
var 变量名:数据类型 = 值
【相当于Java 没有 用final关键字修饰】 - 定义常量:
val 常量名:数据类型 = 值
【相当于Java 用 final关键字修饰】
- 定义变量:
-
注意点
// 1. 常量值无法修改 val NAME:String = "张三" NAME = "李四” // 报错 // 2. Scala是强引用,因此不可为变量赋不同数据类型的值 var age:int = 18 age = "张三" // 不可将字符串赋值给整型变量 // 3. 声明 常量、变量时,数据类型可以省略,但是初始值不可省略。这样可以自动推断数据类型 // 自动推断 // 值为整型:int // 值为小数:double // 值为字符串:String val NAME = "张三" // 会自动推断变量类型 // 4. 关于对象: // (1) var修饰的对象可以修改指向,而val修饰的对象不可以修改修改指向 // (2) 对象的属性是否可以修改,取决于属性是用var还是val修饰 val PERSON = Person() PERSON = Person() // 错误
- 建议:能用常量,就不要用变量
- 标识符的命名规范
1.2 字符串
-
字符串拼接
-
通过
+
号连接println("hello" + "world")
-
重复字符串拼接
println("linhailinhai" * 200)
-
-
字符串输出
- 通过
%
传值:和c语言一样var name = "张三" var age = 18 printf("name: %s age: %d\n", name, age) // print()不换行,println()换行
s
打头,然后通过$
获取变量值var name = "张三" var age = 18 printf(s"name: ${name} age: ${age}\n")
- 通过
-
三引号字符串:方便写SQL
// s打头,每个行用 | ,最后.stripMargin val sql = s""" | select * | from table | where name = ${name} | and age > ${num} """ // stripMargin 的作用就是忽略空格、|等符号,也就是说只剩下SQL语句
1.3 数据类型
细节:
- Scala中一切数据都是对象,都是 Any 的子类
- StringOps是对Java中的String增强
- Unit 相当于 Java中的 void
- Null 是一个类型,只有一个对象就是 null
- Nothing是所有数据类型的子类。应用场景:一个函数没有明确返回值时指定
- Nothing vs Unit
- Unit 用于方法无返回值的情况
- Nothing 用于非方法的代码块无返回值的情况
- 集合类型的泛型很容易和Java中的数组弄混:
- Java中的数组:
int[] a = {0, 1, 2};
- Scala中的泛型:
Array[Int] a = Array(0, 1, 2) // 使用伴生对象创建数组
1.4 伴生对象与伴生类
在Java的类中,用 static 关键字修饰的内部类、方法、属性,可以通过类名访问。比如:Student.school
。而这并没有创建对象,也就是说Java并不是完全面向对象编程。
所以,在Scala中,没有 static 关键字。为了实现与 static 关键字相同的功能,于是引入了伴生对象。
-
那么如何解决这个问题的呢?
-
思路:static 修饰的就是当前类所共享的,而
Student.school
,中的Student应该是一个对象,而不是类。自然的想到为每个类创建第一个同名字的对象,然后将 static 修饰的东西都放入该对象中即可。 -
例子:
class Student(var name:String, var age:Int) { // 定义与类相关的一些信息 def printerInfo():Unit = { println(s"姓名:${name}, 年龄;${age}") } } object Student{ // 所有Student在同一所学校 var school:String = "XXX高中" // main方法本来应该用static修饰,所以应该写在这里 def main(args: Array[String]): Unit = { var stu = new Student("张三", 18) stu.printerInfo() } }
1.5 代码块
在Scala中:
- 所有代码都是代码块
- 最后一行代码的结果就是返回值,千万不要写return ,只有方法才写return,普通的代码块不写return
object Test {
def main(args: Array[String]): Unit = {
// 所有的代码都是代码块
// 表示运行一段代码 同时将最后一行的结果作为返回值
// 千万不要写return
val i: Int = {
println("我是代码块")
10 + 10
}
// 代码块为1行的时候 大括号可以省略
val i1: Int = 10 + 10
// 如果代码块没有计算结果 返回类型是unit
val unit: Unit = {
println("hello")
println("我是代码块")
}
// 当代码块没办法完成计算的时候 返回值类型为nothing
// val value: Nothing = {
// println("hello")
// throw new RuntimeException
// }
}
}
1.6 Unit、null、Nothing
1.7 强制转换
万物皆对象,对象就要调用方法,所以调用方法进行强制转换:
// Java :
int num = (int)2.5
// Scala :
var num : Int = 2.7.toInt
1.8 == 与 equals
- Scala中==与equals相同,都是比较内容是否相等
- 如果要比较地址是否相同,用
.eq()
方法def main(args: Array[String]): Unit = { val s1 = "abc" val s2 = new String("abc") println(s1 == s2) println(s1.eq(s2)) } 输出结果: true false
2. 控制语句
2.1 分支语句
在Scala中,有if、if-else、if-else if-else,没有switch。用法和Java一样。
2.2 循环语句
(1)for循环
- 基本功能
// 1. 左闭右闭 [] for(i <- 1 to 10) { } // 2. 左闭右开 [) for(i <- 1 to 10) { } // 3. 循环步长 for(i <- 1 to 10 by 2) { // 步长为2,故结果为:1 3 5 7 9 } // 4. for-each for(i <- 集合对象){ }
- 循环守卫
for(i <- 1 to 10 if i != 5) { // 排除5
}
- 一个for循环内定义多个变量
for(i <- 1 to 4; j <- 1 to 5) { }
(2)while/do-while循环
不推荐使用,推荐后面使用函数式编程的递归方法。
(3) 循环中断
Scala内置控制结构特地去掉了break和continue,是为了更好的适应 函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。
// 使用Breaks.breakable()方法来实现中断
// 使用Breaks.break()方法代替java中的break关键字
Breaks.breakable(
for(i <- 1 to 10) {
if(i == 3)
Breaks.break()
}
)
3. 函数式编程
函数式编程思想:① y = f(x),重要的是映射关系。② 当做数学题,故推荐使用val,因为不变值在分布式上计算后不会产生歧义
3.1 方法 vs 函数
-
从广义上理解,函数 是更加广的一个概念,方法 是特殊的函数,即:函数作为类的属性而存在时,此时函数叫做方法。
狭义上理解,认为 方法 是用def
定义的代码块,而 函数 是用lambda表达式定义的代码块。
注:
- 从这两个角度理解都是正确的。从第一个角度来说,函数与方法其实是可以相互转换的,即 类的方法也可以写成
lambda表达式
,同理lambda表达式也可以写成def
代码块形式;从第二个角度来说,可以理解为一个编程规范,但不绝对,因为他们可以相互转换。 - 在后续的学习中,将以后者为主,因为它是编程规范。
- 从这两个角度理解都是正确的。从第一个角度来说,函数与方法其实是可以相互转换的,即 类的方法也可以写成
-
从位置上理解:方法定义在类中,做为类的属性;函数定义在方法内
-
从是否可以重载的角度:方法定义在类中可以实现重载;函数不可以重载
-
从运行位置角度:方法是保存在方法区;函数是保存在堆中
3.2 方法
(1) 方法的定义
注意:这只会定义一个方法,并不会执行方法。
(2) 方法声明
package com.atguigu.chapter06
object TestFunctionDeclare {
def main(args: Array[String]): Unit = {
// 方法1:无参,无返回值
def test(): Unit ={
println("无参,无返回值")
}
test()
// 方法2:无参,有返回值
def test2():String={
return "无参,有返回值"
}
println(test2())
// 方法3:有参,无返回值
def test3(s:String):Unit={
println(s)
}
test3("jinlian")
// 方法4:有参,有返回值
def test4(s:String):String={
return s+"有参,有返回值"
}
println(test4("hello "))
// 方法5:多参,无返回值
def test5(name:String, age:Int):Unit={
println(s"$name, $age")
}
test5("dalang",40)
}
}
(3) 方法参数
object Test03_FunArgs {
def main(args: Array[String]): Unit = {
// (1)可变参数:在类型后面加*号
def sayHi(names:String*):Unit = {
println(s"hi $names")
}
sayHi()
sayHi("linhai")
sayHi("linhai","jinlian")
// (2)可变参数必须在参数列表的最后
def sayHi1(sex: String,names:String*):Unit = {
println(s"hi $names")
}
// (3)参数默认值
def sayHi2(name:String = "linhai"):Unit = {
println(s"hi ${name}")
}
sayHi2("linhai")
sayHi2()
// (4)默认值参数在使用的时候 可以不在最后
def sayHi3( name:String = "linhai" , age:Int):Unit = {
println(s"hi ${name}")
}
// (5)带名参数:指调用方法时,指定传参顺序
sayHi3(age = 10, name = "niu")
}
}
(4) 方法至简原则
注意:方法最后一行的return可以省略,但是除此之外都不能省略。比如
object Test04_FuncSimply {
def main(args: Array[String]): Unit = {
//(1)return可以省略,Scala会使用方法体的最后一行代码作为返回值
def func1(x: Int, y: Int): Int = {
x + y
}
// (2)如果方法体只有一行代码,可以省略花括号
def func2(x: Int, y: Int): Int = x + y
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
// 此时,函数就变为了数学表达式的形式:f(x, y) = x + y
def func3(x: Int, y: Int) = x + y
//(4)如果有return,则不能省略返回值类型,必须指定
def func4(x: Int, y: Int): Int = {
if (x < 20) {
return x + y
}
2 * x + 3 * y
}
//(5)如果方法明确声明unit,那么即使函数体中使用return关键字也不起作用
def func5(x: Int, y: Int): Unit = return x + y
//(6)Scala如果期望是无返回值类型,可以省略等号
def func6(x: Int, y: Int) {
println(x + y)
}
// (7)如果方法无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def func7(): Unit = {
println("hello")
}
// (8)如果方法没有参数列表,那么小括号可以省略,调用时小括号必须省略
def func8 {
println("hello")
}
// (9)如果不关心函数名,只关心映射逻辑,就会变为lambda表达式
(x:Int, y:Int) => {println(x + y)}
val func = (x:Int, y:Int) => {println(x + y)} // 如果要设置函数名可以这样。此时,func就是函数名
}
}
3.3 函数
(1) 函数的声明
注意:这只会定义一个函数,也不会执行函数。
这点和Java不一样,java的lambda表达式是会创建对象,并执行构造方法。
对比方法的定义在结构上变了2点:
- ① 方法先去掉
def
、函数名
、参数类型
、返回值类型
,变为 匿名函数(即lambda表达式)
② 再使用 变量 或 常量 为 匿名函数 赋予函数名
、参数类型 => 返回值类型
- ① 方法先去掉
def
、函数名
、参数类型
,变为 匿名函数(即lambda表达式)
② 再使用 变量 或 常量 为 匿名函数 赋予函数名
object TestFunction {
def main(args: Array[String]): Unit = {
// 1 函数
val add = (x:Int,y:Int) => { x + y }
// 2 函数调用
println(add(10,20))
}
}
(2) 匿名函数及其至简原则
-
匿名函数:没有名字的函数就是匿名函数,表现为:
(x:Int)=>{函数体}
-
至简原则:
def main(args: Array[String]): Unit = { val f0 = (x:Int) => {x * x} //(1)参数的类型可以省略,会根据形参进行自动的推导 val f1 = (x) => {x * x} //(2)类型省略之后,发现只有一个参数,则圆括号可以省略; // 其他情况:没有参数和参数超过1的永远不能省略圆括号。 val f2 = x => {x * x} //(3)匿名函数如果只有一行,则大括号也可以省略 val f3 = x => x * x }
(3) 高阶函数
-
高阶函数:参数或返回值为函数的函数(或方法)称为高阶函数
-
函数作为参数传递:
object TestFunction { def main(args: Array[String]): Unit = { //制作一个计算器 val calculator = (a: Int, b: Int, operater: (Int, Int) => Int) => { //高阶函数————函数作为参数 operater(a, b) } //函数————求和 val plus = (x: Int, y: Int) => { x + y } //方法————求积 val multiply = (x: Int, y: Int) => { x * y } //函数作为参数 println(calculator(2, 3, plus)) println(calculator(2, 3, multiply)) } }
-
函数作为返回值传递
object TestFunction { def main(args: Array[String]): Unit = { val func1 = (x: Int) => { val func2 = (y: Int) => x + y func2 // 或者直接返回:(y: Int) => x + y } } }
一看到
=>
,就要想到这是表示函数。
3.5 函数柯里化&闭包
-
闭包:内层函数用到了外层函数变量,如果直接调用内层函数会取不到外层函数的这个变量值。此时,内层函数(万物皆对象,函数也是对象)的堆中的对象会保留一份引用到外层函数的值。
闭包参考链接 -
函数柯里化:将一个接收多个参数的函数转化成一个一个接受参数的函数过程,可以简单的理解为一种特殊的参数列表声明方式。函数柯里化
object TestFunction { val sum = (x: Int, y: Int, z: Int) => x + y + z // 函数柯里化的底层逻辑:本质是将函数作为返回值 val sum1 = (x: Int) => { y: Int => { // 匿名函数 z: Int => { // 匿名函数 x + y + z } } } // 函数柯里化的另一种简单表达 val sum2 = (x: Int) => (y: Int) => (z: Int) => x + y + z // 方法也有函数柯里化 def sum3(x: Int)(y: Int)(z: Int) = x + y + z def main(args: Array[String]): Unit = { sum(1, 2, 3) sum1(1)(2)(3) // sum1(1)调用完后,返回一个函数; sum1(1)(2)是调用返回的函数; ....... sum2(1)(2)(3) sum3(1)(2)(3) } }
3.6 递归 & 尾递归
-
递归与Java中的递归一样:
object Test{ def main(args: Array[String]): Unit = { // 实现阶乘 def fact(n : Int) : Int = { // 跳出递归 if(n == 1) return 1 // 递归逻辑 n * fact(n - 1) } // 调用阶乘方法 println(fact(5)) } }
-
尾递归:递归是将每次调用函数/方法会压入到栈中,是累计使用资源,容易造成栈溢出;而尾递归是覆盖使用资源,不会造成栈溢出。所以,尾递归资源利用率更加高。尾递归参考链接
一般支持函数式编程语言都支持尾递归;但是Java不支持尾递归。