Scala
概述
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的 静态类型编程语言(静态语言需要提前编译的如:Java、c、c++等,动态语言如:js)。
- Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。(多范式,就是多种编程方 法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
- Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有 的Java类库,实现两种语言的无缝对接。
- Scala单作为一门语言来看,非常的简洁高效。
变量和数据结构
注释
Scala 注释使用和 Java 完全一样。
单行注释://
多行注释:/**/
文档注释:/*
*
*/
变量和常量
常量:用val声明 val 常量名 [:常量类型]=初始值
变量:用var声明 var 变量名 [:变量类型]=初始值
注意:
- 能用常量的地方不用变量
- 声明变量时,类型可以省略,编译器自动推导,即类型推导
- 类型确定后,就不能修改,说明 Scala 是强数据类型语言。
- 变量声明时,必须要有初始值
- 在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变, val 修饰的变量不可改。
- var 修饰的对象引用可以改变,val 修饰的对象则不可改变,但对象的状态(值) 却是可以改变的。
数据类型
- Scala中一切数据都是对象,都是Any的子类。
- Scala中数据类型分为两大类:数值类型(AnyVal)、 引用类型(AnyRef),不管是值类型还是引用类型都是 对象。
- Scala数据类型仍然遵守,低精度的值类型向高精 度值类型,自动转换(隐式转换)
- Scala中的StringOps是对Java中的String增强
- Unit:对应Java中的void,用于方法返回值的位置,表 示方法没有返回值。Unit是 一个数据类型,只有一个对象 就是()。Void不是数据类型,只是一个关键字
- Null是一个类型,只 有一个对 象就 是null。它是 所有引用类型(AnyRef)的子类。
- Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使 用
整数类型
浮点类型
字符类型
字符类型可以表示单个字符,字符类型是 Char。
布尔类型
布尔类型也叫 Boolean 类型,Booolean 类型数据只允许取值 true 和 false。Boolean 类型占 1 个字节。
Unit 类型、Null 类型和 Nothing 类型
- Unit 类型用来标识过程,也就是没有明确返回值的函数。
- Null 类只有一个实例对象,Null 类似于 Java 中的 null 引用。Null 可以赋值给任 意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
- Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方 法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼 容。
类型转换
自动转换(隐士转换):精度小的自动向精度大的转换
强制类型转换:调用toXxx()方法
运算符
算术运算符
对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整 数部分而舍弃小数部分。
对一个数取模 a%b,和 Java 的取模规则一样。
关系运算符
==与equals
java: ==比较两个变量本身的值,即两个对象在内存中的首地址;equals 比较字符串中所包含的内容是否相同。
scala:==更加类似于 Java 中的 equals
逻辑运算符
赋值运算符
位运算符
流程控制
分支控制
if(条件表达式){
执行代码块
}[else if(条件表达式){
执行代码块
}][else{
执行代码块
}]
object s03 {
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("老年")
}
}
}
Scala 中 if else 表达式其实是有返回值的,具体返回值取决于满足条件的 代码体的最后一行内容。
val res:String=if(age<18){
"童年"
}else if(age>=18&&age<30){
"中年"
}else{
"老年"
}
println(res)
Scala 中返回值类型不一致,取它们共同的祖先类型。
val res:Any=if(age<18){
"童年"
}else if(age>=18&&age<30){
"中年"
}else{
100
}
println(res)
for循环
Scala 也为 for 循环这一常见的控制结构提供了非常多的特性,这些 for 循环的特性被称 为 for 推导式或 for 表达式。
for(变量<-a to b by c){//[a,b] 步长为 c
todo
}
for(变量 <-a until b by c){//[a,b) 步长c
todo
}
循环守护
循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为 true 则进入循环 体内部,为 false 则跳过,类似于 continue。
for(变量 <-a to b if 关于变量的条件语句){
todo
}
循环返回值
val res = for(i <- 1 to 10) yield i
println(res)
将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。
while与do…while
与java相同
函数式编程
Scala 语言是一个完全面向对象编程语言。万物皆对象
对象的本质:对数据和行为的一个封装
Scala 语言是一个完全函数式编程语言。万物皆函数。
函数的本质:函数可以当做一个值进行传递
函数和方法的区别
方法有访问权限,函数没有访问权限
为完成某一功能的程序语句的集合,称为函数。
类中的函数称之方法。
函数没有重载和重写的概念;方法可以进行重载和重写
Scala 中函数可以嵌套定义
函数至简原则
函数至简原则:能省则省
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)。如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
高级函数
- 函数可以作为值进行传递
- 函数可以作为参数进行传递
- 函数可以作为函数返回值返回
匿名函数
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
传递匿名函数至简原则:
- 参数的类型可以省略,会根据形参进行自动的推导
- 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参 数超过 1 的永远不能省略圆括号。
- 匿名函数如果只有一行,则大括号也可以省略
- 如果参数只出现一次,则参数省略且后面参数可以用_代替
闭包
如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的 环境,称为闭包。
object 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))
}
}
函数柯里化
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
惰性加载
当函数被调用是先执行被调函数里的语句。
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函 数才会执行。这种函数我们称之为惰性函数。
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)//只有res调用sum函数才执行
}
def sum(n1: Int, n2: Int): Int = {
println("sum 被执行。。。")//只有res调用该语句才输出
return n1 + n2
}
azy 不能修饰 var 类型的变量
面向对象
[修饰符]class 类名{
类体
}
Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是 public)
一个scala源文件可以包含多个类
创建对象
val | var 对象名[:类型]=new 类型()
val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
var 修饰对象,可以修改对象的引用和修改对象的属性值
自动推导变量类型不能多态,所以多态需要显示声明
构造器
Scala 类的构造器包括:主构造器和辅助构造器
class 类名(形参列表){//主构造器
def this(形参列表){//辅助构造器
}
}
辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型 来区分。
辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
构造器参数
Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰
- 未用任何修饰符修饰,这个参数就是一个局部变量 。
- var 修饰参数,作为类的成员属性使用,可以修改 。
- val 修饰参数,作为类只读属性使用,不能修改
继承
class Person(nameParam: String) {
var name = nameParam
var age: Int = _
def this(nameParam: String, ageParam: Int) {
this(nameParam)
this.age = ageParam
println("父类辅助构造器")
}
println("父类主构造器")
}
class Emp(nameParam: String, ageParam: Int) extends
Person(nameParam, ageParam) {
var empNo: Int = _
def this(nameParam: String, ageParam: Int, empNoParam: Int) {
this(nameParam, ageParam)
this.empNo = empNoParam
println("子类的辅助构造器")
}
println("子类主构造器")
}
object Test {
def main(args: Array[String]): Unit = {
new Emp("z3", 11,1001)
}
}
特质
Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同 的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可 以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。
Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种 补充。
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素, 所以在使用时,也采用了 extends 关键字,如果有多个特质或存在父类,那么需要采用 with 关键字连接。
没有父类:class 类名 extends 特质 1 with 特质 2 with 特质 3 …
有父类:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
说明 :
- 类和特质的关系:使用继承的关系。
- 当一个类去继承特质时,第一个连接词是 extends,后面是 with。
- 如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。
特质叠加
由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入 的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。 冲突分为以下两种:
第一种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且 两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
第二种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且 两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala 采用了特质叠加的策略。
所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来
trait Ball {
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.describe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.describe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"my ball is a " + super.describe()
}
}
object TestTrait {
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
//结果:my ball is a blue-foot-ball
特质叠加执行顺序
上述案例中的 super.describe()调用的是父 trait 中的方法吗?
案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质, 即,MyClass 中的 super 指代 Color,Color 中的 super 指代 Category,Category 中的 super 指代 Ball。
如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如 super[Category].describe()。