数据类型:
Unit 表示无值,和其它语言中void相同
Null 空值或空引用
Nothing 所有类型的子类型,表示没有值
Any 所有其它类型的超类,任何实例都是Any类型
AnyRef 所有引用类型的超类
AnyVal 所有值类型的超类
var和val的区别:var是可变量,val是不可变量(相当于java中的final)。
val支持推导类型,即根据变量的初始值推导出变量的类型,但var必须显式声明
val线程安全,因为它不可变,不会引起结果的差异和竞态条件。
val更适合函数式编程
竞态条件(Race Condition)是指在多线程编程中,多个线程对共享资源的访问和操作没有正确同步导致的不确定结果。竞态条件可能会导致程序出现意外的行为,产生不一致的结果。
竞态条件发生的原因是多个线程并发执行时,它们对共享资源的访问没有进行适当的同步操作。这意味着多个线程可以以任意的顺序读取和写入共享资源,导致操作结果的不可预测性。
以下是一些常见的竞态条件情况:
-
读-修改-写(Read-Modify-Write)操作:多个线程同时读取同一个变量的值,并根据这个值进行修改后再写回。如果多个线程同时执行读取和写入操作,可能会导致最终结果不正确。
-
检查-执行(Check-Then-Act)操作:多个线程同时检查某个条件,并根据条件执行相应的操作。如果多个线程同时执行检查操作,可能导致条件的结果在执行操作之前被修改,从而产生错误的行为。
-
非原子操作:多个线程同时执行多个操作,这些操作需要以原子方式执行才能保证正确性。如果没有适当的同步措施,可能导致操作之间的交叉执行,产生不一致的结果。
为了避免竞态条件,需要使用适当的同步机制来保护共享资源的访问,例如使用锁(Lock)、互斥量(Mutex)、信号量(Semaphore)等。这些同步机制可以确保多个线程按照正确的顺序访问共享资源,避免竞态条件的发生。
在Java和Scala中,数值类型之间的转换关系如下:
- 从较小的类型向较大的类型转换是自动进行的,称为隐式类型转换。这是因为较小的类型可以容纳较大类型的值,不会导致数据丢失或溢出。
Byte
可以自动转换为Short
、Int
、Long
、Float
、Double
。Short
可以自动转换为Int
、Long
、Float
、Double
。Char
可以自动转换为Int
、Long
、Float
、Double
。Int
可以自动转换为Long
、Float
、Double
。Long
可以自动转换为Float
、Double
。Float
可以自动转换为Double
。
- 从较大的类型向较小的类型转换需要显式进行,称为显式类型转换或强制类型转换。这是因为较大的类型可能无法完全容纳较小类型的值,可能会导致数据丢失或溢出。
Double
需要显式转换为Float
、Long
、Int
、Short
、Byte
。Float
需要显式转换为Long
、Int
、Short
、Byte
。Long
需要显式转换为Int
、Short
、Byte
。Int
需要显式转换为Short
、Byte
。Short
需要显式转换为Byte
。
val intValue: Int = 1000
val byteValue: Byte = intValue.toByte
val doubleValue: Double = 3.14
val intValue: Int = doubleValue.toInt
隐式转换案例
通过定义一个隐式转换函数 intToString
,它接受一个 Int
类型的参数,并将其转换为 String
类型。然后,在主函数中将 intValue
赋值给 stringValue
变量时,由于没有直接的类型匹配,编译器会自动应用隐式类型转换函数将 intValue
隐式转换为 String
类型。
// 定义一个隐式转换函数
implicit def intToString(value: Int): String = value.toString
def main(args: Array[String]): Unit = {
val intValue: Int = 42
// 隐式类型转换:将 intValue 转换为 String 类型
val stringValue: String = intValue
println(stringValue)
}
if-else举例
def main(args: Array[String]): Unit = {
val x = 10
if (x > 5) {
println("x is greater than 5")
} else {
println("x is less than or equal to 5")
}
}
for循环
for(i <- 1 to 9;j <- 1 until 10){
}
scala中没有break和continue的实现,但可以通过breakable关键字和递归函数替代实现。
使用breakable{}代码块包裹需要使用break功能的代码块
import scala.util.control.Breaks._
var i = 0
breakable {
while (i < 10) {
if (i == 5) break // 使用 breakable 块中的 break 方法实现中断循环
println(i)
i += 1
}
}
递归
def printNumbers(n: Int): Unit = {
if (n < 1) return // 递归终止条件
if (n == 5) return // 使用 return 语句实现跳过当前迭代
println(n)
printNumbers(n - 1) // 递归调用
}
printNumbers(10)
方法的定义:Method
def fun(x int ,y int) :unit = {
}
def fun1 (a : Int , b : Int)= a+b
- 方法可以写返回值的类型也可以不写,会自动推断,有时候不能省略,必须写,比如在递归方法中或者方法的返回值是函数类型的时候。
- scala中方法有返回值时,可以写return,也可以不写return,会把方法中最后一行当做结果返回。当写return时,必须要写方法的返回值。
- 如果返回值可以一行搞定,可以将{}省略不写
- 传递给方法的参数可以在方法中使用,并且scala规定方法的传过来的参数为val的,不是var的。
- 如果去掉方法体前面的等号,那么这个方法返回类型必定是Unit的。这种说法无论方法体里面什么逻辑都成立,scala可以把任意类型转换为Unit.假设,里面的逻辑最后返回了一个string,那么这个返回值会被转换成Unit,并且值会被丢弃。
函数Function
定义一个函数
val f1=(x:Int,y:Int)=>x+y
调用: f1(1,2)
匿名函数
(x:Int,y:Int)=>x+y
函数和方法的区别:函数式独立的、可单独调用的代码块,方法是类对象中的一个成员,通过类的实例进行调用
函数式编程:
高阶函数:返回值是函数,或参数是函数,或同时都是的函数
返回值是函数的函数:
def f(a:int,b:int):(int,int)=>int ={
def f2(x:int,y:int):int={
x+y+a+b
}
f2
}
println(f(1,2)(3,4))
//函数的返回是函数
//1,2,3,4相加
def hightFun2(a : Int,b:Int) : (Int,Int)=>Int = {
def f2 (v1: Int,v2:Int) :Int = {
v1+v2+a+b
}
f2
}
println(hightFun2(1,2)(3,4))
这段代码定义了一个高阶函数 hightFun2
,它接受两个整型参数 a
和 b
,并返回一个函数 (Int, Int) => Int
。
函数 hightFun2
内部定义了一个局部函数 f2
,它接受两个整型参数 v1
和 v2
,并返回它们的和再加上外部参数 a
和 b
的值。这个局部函数 f2
捕获了外部函数 hightFun2
的参数 a
和 b
,因此在函数体内部可以直接访问这两个参数。
最后,函数 hightFun2
返回了局部函数 f2
。在打印语句中,我们通过 hightFun2(1, 2)
调用了 hightFun2
函数,并将返回的函数 (Int, Int) => Int
作为参数传递给了另一个函数调用 println
。然后,我们通过 (3, 4)
作为参数调用了这个返回的函数,得到结果 1 + 2 + 3 + 4 = 10
,并将结果打印出来。
因此,整体来说,这段代码的含义是定义了一个接受两个参数的高阶函数 hightFun2
,它返回一个函数,这个返回的函数接受两个参数,并返回这两个参数的和再加上外部传入的参数的值。然后,通过调用 hightFun2(1, 2)(3, 4)
,得到了最终的结果并打印出来。
def hightFun3(f : (Int ,Int) => Int) : (Int,Int) => Int = {
f
}
println(hightFun3(f)(100,200))
println(hightFun3((a,b) =>{a+b})(200,200))
//以上这句话还可以写成这样
//如果函数的参数在方法体中只使用了一次 那么可以写成_表示
println(hightFun3(_+_)(200,200))
这段代码定义了一个高阶函数 hightFun3
,它接受一个函数 f
,该函数的类型为 (Int, Int) => Int
,并返回一个函数 (Int, Int) => Int
。
在第一个打印语句中,我们调用了 hightFun3(f)
,将函数 f
作为参数传递给 hightFun3
,然后将返回的函数再次调用,并传入参数 (100, 200)
。由于函数 hightFun3
的实现中直接返回了参数函数 f
,因此第一个打印语句的结果就是调用函数 f
,即 f(100, 200)
。
在第二个打印语句中,我们调用了 hightFun3
,并传入了一个匿名函数 (a, b) => { a + b }
。这个匿名函数接受两个参数,并返回它们的和。因此,第二个打印语句的结果就是调用这个匿名函数,即 (a, b) => { a + b }(200, 200)
,最终结果为 400
。
注意点:{}中最后一行为默认返回值
匿名函数 中的参数如果只使用一次,可以 简写,即 (a,b)=>{a+b} 简化为 (_+_)
柯里化函数: 高阶函数调用的简化
将原来需要接受多个参数的函数转换成只要一个参数的函数过程,并且返回 一个函数(参数为 剩余的参数)。
scala柯里化风格的使用可以简化主函数的复杂度,提高主函数的自闭性,提高功能上的可扩张性、灵活性。可以编写出更加抽象,功能化和高效的函数式代码。
//柯理化
object KLH {
def main(args: Array[String]): Unit = {
def klh(x:Int)(y:Int) =x*y
val res=klh(3)(_)
println(res(4))
// KLH(3)(_)(4)
}
}
/**
* 柯里化函数
*/
def fun7(a :Int,b:Int)(c:Int,d:Int) = {
a+b+c+d
}
println(fun7(1,2)(3,4))
// val res = fun7(1,2)_
// val res1 = res(3,4)
接下来,使用 klh(3)(_)(4)
的方式调用柯里化函数。其中,第一个参数列表 klh(3)
接受参数 3
,并返回一个函数,这个函数需要一个参数 y
。而 _
表示占位符,代表后续需要传入的参数。
然后,将返回的函数赋值给变量 res
。最后,通过 res(4)
调用这个函数,将参数 4
传递给 res
,并输出结果。
因此,程序的输出结果为 12
,即 3 * 4
。
面向对象
scala和java在继承方面的区别:
1.java只能单继承,scala通过trait实现多重继承(一个类可以继承多个父类),即混入mixin。
2.trait,类似于java中的接口,比接口强大。定义一组方法的规范,但java在接口里不会写实现,而scala包含具体的实现。
3.构造方法,java中的是与类同名的特殊方法,scala中是对象中一个代码块。
4.方法重写:java使用@override注解,scala直接使用override关键字
单例对象
某个对象的实例在整个应用程序的生命周期内是唯一的,类似于java单例模式。使用Singleton Object创建只有一个实例的类。
object SingletonExample {
def sayHello(): Unit = {
println("Hello, I am a singleton object!")
}
}
SingletonExample.sayHello()
伴生对象
scala允许定义和class结构同名的object结构object称之为伴生对象class称之为伴生类当只有object对象时,我们也称这个对象为单例对象、孤立对象
集合
数组 使用 Array
类型表示,可变长度的有序集合,使用索引访问元素 val array = Array(1, 2, 3, 4)
list列表 使用 List
类型表示,不可变的有序集合,可以通过 ::
运算符构建新的列表。 val list = List(1, 2, 3, 4)
set 去重,不可变
/**
* 可变长Set
*/
import scala.collection.mutable.Set
val set = Set[Int](1,2,3,4,5)
set.add(100)
set.+=(200)
set.+=(1,210,300)
set.foreach(println)
map k-v类型的集合 val map = Map("a" -> 1, "b" -> 2, "c" -> 3)
元组 可以包含不同类型的元素,用()包裹
队列(Queue):使用 Queue
类型表示,可变的先进先出集合。
import scala.collection.mutable.Queue
val queue = Queue(1, 2, 3, 4)
可变集合在操作时可以修改其内容,而不可变集合的操作会返回一个新的集合,原集合保持不变。
scala集合常用的计算函数
sum
:计算集合中元素的总和。
val numbers = List(1, 2, 3, 4, 5) val total = numbers.sum println(total) // 输出:15
max
:返回集合中的最大值。
val numbers = List(1, 2, 3, 4, 5) val maxNum = numbers.max println(maxNum) // 输出:5
min
:返回集合中的最小值。
val numbers = List(1, 2, 3, 4, 5) val minNum = numbers.min println(minNum) // 输出:1
average
:计算集合中元素的平均值。
val numbers = List(1, 2, 3, 4, 5) val avg = numbers.sum.toDouble / numbers.length println(avg) // 输出:3.0
count
:统计满足特定条件的元素个数。
val numbers = List(1, 2, 3, 4, 5) val evenCount = numbers.count(_ % 2 == 0) println(evenCount) // 输出:2
filter
:过滤集合中满足特定条件的元素。
val numbers = List(1, 2, 3, 4, 5) val evenNumbers = numbers.filter(_ % 2 == 0) println(evenNumbers) // 输出:List(2, 4)
map
:对集合中的每个元素应用某个函数并返回新的集合。
val numbers = List(1, 2, 3, 4, 5) val doubledNumbers = numbers.map(_ * 2) println(doubledNumbers) // 输出:List(2, 4, 6, 8, 10)
模式匹配
case关键字,每个备选项都包含一个模式和一个或多个表达式 => 隔开了模式和表达式
1.可以匹配值和类型
2.从上到下匹配,匹配上了就不会再往下匹配
3. 都匹配不上时,会匹配case _ 相当于匹配默认值
4.match 的{} 可以去掉
def matchTest(x:Any) ={
x match {
case x:Int=> println("type is Int")
case 1 => println("result is 1")
case 2 => println("result is 2")
case 3=> println("result is 3")
case 4 => println("result is 4")
case x:String => println("type is String")
// case x :Double => println("type is Double")
case _ => println("no match")
}
}
}
match {
case a =>
case b =>
case c =>
}
偏函数:方法中没有match只有case
异常
- 将会发生异常的代码封装在
try
块中。在try
块之后使用了一个catch
处理程序来捕获异常。如果发生任何异常,catch
处理程序将处理它,程序将不会异常终止。 - Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“
checked
(编译期)”异常,即 Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。 - 异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。因此,在 catch 子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在 Scala 中也不会报错,但这样是非常不好的编程风格。
-
object Test_Exception { def main(args: Array[String]): Unit = { try{ val n =10 /0; }catch { case e: ArithmeticException =>{ println("发生算数异常") } case e: Exception => { println("发生一般异常") } }finally { println("处理结束") } } } object a { def main():Unit = { try{ } catch{ //模式匹配 case e : 异常处理类 => {} } finally{ } } }
隐式转换
类型匹配时,如果找不到合适的类型,会让编译器在作用范围内自动推断类型
隐式方法,即隐式转换函数
使用关键字 implicit修饰,当A对象调用一个方法时,发现A类里面没有这样的方法,但B类中存在这个方法,那么编译器就会在作用域里面找有没有可以将A类对象转换B类对象的隐式转换函数,如果有,就使用,A类就可以调用该方法。
class Animal(name:String){
def canFly(): Unit ={
println(s"$name can fly...")
}
}
class Rabbit(xname:String){
val name = xname
}
object Lesson_ImplicitFunction {
implicit def rabbitToAnimal(rabbit:Rabbit):Animal = {
new Animal(rabbit.name)
}
def main(args: Array[String]): Unit = {
val rabbit = new Rabbit("RABBIT")
rabbit.canFly()
隐式参数:方法的参数用implicit关键字修饰,必须用KLH的方式书写,写在后面的()中
隐式转换的作用是当调用方法时,不用传参,编译器会自动在作用域范围内搜寻并将隐式参数传入
隐式类:用implicit修饰的类。当A变量没有某个方法,可以在定义一个隐式类,在里面定义方法,然后隐式类的方法传给A去实现
隐式类注意:
1).隐式类必须定义在类,包对象,伴生对象中。
2).隐式类的构造必须只有一个参数,同一个类,包对象,伴生对象中不能出现同类型构造的隐式类。
隐式转换机制即隐式转换函数或类和隐式参数
泛型:通用的类、方法、函数,可以接受各种类型的参数,提高代码灵活性。
class Box[T](value: T) {
def getValue: T = value
}
val boxInt = new Box[Int](42)
val boxString = new Box[String]("Hello")
val intValue: Int = boxInt.getValue
val stringValue: String = boxString.getValue