Scala语言运行在JVM上, 因此Scala可以认为在一定程度上与Java是兼容的, Scala可以自由的使用Java所有的类.
变量
Scala的变量分为可变变量与不可变变量. 变量在创建时可以指定变量类型, 也可以省略, 让编译器根据赋予的初始值自动推断类型. 所有变量都必须在定义时初始化, 但如果初始值不确定, 可以使用占位符进行初始化.
1 | var numberA = 123 |
在Scala中统一了基本类型和封装类型, 因此无论是Java中的int类型还是Integer类型, 在Scala中都是为Int类型.
惰性初始化
初始化变量时, 可以采用lazy关键字, 使得变量在使用时才会赋值. lazy关键字只能修饰val变量
1 | > lazy val v1 = "test" |
基础特性
Scala有一些基础特性, 在其他语言中可能有类似的语言, 具体如下
- 3个双引号”””包裹的字符串保持原样输出
- 以s开头的字符串使用
$x
的形式在字符串中引用变量 - 支持圆括号定义元组, 并使用
tuple._n
的形式访问元组的第n个元素(第一个元素是_1
) - 支持元组拆包
- 使用
'
定义一个符号(同Scheme语言) - 单参数函数调用可以省略
.
和括号, 例如a.to(b)
等价于a to b
或者a + b
等价于a.+(b)
- 支持默认参数和命名参数传递(同Python)
控制语句
Scala的控制语句基本与Java一致, 但for循环有额外的格式
1 | for( i <- 1 to 5) { |
其中的变量i不需要定义, 编译器能够自动推断i的类型. 表达式部分要求是任意的Range类型. to
函数是包含右边界的, 如果不需要包含右边界, 可以使用until
函数.
对于For循环的控制, Scala不提供break和continue关键字, 但提供类似Python列表推导格式的循环控制
1 | for(i <- 1 to 5 if i > 2; if i < 4) { |
与Python相同, 可以使用yield关键字实现序列
数据结构
Scala区分可变集合与不可变集合, 且默认情况下, 所有的集合都是不可变集合. 不可变集合是函数变成的一个基本设置. 默认情况下, Scala会自动导入不可变集合的相关包, 如果需要可变集合, 则需要手动导入scala.collection.mutable
包中的相关类. 常用的集合类如下所示
不可变集合 | 可变集合 |
---|---|
Array | ArrayBuffer |
List | ListBuffer |
/ | LinkedList, DoubleLinkedList |
List | MutableList |
/ | Queue |
Array | ArraySeq |
Stack | Stack |
HashMap | HashMap |
HashSet | HashSet |
/ | ArrayStack |
与Java一样, 集合类型都是泛型, 但Scala使用[]
指定泛型的具体类型, 例如
1 | // 显式创建数组 |
对于List类型, Scala还提供了一个类似Scheme的创建方法, 形式如下
1 | > val nums = 1::2::3::4::Nil |
集合操作相关的API与JAVA大同小异, 具体使用时, 可以查阅API文档
函数
一个标准的函数具有如下的结构
1 | def gcd(x:Int, y:Int): Int = { |
函数体的最后一个表达式的值将作为函数的返回值, 并且通常情况下返回值类型可以自动推导, 但如果显式使用return或者递归则无法推导返回值类型, 此时需要明确指定返回值类型.
Lambda函数
Scala支持Lambda函数, 格式与Java类似
1 | > val sum = (x:Int, y:Int) => {x+y} |
Lambda函数可以作为其他函数的参数, 使用方式与大部分语言类似
1 | > val arrInt = Array(1,2,3,4) |
由于部分操作十分经典, 因此Scala提供了更简单的输入方式, 以上面的对所有元素执行+1操作为例, 可以进一步简化为
1 | // 集合是不可变的, 因此重复执行map操作结果是一致的 |
高阶函数
Scala支持高阶函数, 定义形式如下
1 | def higher(f: (Double) => Double) = f(100) |
高阶函数的另一个常见的用法是将函数作为返回值, 例如
1 | def higher2(factor:Int) = (x:Double) => factor * x |
注意: =
是分割函数声明和函数体的标志, 明确这一点有助于阅读复杂的高阶函数
函数柯里化
对于将函数作为返回值的高阶函数, 通常具有f(A)(B)
的调用形式, Scala可以直接定义柯里化函数实现上述形式的函数调用, 例如上一节的函数可以直接定义为
1 | def multiply(factor:Int)(x: Double) = factor * x |
此时虽然看起来与高阶函数有相同的调用形式, 但如果单独调用multiply(10)
会报错, 因为柯里化函数并不是高阶函数, 不能分步调用. 如果需要此特性, 可以使用部分应用函数, 格式如下
1 | val paf = multiply(10) _ |
对于普通函数, 也能够使用部分应用函数, 例如
1 | def product(x1:Int, x2:Int, x3:Int) = 100*x1 + 10*x2 + x3 |
通过函数柯里化和部分应用函数, 可以使得部分参数直接固定到函数之中, 从而提高函数的复用性, 减少重复输入. 柯里化的其他应用可以参考如下的一些资料
偏函数
偏函数是处理定义域子集的函数, 如果参数不在子集内, 则抛出异常, 这一特点通常与Scala的模式匹配结合. 偏函数的定义模式为
1 | val isEven: PartialFunction[Int, String] = { |
面向对象编程
1 | class Person { |
上述代码均可实现对成员变量的定义, 并且Scala会同时自动生成Scala风格的get和set方法. Scala风格的get与set使用方法类似于C#的属性, 在实现上, Scala定义了与成员变量同名的函数实现get功能, 重载了=
运算符实现了set功能, 例如
1 | var p = new Person("Scala",23) |
单例对象
Scala不支持Java中的静态类成员, 类似的功能需要使用单例对象实现, 单例对象使用object关键字定义. 具体形式如下
1 | object Student { |
包含main方法的单例对象也称为应用实例对象. 与Java一样, 此main函数可以作为程序的入口函数. 此外, 如果继承了App类, 也可以直接执行代码, 而不用定义main函数
1 | object AppTest extends App { |
伴生对象
Scala可以分别使用class关键字和object关键字定义同名的类和对象. 这组对象分别称为伴生类和伴生对象. 伴生类和伴生对象内部可以无视访问权限控制而直接访问私有字段.
伴生对象定义apply
方法可以是伴生类不使用new创建对象. 伴生对象定义unapply
方法可以在模式匹配中使伴生类实现构造函数匹配.
构造函数
在前面定义Person对象时, 如果只需要一个简单的数据结构, 那么只有函数声明部分即可实现全部功能, 如果需要重写其他内容, 则可以按照如下的方式重写
1 | class Person(val name:String, var age:Int) { |
这种定义类的方法称为 主构造函数. Scala支持默认参数和辅助构造函数. 无论这个类的名称如何, 辅助构造函数都定义为this(argA,argB)
的形式
Trait
Trait的含义是特质, 其基本作用是代替Java中的Interface, 例如
1 | trait Closeable { |
类实现Trait被称为混入, 如果只需要混入一个Trait, 则可以使用extends关键字, 如果需要混入多个Trait, 则可以使用with关键字, 例如
1 | class File(val name:String) extends java.io.File with Closeable with Cloneable { |
Trait可以像Java中的接口类一样使用, 不过Trait在使用上可以更加灵活, 可以有具体的成员变量和成员函数, 因此与Java中的抽象类更接近.
在Java中, 一个类只能继承一个类, 而Trait具有抽象类的特性, 同时Scala允许混入多个Trait, 因此在Scala中存在多继承问题.
模式匹配
模式匹配是Scala的一个重要的语言特性, 在开发过程中被广泛应用. 常见的元组解包就是模式匹配的一个应用. 模式匹配通常具有如下的结构
1 | for(i <- 1 to 5) { |
相比于Jave的switch语法, Scala的模式匹配能够支持更多不同的类型, 从而提高的这一语法的表达能力. 在前面的偏函数章节也可以看到, 模式匹配语法除了作为语句使用外, 也可以作为函数体.
模式匹配类型
上面演示了模式匹配对常量和变量的匹配格式, 下面介绍模式匹配的几种其他用法.
使用构造函数解构对象
1 | case class Dog(val name:String, var age:Int); |
序列匹配与元组匹配
1 | val arrInt = Array(1,2,3,4) |
1 | val tupleInt = (1,2,3,4) |
类型匹配
1 | def patternMatching(x:Any) = x match { |
变量绑定模式
变量绑定模式相当于将指定的变量绑定到任意的表达式
1 | val list = List(List(1,2,3,4), List(4,5,6,7,8,9)) |
上述模式匹配规则表明, e2需要匹配一个任意长度的List, 且第一个元素是数字4. e1需要匹配一个有两个元素的List, 且第二个元素需要时e2对应格式的List. 这种操作非常类似正则表达式中的变量捕获.
For循环
For循环支持所有格式的模式匹配, 例如
1 | for((language, e@"Spark) <- Map("Java" -> "Hadoop".length, "Closure" -> "Storm", "Scala" -> "Spark")) { |
类型参数
Scala的泛型与Java泛型基本上存在一一对应的关系, Java的泛型特性Scala都支持, 并且在Java泛型的基础上, Scala提供了更多特性.
特性 | Java泛型 | Scala泛型 |
---|---|---|
泛型 | List<String> |
Array[String] |
通配符 | List<?> |
Array[_] |
下界 | List<? extends String> |
Array[T <: String] |
上界 | List<? super String> |
Array[T >: String] |
协变 | <T extends U> |
[+T] |
逆变 | <T super U> |
[-T] |
最后更新: 2024年03月28日 23:43
版权声明:本文为原创文章,转载请注明出处
原始链接: https://lizec.top/2019/12/17/Scala%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/