Scala

发布于:2025-04-02 ⋅ 阅读:(25) ⋅ 点赞:(0)

Scala 和 Java 的 Lambda 表达式在概念上有相似之处,Scala 的 Lambda 和函数式编程能力比 Java 更早、更彻底,语法更灵活,且不依赖接口。

特性 Java Lambda Scala Lambda
语法 -> 符号,需绑定接口 => 符号,直接作为函数值
类型推断 有限,需上下文信息 强大,通常无需显式声明
函数式支持 基于接口的 SAM(单一抽象方法) 原生函数类型,支持高阶函数
灵活性 受限,需符合接口定义 极高,支持柯里化、模式匹配等

变量
Scala中的变量分为两种:可变 var 和 不可变 val
可变var:可以随时修改var声明的变量的值
不可变val:val声明的变量,值不能被修改,否则会报错: error: reassignment to val 

在实际工作中,针对一些不需要改变值的变量,通常建议使用val,这样可以不用担心值被错误的修改(等于java中的final类型)。这样可以提高系统的稳定性和健壮性!

无论声明val变量,还是声明var变量,都可以手动指定变量的类型.
如果不指定,Scala会自动根据值,进行类型推断
val c = 1 等价于 val c: Int = 1

数据类型
Scala中的数据类型可以分为两种,基本数据类型和增强版数据类型 .

基本数据类型有: Byte、Char、Short、Int、Long、Float、Double、Boolean
增强版数据类型有: StringOps、RichInt、RichDouble、RichChar 等

操作符
Scala的算术操作符与Java的算术操作符没有什么区别。
比如 +、-、*、/、% 等,以及 &、|、^、>>、<< 等。注意:Scala中没有提供++、–操作符

if 表达式
在Scala中,if表达式是有返回值的,就是if或者else中最后一行语句返回的值,这一点和java中的if是不一样的,java中的if表达式是没有返回值的 。而if和else子句的值的类型可能还不一样,此时if表达式的值Scala会自动进行推断,取两个类型的公共父类型。如果if后面没有跟else,则默认else的值是Unit,也可以用()表示,类似于java中的void或者null。

for

scala> :paste
// Entering paste mode (ctrl-D to finish)
val n = 10
for(i <- 1 to n)
println(i)

scala> :paste
// Entering paste mode (ctrl-D to finish)
val n = 10
for(i <- 1 until 10)

println(i)

1 to 10 可以获取1~10之间的所有数字

1 until 10可以获取1~9之间的所有数字

to 和 until 其实都是函数,一个是闭区间,一个是开区间

scala> for(c <- "hello scala") println(c)

字符串操作

在这里for循环后面没有使用花括号,都省略了,因为for循环的循环体代码就只有一行

高级for

if守卫
if守卫模式,假设我们想要获取1~10之间的所有偶数,使用普通的for循环,需要把每一个数字都循环出来,然后判断是否是偶数。如果在for循环里面使用if守卫,可以在循环的时候就执行一定的逻辑,判断数值是否是偶数。

scala> for(i <- 1 to 10 if i % 2 == 0) println(i)

yield  类似于 Collector.toList

我们在使用for循环迭代数字的时候,可以使用yield指定一个规则,对迭代出来的数字进行处理,并且创建一个新的集合

scala> for(i <- 1 to 10) yield i *2

res16: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12,14,16,18,20)

while

类似Java的while

集合体系 类似Java集合

可变集合:在 scala.collection.mutable 这个包下面
不可变集合:在 scala.collection.immutable 这个包下面
在创建集合的时候,如果不指定具体的包名,默认会使用不可变集合。

Set:不需要new 关键字

scala> val s = scala.collection.mutable.SortedSet[String]()s: scala.collection.mutable.SortedSet[String] = TreeSet()
scala> s +=("c")
res45: s.type = TreeSet(c)
scala> s +=("a")
res46: s.type = TreeSet(a, c)
scala> s +=("b")
res47: s.type = TreeSet(a, b, c)

 List:不需要new 关键字

scala> val lb = scala.collection.mutable.ListBuffer[Int]()

lb: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
scala> lb +=1
res56: lb.type = ListBuffer(1)
scala> lb +=2
res57: lb.type = ListBuffer(1, 2)
scala> lb +=5
res58: lb.type = ListBuffer(1, 2, 5)
scala> lb -=5
res59: lb.type = ListBuffer(1, 2)

#head:表示获取List中的第一个元素
#tail:表示获取List中第一个元素之后的所有元素

#::  通过 :: 操作符,可以将head和tail的结果合并成一个List

scala> l.head :: l.tail

res52: List[Int] = List(1, 2)

Map

scala> val ages = scala.collection.mutable.Map(("jack",30),("tom",25))

scala.collection.mutable.Map[String,Int] = Map(tom-> 25, jack -> 30)
scala> val age = ages("jack")age: Int = 30
scala> val age = ages("jack1")java.util.NoSuchElementException: key not found: jack1 

scala> val age = ages.getOrElse("jack1", 0)

age: Int = 0

Array
Scala中Array的含义与Java中的数组类似,长度不可变
由于Scala和Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上就是Java数组,数组初始化后,长度就固定下来了,而且元素全部根据其类型进行初始化。

ArrayBuffer
Scala中ArrayBuffer与Java中的ArrayList类似,长度可变,ArrayBuffer:添加元素、移除元素

Tuple
Tuple:称之为元组,它与Array类似,都是不可变的,但与数组不同的是元组可以包含不同类型的元素,Tuple中的元素角标从 1 开始
注意:目前 Scala 支持的元组最大长度为 22 ,对于更大长度可以使用集合或数组。

scala> val t = (1, 3.14, "hehe")

t: (Int, Double, String) = (1,3.14,hehe)
scala> t._1
res117: Int = 1
scala> t._3
res118: String = hehe

集合总结
很多集合体系中的数据结构,有的是可变的,有的是不可变的,有的是既是可变的又是不可变
的,听起来有点乱,在这里总结一下
可变集合: LinkedHashSet、ListBuffer、ArrayBuffer、LinkedHashMap
不可变集合: List、SortedMap
可变+不可变集合: Set、HashSet、SortedSet、Map、HashMap

还有两个编外人员:
Array、Tuple
Array:长度不可变,里面的元素可变
Tuple:长度不可变,里面的元素也不可变 

 函数

在Scala中定义函数需要使用 def 关键字,函数包括函数名、参数、函数体。
Scala要求必须给出函数所有参数的类型,但是函数返回值的类型不是必须的,因为Scala可以自己根据函数体中的表达式推断出返回值类型。
函数中最后一行代码的返回值就是整个函数的返回值,不需要使用return,这一点与Java不同,java中函数的返回值是必须要使用return的。

scala> :paste
// Entering paste mode (ctrl-D to finish)
def sayHello(name: String, age: Int) = {

        println("My name is "+name+",age is "+age)

        age
}
// Exiting paste mode, now interpreting.
sayHello: (name: String, age: Int)Int
scala> sayHello("Scala",18)

My name is Scala,age is 18

res120: Int = 18

默认参数

scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")

sayHello: (fName: String, mName: String, lName: String)String
scala> sayHello("zhang","san")

res122: String = zhang san last 

带名参数
在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。

scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")

sayHello: (fName: String, mName: String, lName: String)String
scala> sayHello(fName = "Mick", lName = "Tom", mName = "Jack")

res127: String = Mick Jack Tom 

可变参数
在Scala中,有时我们需要将函数定义为参数个数可变的形式,可以使用变长参数来定义函数。

 scala> :paste
// Entering paste mode (ctrl-D to finish)
def sum(nums: Int*) = {

        var res = 0 for (num <- nums) res += num

        res
}
// Exiting paste mode, now interpreting.
sum: (nums: Int*)Int
scala> sum(1,2,3,4,5)res129: Int = 15

特殊的函数-过程
在Scala中,定义函数时,如果函数体直接在花括号里面而没有使用=连接,则函数的返回值类型就是Unit,这样的函数称之为过程,过程通常用于不需要返回值的函数
过程还有一种写法,就是将函数的返回值类型显式定义为Unit 。

区别:

// 函数

def sayHello(name: String) = "Hello, " + name

def sayHello(name: String): String = "Hello, " + name

// 过程

def sayHello(name: String) { "Hello, " + name }

def sayHello(name: String): Unit = "Hello, " + name

 lazy
Scala提供了lazy特性,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算,这种特性对于特别耗时的操作特别有用,比如打开文件这个操作。

scala> import scala.io.Source._import scala.io.Source._

// 即使D://test.txt文件不存在,代码也不会报错,只有变量使用时才会报错
scala> lazy val lines = fromFile("D://test.txt").mkString

lines: String = <lazy>

Scala面向对象编程
主要学习Scala中的类、对象和接口注意:

Scala中类和java中的类基本是类似的
Scala中的对象时需要定义的,而java中的对象是通过class new出来的
Scala中的接口是trait,java中的接口是interface

类:

class Person{
 var name = "scala"
 def sayHello(){
 println("Hello,"+name)
 }
 def getName= name
}
Scala 类中的构造函数可以分为主构造函数和辅助构造函数
constructor :类似 Java 的默认构造函数 this()
辅助 constructor :类似 Java 的重载构造函数 this(name,age)
对象 -object
Java 中也有 object ,通过 class 就可以创建 object ,在scala 中,我们可以直接定义一个 object ,就像定义 class 一样。
object :相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method。
object 不能定义带参数的 constructor ,只有空参的 constructor,第一次调用object 的方法时,会执行 object constructor ,也就是执行 object 内部不在任何方法中的代码,因为它只有空参的构造函数。object的 constructor 的代码只会在他第一次被调用时执行一次,以后再次调用就不会再执行
了。
object 通常用于作为单例模式的实现,或者放 class 的一些静态成员,比如工具方法
object 可以直接使用,不能 new
创建一个object ,使用 object 关键字。
object Person {
 var age = 1
 println("this Person object!")
 def getAge = age 
}
执行,直接通过 Object 的名称调用属性或者方法即可,类似于 Java 中的静态类

伴生对象

如果有一个 class ,还有一个与 class 同名的 object ,那么就称这个 object class 的 伴生对象 , class 是 object的 伴生类。
        ps:   伴生类和伴生对象必须存放在一个.scala 文件之中。
                伴生类和伴生对象最大特点在于可以互相访问private field。
object Person {
 private val fdNum= 1
 def getFdNum = fdNum
}
class Person(val name: String, val age: Int) {
 def sayHello = println("Hi, " + name + ",you are " + age + " years old!"  
}

apply
apply object 中非常重要的一个特殊方法,通常在伴生对象中实现 apply 方法,并在其中实现构造伴生类对象的功能。
在创建对象的时候,就不需要使用 new Class 的方式,而是使用 Class() 的方式,隐式调用伴生对象的 apply方法,这样会让对象创建更加简洁。
例如: Array 的伴生对象的 apply 方法就实现了接收可变数量的参数,以及会创建一个 Array 对象
val a = Array(1, 2, 3, 4, 5)。

 Array object的源码中可以看出来,它里面就是在apply方法内部使用new Array创建的对象

定义一个伴生类和伴生对象
class Person(val name: String){
 println("my name is,"+name)
}
object Person {
 def apply(name: String) = {
 println("apply exec...")
 new Person(name)
 }
}

 

ps:在这里 new Person(“zhang”) 等于 Person(“zhang”) ,都是获取 Person 的对象
只不过 Person(“zhang”) 是用的 object apply 方法
new Person(“zhang”) 是直接基于 class 创建的
main 方法
package com.demo
/**
 * Created by xuwei
 */
object mainDemo {
 def main(args: Array[String]): Unit = {
 println("hello scala!")
}
接口 -trait
Scala 中的接口称为 trait trait 类似于 Java 中的 interface
triat 中可以定义抽象方法
类可以使用 extends 关键字继承 trait ,无论继承类还是 trait 统一都是使用 extends 这个关键字
类继承 trait 后,必须实现 trait 中的抽象方法,实现时不需要使用 override 关键字
scala 不支持对类进行多继承,但是支持对 trait 进行多重继承,使用 with 关键字即可
/**
 * Created by xuwei
 */
object PersonDemo {
 def main(args: Array[String]): Unit = {
 val p1 = new Person("tom")
 val p2 = new Person("jack")
 p1.sayHello(p2.name)
 p1.makeFriends(p2)
 }
}
trait HelloTrait { def sayHello(name: String)}
trait MakeFriendsTrait { def makeFriends(p: Person)}
class Person(val name: String) extends HelloTrait with MakeFriendsTrait {
 def sayHello(name: String) = println("Hello, " + name)
 def makeFriends(p: Person) = println("Hello, my name is " + name + ", your 
}
函数赋值给变量
Scala 中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量
Scala 的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线
高阶函数
由于函数是一等公民,所以说我们可以直接将某个函数作为参数传入其它函数。
这个功能是极其强大的,也是 Java 这种面向对象的编程语言所不具备的,这个功能在实际工作中是经常需要用到的。
        接收其它函数作为当前函数的参数,当前这个函数也被称作高阶函数 (higher-order function)
//先定义一个匿名函数,赋值给变量sayHelloFunc
val sayHelloFunc = (name: String) => println("Hello, " + name)

//再定义一个高阶函数,这个高阶函数的参数会接收一个函数
//参数: (String) => Unit 表示这个函数接收一个字符串,没有返回值
def greeting(func: (String) => Unit, name: String) { func(name) }

直接把匿名函数的定义传过来也是可以

高阶函数可以自动推断出它里面函数的参数类型,对于只有一个参数的函数,还可以省去小括号
# 先定义一个高阶函数
def greeting(func: (String) => Unit, name: String) { func(name) }
# 使用高阶函数:完整写法
greeting((name: String) => println("Hello, " + name), "scala")
# 使用高阶函数:高阶函数可以自动推断出参数类型,而不需要写明类型
greeting((name) => println("Hello, " + name), "scala")
# 使用高阶函数:对于只有一个参数的函数,还可以省去其小括号
greeting(name => println("Hello, " + name), "scala")
常用高阶函数
map :对传入的每个元素都进行处理,返回一个元素
flatMap :对传入的每个元素都进行处理,返回一个或者多个元素
foreach :对传入的每个元素都进行处理,但是没有返回值
filter :对传入的每个元素都进行条件判断,如果返回 true ,则保留该元素,否则过滤掉该元素
reduceLeft :从左侧元素开始,进行 reduce 操作
模式匹配
模式匹配,其实类似于 Java 中的 switch case 语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理。
def demo1(day: Int) {
 day match {
 case 1 => println("Monday")
 case 2 => println("Tuesday")
 case 3 => println("Wednesday")
 case _ => println("none")
 }
}
可以直接匹配类型,而不是值
import java.io._
def processException(e: Exception) {
 e match {
 case e1: IllegalArgumentException => println("IllegalArgumentException " 
 case e2: FileNotFoundException => println("FileNotFoundException " + e2)
 case e3: IOException => println("IOException " + e3)
 case _: Exception => println("Exception " )
 }
}

// try - catch 应用
try { 
 val lines02 = scala.io.Source.fromFile("D://test02.txt").mkString
} catch { 
 case ex: FileNotFoundException => println("no file")
 case ex: IOException => println("io exception")
 case ex: Exception => println("exception")
}
case class 与模式匹配
Scala 中提供了一种特殊的类,用 case class 进行声明,中文可以称为样例类
case class 其实有点类似于 Java 中的 JavaBean 的概念
即只定义 field ,会由 Scala 在编译时自动提供 get set 方法,但是没有其它的 method
case class 的主构造函数接收的参数通常不需要使用 var val 修饰, Scala 自动就会使用 val 修饰(但是如果你自己使用var 修饰,那么还是会按照 var 来,在这用哪个区别都不大)
Scala 自动为 case class 定义了伴生对象,也就是 object ,并且定义了 apply() 方法,该方法接收主构造函数中相同的参数,并返回 case class 对象
class Person
case class Teacher(name: String, sub: String) extends Person
case class Student(name: String, cla: String) extends Person

def check(p: Person) {
 p match {
 case Teacher(name, sub) => println("Teacher, name is " + name + ", sub is 
 case Student(name, cla) => println("Student, name is " + name + ", cla is 
 case _ => println("none")
 } 
}

Option 与模式匹配
Scala 有一种特殊的数据类型,叫做 Option
Option 有两种值,一种是 Some ,表示有值,一种是 None ,表示没有值
Option 通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比 null 来的更加简洁明了
val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
def getAge(name: String) {
 val age = ages.get(name)
 age match {
 case Some(age) => println("your age is " + age)
 case None => println("none")
 }
}

隐式转换
Scala 的隐式转换,允许手动指定将某种类型的对象转换成其它类型的对象
Scala 的隐式转换,最核心的就是定义隐式转换函数,即 implicit conversion function
隐式转换函数与普通函数唯一的语法区别是要以 implicit 开头而且最好要定义函数返回类型
隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,我们可以为某个普通类定义一个加强类,并定义对应的隐式转换函数,这样我们在使用加强类里面的方法的时候,Scala 会自动进行隐式转换,把普通类转换为加强类,然后再调用加强类中的方法
Scala 默认会自动使用两种隐式转换
如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用 import 引入对应的隐式转换函数
通常建议,仅仅在需要进行隐式转换的地方,比如某个函数内,用 import 导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换
class cat(val name: String){
 def catchMouse(){println(name+" catch mouse")}
}
class dog(val name: String)
implicit def object2Cat (obj: Object): cat = {
 if (obj.getClass == classOf[dog]) { 
 val dog = obj.asInstanceOf[dog]
 new cat(dog.name)
 }
 else Nil
}

在工作中一般很少需要自己去定义隐式转换函数,大部分的场景是只需要使用 import 导入对应的隐式转换函数就可以了,自己手工实现了一个隐私转换函数,因为他们都在一个作用域内,所以就不需要import了。

 


网站公告

今日签到

点亮在社区的每一天
去签到