1、scala语⾔集成⾯向对象和函数式编程
2、函数式编程是⼀种典范,将电脑的运算视作是函数的运算
3、与过程化编程相⽐,函数式编程⾥的函数计算可以随时调⽤,函数式编程中,函数是⼀等公民
1、定义:你可以在任何作⽤域内定义函数:包,类甚⾄是另⼀个函数或⽅法。在函数体内,可以访问到相应作⽤域内地任何变量。(重点)函数可以在变量不再处于作⽤域内时被调⽤。
例如:
1 def mulBy(factor:Double) = (x:Double) => factor * x
2 //开始调⽤
3 val tripe = mulBy(3)
4 val half = mulBy(0.5)
5 println(tripe(14) + " " + half(14))
这就是一个闭包
定义:柯⾥化指的是将原来接受两个参数的函数变成新的接受⼀个参数的函数的过程。新的函数返回⼀个以原有的第⼆个参数作为参数的函数
例如:
1 def mul(x:Int,y:Int) = x * y //该函数接受两个参数
2 def mulOneAtTime(x:Int) = (y:Int) => x * y //该函数接受⼀一个参数⽣生成另外⼀一个接受单个参数的函数
3 这样的话,如果需要计算两个数的乘积的话只需要调⽤用:
4 mulOneAtTime(5)(4)
scala的模式匹配包括了了一系列的备选项,每个替代项以关键字⼤小写为单位,每个替代方案包括一个模式或多个表达式,如果匹配将会进行计算,箭头符号=>将模式与表达式分离
例如:
1 obj match{
2 case 1 => "one"
3 case 2 => "two"
4 case 3 => "three"
5 case _ => default
6 }
是一个样本类,样本类是一种不可变切可分解类的语法糖,也就是说在构建的时候会自动生成一些语法糖,具有以下几个特点:
1、自动添加与类名一致的构造函数(也就是伴生对象,通过apply方法实现),也就是说在构造对象的时候不需要使用new关键字
2、样本类中的参数默认是val关键字,不可以修改
3、默认实现了toString,equals,hashcode,copy方法
4、样本类可以通过==来比较两个对象,不在构造方法内地属性不会用在比较上
class是⼀个类
class在构造对象的时候需要使⽤new关键字才可以。
隐式转换(implicit conversion)是指在 Scala 编程中,可以定义一些隐式的方法或函数,使得编译器在需要某种类型的实例时,自动地将另外一种类型的实例进行转换。这种转换过程是在编译期间完成的,因此也称为编译期间隐式转换(implicit conversion)。
隐式转换的主要作用是增强 Scala 的表达能力和扩展语言的功能。通过定义一些隐式转换,我们可以让编译器自动地将一些常见的类型转换或者操作转换成我们期望的结果,从而让代码更加简洁和易于理解。例如,在 Scala 中,我们可以通过隐式转换来实现类型的自动转换,将一个字符串转换成整数,将一个整数转换成浮点数,等等。
隐式转换的具体实现方式是通过定义隐式转换函数或者隐式类来实现的。隐式转换函数是一个接收一个参数并返回另外一个类型的函数,可以用来将一个类型隐式地转换成另外一个类型。隐式类是一个带有隐式关键字的类,用来扩展现有类的功能。当编译器发现类型不匹配时,会自动地查找可用的隐式转换函数或者隐式类来进行类型转换。
需要注意的是,隐式转换的滥用可能会导致代码难以理解和维护。因此,在使用隐式转换时,需要遵循一定的规范和准则,避免出现意料之外的结果。
假设我们有一个 Point 类型,用来表示平面上的一个点,它有两个 Double 类型的属性 x 和 y。现在我们想要给 Point 类型增加一些扩展方法,比如计算两个点之间的距离。我们可以使用隐式转换来实现这个功能。
首先,我们定义一个 Double 类型的隐式类 DistanceOps,用来扩展 Double 类型的功能:
implicit class DistanceOps(d: Double) {
def distanceTo(other: Point): Double =
math.sqrt((d - other.x) * (d - other.x) + (d - other.y) * (d - other.y))
}
然后,我们在 Point 类型中定义一个 distanceTo 方法,用来计算两个点之间的距离:
case class Point(x: Double, y: Double) {
def distanceTo(other: Point): Double =
math.sqrt((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y))
}
最后,我们就可以使用 Point 类型的 distanceTo 方法来计算两个点之间的距离了。由于我们已经定义了一个 Double 类型的隐式类 DistanceOps,编译器会自动地将 Double 类型的值进行隐式转换,从而让我们可以使用 distanceTo 方法:
val p1 = Point(0, 0)
val p2 = Point(1, 1)
val distance = p1.distanceTo(p2) // 相当于 (p1.x, p1.y) 到 (p2.x, p2.y) 的距离
println(distance) // 输出 1.4142135623730951
通过这个例子,我们可以看到隐式转换的作用:通过定义隐式类和扩展方法,让我们可以自然地扩展现有类型的功能,从而让代码更加简洁和易于理解。
在 Scala 中,每个类都可以有一个同名的伴生对象(companion object),用于存放静态方法和属性,或者说是类级别的方法和属性。伴生对象的名称与类的名称相同,但它们的定义使用了 object 关键字而不是 class 关键字。
相对应的,伴生对象和伴生类之间是有关联的。具体来说,一个类和它的伴生对象必须在同一个源文件中定义,并且它们的名称必须相同。
伴生类和伴生对象之间可以互相访问对方的私有成员。也就是说,伴生对象可以访问伴生类的私有成员,伴生类也可以访问伴生对象的私有成员。
下面是一个示例,展示了如何定义伴生类和伴生对象:
scala
Copy code
class MyClass(val name: String) {
def hello(): Unit = println(s"Hello, $name!")
}
object MyClass {
def create(name: String): MyClass = new MyClass(name)
}
在上面的代码中,我们定义了一个名为 MyClass 的类,它有一个名为 name 的属性和一个名为 hello 的方法。我们同时也定义了一个同名的伴生对象 MyClass,它有一个名为 create 的静态方法,用于创建 MyClass 的实例。
使用伴生对象可以让我们方便地创建和操作类级别的属性和方法,从而使代码更加简洁和易于理解。
1、变量声明:
scala:只需要申明是val或是var,具体的类型(比如String,Int,Double等等)由编译器⾃行推断
java: 需要在变量前⾯先注明变量的类型
2、返回值:
scala:申明返回值是在后⾯,并且不需要return语句,⾮要用,也不是不可以
java: 如果有返回值,需要return语句
3、结束符
scala:不需要使用分号作为结束符
java: 每个语句结束后需要分号作为结束符
4、循环
scala:循环语句可以⽤于守卫
java: 不可以这么写
5、通配符:
scala:_
java: *
6、构造器
scala:构造器名称为this,scala的辅助构造器之前需要有一个主构造器或者其他辅助构造器,并且scala的构造器参数可以直接放在类的后⾯
java: 构造器名称需要与类名称一样
7、内部类
scala:scala实例化的内部类是不同的,可以使⽤类型投影,例如 Network#Person表示Network的Person类
8、接⼝
java:内部类从属于外部类
scala:scala中接口称为特质(trait),特质中是可以写抽象方法,也可以写具体的方法体以及状态。且类是可以实现多个特质的。特质中未被实现的⽅方法默认就是抽象的⼦子类的实现或继承统一使⽤用的事extends关键字,如果需要实现或继承多个使⽤用with关键字特质中可以有构造器特质可以继承普通的类,并且这个类称为所有继承trait的父类
9、赋值
java: java中的接口(interface),接口中的方法只能是抽象方法,不可以写具体包含方法体的方法接口中不能有抽象的属性,且属性的修饰符都是public static final
类实现接口需要使用implements关键字,实现多个接口,需要用逗号隔开接口中不可以有构造器
接口不可以继承普通的类
scala:scala中的赋值语句返回结果是unit的不可以串联,例如x=y=1,这样是有问题的,x并没有被赋值为 java: x=y=1,这样是没问题的
1. 正常得递归,每一次递归步骤,需要保存信息到堆栈中去,当递归步骤很多的时候,就会导致内存溢出
2. 尾递归,就是为了解决上述的问题,在尾递归中所有的计算都是在递归之前调用,编译器可以利⽤这个属性避免堆栈错误,尾递归的调用可以使信息不插⼊堆栈,从⽽优化尾递归
例如:
5 + sum(4) // 暂停计算 => 需要添加信息到堆栈
5 + (4 + sum(3))
5 + (4 + (3 + sum(2)))
5 + (4 + (3 + (2 + sum(1))))
5 + (4 + (3 + (2 + 1)))
15
尾递归
@tailrec //告诉编译器器,强制使⽤用尾递归
def tailSum(n:Int,acc:Int = 0):Int = { if (n ==0 ){
acc
}else{
tailSum(n - 1,acc + n)
}
}//执⾏行行结果tailSum(5) // tailSum(5, 0) 默认
值是0
tailSum(4, 5) // 不不需要暂停计算
tailSum(3, 9)
tailSum(2, 12)
tailSum(1, 14)
tailSum(0, 15)
15
1. var是变量声明关键字,类似于Java中的变量,变量值可以更改,但是变量类型不能更改。
2. val常量声明关键字。
3. def 关键字⽤于创建方法(注意方法和函数的区别)
4. 还有一个lazy val(惰性val)声明,意思是当需要计算时才使用,避免重复计算
(1)一个类只能集成一个抽象类,但是可以通过with关键字继承多个特质;
(2)抽象类有带参数的构造函数,特质不行(如 trait t(i:Int){} ,这种声明是错误的)
object是类的单例对象,开发⼈人员⽆需用new关键字实例化。如果对象的名称和类名相同,这个对象就是伴生对象(深⼊了解请参考问题Q7)
样本类是一种不可变且可分解类的语法糖,这个语法糖的意思大概是在构建时,自动实现⼀些功能。样本类具有以下特性:
(1)⾃动添加与类名一致的构造函数(这个就是前面提到的伴生对象,通过apply⽅法实现),即构造对象时,不需要new;
(2)样本类中的参数默认添加val关键字,即参数不能修改;
(3)默认实现了toString,equals,hashcode,copy等方法;
(4)样本类可以通过==比较两个对象,并且不在构造⽅法中定义的属性不会用在比较上。
先讲⼀个概念——提取器,它实现了构造器相反的效果,构造器从给定的参数创建⼀一个对象,然⽽而提取器却从对象中提取出构造该对象的参数,scala标准库预定义了⼀些提取器,如上⾯面提到的样本类中,会⾃动创建⼀一个伴生对象(包含apply和unapply⽅方法)。
为了成为一个提取器,unapply⽅法需要被伴生对象。
apply⽅法是为了自动实现样本类的对象,⽆需new关键字。
1、Null是⼀个trait(特质),是所有引用类型AnyRef的⼀个⼦类型,null是Null唯一的实例。
2、Nothing也是一个trait(特质),是所有类型Any(包括值类型和引用类型)的子类型,它不在有子类型,它也没有实例,实际上为了一个方法抛出异常,通常会设置⼀个默认返回类型。
3、Nil代表⼀一个List空类型,等同List[Nothing]
4、None是Option monad的空标识(深入了解请参考问题Q11)
Unit代表没有任何意义的值类型,类似于java中的void类型,他是anyval的子类型,仅有⼀个实例例对象"( )"
(1) call-by-value是在调⽤用函数之前计算;
(2) call-by-name是在需要时计算
在Java中,null是一个关键字,不是⼀个对象,当开发者希望返回一个空对象时,却返回了了⼀个关键字,为了解决这个问题,Scala建议开发者返回值是空值时,使⽤Option类型,在Scala中null是Null的唯⼀对象,会引起异常,Option则可以避免。Option有两个子类型,Some和None(空值)
yield⽤于循环迭代中生成新值,yield是comprehensions的一部分,是多个操作(foreach, map, flatMap, filter or withFilter)的composition语法糖。(深⼊了解请参考问题Q14)
在Scala中implicit的功能很强大。当编译器寻找implicits时,如果不注意隐式参数的优先权,可能会引起意外的错误。因此编译器会按顺序查找隐式关键字。顺序如下:
(1)当前类声明的implicits ; (2)导⼊入包中的 implicits; (3)外部域(声明在外部域的implicts); (4) inheritance (5) package object (6) implicit scope like companion objects
comprehension(推导式)是若⼲个操作组成的替代语法。如果不用yield关键字,comprehension(推导式)可以被forech操作替代,或者被map/flatMap,filter代替。
开发时经常遇到这个的问题,当你使用integer时,希望它代表一些东⻄西,⽽不是全部东西,例如,⼀个integer代表年龄,另⼀个代表高度。由于上述原因,我们考虑包裹原始类型⽣生成⼀个新的有意义的类型(如年龄类型和高度类型)。
Value classes 允许开发者安全的增加⼀个新类型,避免运⾏时对象分配。有一些 必须进⾏分配的情况 and 限制,但是基本的思想是:在编译时,通过使⽤用原始类型替换值类实例例,删除对象分配。
1、这三种monads允许我们显示函数没有按预期执行的计算结果。
2、Option表示可选值,它的返回类型是Some(代表返回有效数据)或None(代表返回空值)。
3、Try类似于Java中的try/catch,如果计算成功,返回Success的实例例,如果抛出异常,返回Failure。
4、Either可以提供⼀一些计算失败的信息,Either有两种可能返回类型:预期/正确/成功的 和 错误的信息。
高阶函数指能接受或者返回其他函数的函数,scala中的filter map flatMap函数都能接受其他函数作为参数。