scala函数式编程
方法、函数、函数式编程
函数的定义
基本语法
def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
语句...
return 返回值
}
1.函数声明关键字为def (definition)
2.[参数名: 参数类型], ... 表示函数的输入(就是参数列表), 可以没有。 如果有多个参数使用逗号间隔
3.函数中的语句表示为了实现某一功能代码块
4.函数可以有返回值,也可以没有
5.返回值形式1: : 返回值类型 =
6.返回值形式2: = 表示返回值类型不确定使用类型推导完成
7.返回值形式3: 表示没有返回值return 不生效
8.如果没有return ,默认以执行到最后一行的结果作为返回值
object FunDemo01 {
def main(args: Array[String]): Unit = {
val n1 = 10
val n2 = 20
println("res=" + getRes(1, 2, ')'))
}
//定义函数/方法
def getRes(n1: Int, n2: Int, oper: Char) = {
if (oper == '+') {
n1 + n2 //返回
} else if (oper == '-') {
n1 - n2
} else {
//返回null
null
}
}
}
函数注意事项和细节
1.函数的形参列表可以是多个, 如果函数没有形参调用时 可以不带()
2.形参列表和返回值列表的数据类型可以是值类型和引用类型。
3.Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下return关键字可以省 略
4.因为Scala可以自行推断所以在省略return关键字的场合返回值类型也可以省略
5.如果函数明确使用return关键字那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = 当然 如果你什么都不写即使有return 返回值为()
def getSum(n1: Int, n2: Int): Int = {
//因为这里有明确的return , 这时 getSum 就不能省略 : Int = 的 Int了
return n1 + n2
}
6.如果函数明确声明无返回值声明Unit那么函数体中即使使用return关键字也不会有返回值
7.如果明确函数无返回值或不确定返回值类型那么返回值类型可以省略(或声明为Any)
8.Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活)即函数中可以再声明/定义函数类中可以再声明 类 方法中可以再声明/定义方法
9.Scala函数的形参在声明参数时直接赋初始值(默认值)这时调用函数时如果没有指定实参则会使用默认 值。如果指定了实参则实参会覆盖默认值。
def sayOk(name : String = "jack"): String = {
return name + " ok! "
}
10.如果函数存在多个参数每一个参数都可以设定默认值那么这个时候传递的参数到底是覆盖默认值还是赋值给没有默认值的参数就不确定了(默认按照声明顺序[从左到右])。在这种情况下可以采用带名参数
def mysqlCon(add:String = "localhost",port : Int = 3306,
user: String = "root", pwd : String = "root"): Unit = {
println("add=" + add)
println("port=" + port)
println("user=" + user)
println("pwd=" + pwd)
}
11.scala 函数的形参默认是val的因此不能在函数中进行修改.
12.递归函数未执行之前是无法推断出来结果类型在使用时必须有明确的返回值类型
def f8(n: Int) = { //? 错误递归不能使用类型推断必须指定返回的数据类型
if(n <= 0)
1
else
n * f8(n - 1)
}
13.Scala函数支持可变参数
//支持0到多个参数
def sum(args :Int*) : Int = {
}
//支持1到多个参数
def sum(n1: Int, args: Int*) : Int = {
}
//说明:
//1.args 是集合, 通过 for循环 可以访问到各个值。
//2.案例演示 编写一个函数sum ,可以求出 1到多个int的和
//3.可变参数需要写在形参列表的最后。
过程
将函数的返回类型为Unit的函数称之为过程(procedure)如果明确函数没有返回值那么等号可以省略
惰性函数
惰性计算尽可能延迟表达式求值是许多函数式编程语言的特性。惰性集合在需要时提供其元素无需预先计算它们这带来了一些好处。首先您可以将耗时的计算推迟到绝对需要的时候。其次您可以创造无限个集合只要它们继续收到请求就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持Scala提供了。
当函数返回值被声明为lazy时函数的执行将被推迟直到我们首次对此取值该函数才会执行。这种函数我们称之为惰性函数在Java的某些框架代码中称之为懒加载(延迟加载)
//注意事项和细节
1.lazy 不能修饰 var 类型的变量
2.不但是 在调用函数时加了 lazy ,会导致函数的执行被推迟我们在声明一个变量时如果给声明了 lazy ,那 么变量值得分配也会推迟。 比如 lazy val i = 10
异常
object ScalaExceptionDemo {
def main(args: Array[String]): Unit = {
try {
val r = 10 / 0
} catch {
//说明
//1. 在scala中只有一个catch
//2. 在catch中有多个case, 每个case可以匹配一种异常 case ex: ArithmeticException
//3. => 关键符号表示后面是对该异常的处理代码块
//4. finally 最终要执行的
case ex: ArithmeticException=> {
println("捕获了除数为零的算数异常")
}
case ex: Exception => println("捕获了异常")
} finally {
// 最终要执行的代码
println("scala finally...")
}
println("ok,继续执行~~~~~")
}
}
Scala异常处理小结
1.我们将可疑代码封装在try块中。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常catch 处理程序将处理它程序将不会异常终止。
2.Scala的异常的工作机制和Java一样但是Scala没有“checked(编译期)”异常即Scala没有编译异常这个概 念异常都是在运行的时候捕获处理。
3.用throw关键字抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的就是 Nothing因为Nothing是所有类型的子类型所以throw表达式可以用在需要类型的地方
def main(args: Array[String]): Unit = {
//如果我们希望在test() 抛出异常后代码可以继续执行则我们需要处理
try {
test()
}catch {
case ex: Exception => {
println("捕获到异常" + ex.getMessage)
println("xxxx")
}
case ex:ArithmeticException => println("得到一个算术异常~~")
}finally {
//写上对try{} 中的资源的分配
}
println("ok~~~~~")
}
4.在Scala里借用了模式匹配的思想来做异常的匹配因此在catch的代码里是一系列case子句来匹配异常。【前面案例可以看出这个特点, 模式匹配我们后面详解】当匹配上后 => 有多条语句可以换行写类似 java 的 switch case x: 代码块..
5.异常捕捉的机制与其他语言中一样如果有异常发生catch子句是按次序捕捉的。因此在catch子句中越具体 的异常越要靠前越普遍的异常越靠后如果把越普遍的异常写在前把具体的异常写在后在scala中也不会报 错但这样是非常不好的编程风格。
7.finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤一般用于对象的清理工作这点和Java一样。
8.Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中以避免程序异常终止。在scala中可以使用throws注释来声明异常
def main(args: Array[String]): Unit = {
f11()
}
@throws(classOf[NumberFormatException])//等同于NumberFormatException.class
def f11() = {
"abc".toInt
}
类与对象
class Cat {
//定义/声明三个属性
//说明
//1. 当我们声明了 var name :String时, 在底层对应 private name
//2. 同时会生成 两个public方法 name() <=类似=> getter public name_$eq() => setter
var name: String = "" //给初始值
var age: Int = _ // _ 表示给age 一个默认的值 如果Int 默认就是0
var color: String = _ // _ 给 color 默认值如果String ,默认是就是""
}
object CatDemo {
def main(args: Array[String]): Unit = {
//创建一只猫
val cat = new Cat
//给猫的属性赋值
//说明
//1. cat.name = "小白" 其实不是直接访问属性而是 cat.name_$eq("小白")
//2. cat.name 等价于 cat.name()
cat.name = "小白" //等价
cat.age = 10
cat.color = "白色"
println("ok~")
printf("\n小猫的信息如下: %s %d %s", cat.name, cat.age, cat.color)
}
}
类和对象的区别和联系
1.类是抽象的概念的代表一类事物,比如人类,猫类..
2.对象是具体的实际的代表一个具体事物
3.类是对象的模板对象是类的一个个体对应一个实例
4.Scala中类和对象的区别和联系 和 Java是一样的。
如何定义类
[修饰符] class 类名 {
类体
}
//定义类的注意事项
1.scala语法中类并不声明为public所有这些类都具有公有可见性(即默认就是public)
2.一个Scala源文件可以包含多个类
属性
1.属性是类的一个组成部分一般是值数据类型,也可是引用类型。比如我们前面定义猫类 的 age 就是属性
类与对象
1.属性的定义语法同变量示例[访问修饰符] var 属性名称 [类型] = 属性值
2.属性的定义类型可以为任意类型包含值类型或引用类型[案例演示]
3.Scala中声明一个属性,必须显示的初始化然后根据初始化数据的类型自动推断属性类型可以省略(这点和Java不同)。
4.如果赋值为null,则一定要加类型因为不加类型, 那么该属性的类型就是Null类型.
class Person {
var age : Int = 10
var sal = 8090.9 //给属性赋初值省略类型会自动推导
var Name = null // Name 是什么类型?
var address : String = null // address 是什么类型 }
}
5.如果在定义属性时暂时不赋值也可以使用符号_(下划线)让系统分配默认值.
class A {
var var1 :String = _ // null
var var2 :Byte = _ // 0
var var3 :Double = _ //0.0
var var4 :Boolean = _ //false
}
类型 | _ 对应的值 |
---|---|
Byte Short Int Long | 0 |
Float Double | 0.0 |
String 和 引用类型 | null |
Boolean | false |
6.不同对象的属性是独立互不影响一个对象对属性的更改不影响另外一个。案例演示+图(Monster) //这点和java完全一样
如何创建对象
//基本语法
val | var 对象名 [父类类型] = new 子类类型()
//说明
1.如果我们不希望改变对象的引用(即内存地址), 应该声明为val 性质的否则声明为var, scala设计者推荐使 用val ,因为一般来说在程序中我们只是改变对象属性的值而不是改变对象的引用。
2.scala在声明对象变量时可以根据创建对象的类型自动推断所以类型声明可以省略但当类型和后面new 对象 类型有继承关系即多态时就必须写了
def main (args: Array[String] ): Unit = {
val ee : Person = new Emp
}
class Person{
}
class Emp extends Person{
}
类和对象的内存分配机制(重要)
1) 当我们scala开始执行时先在栈区开辟一个main栈。main栈是最后被销毁
2) 当scala程序在执行到一个方法时总会开一个新的栈。
3) 每个栈是独立的空间变量基本数据类型是独立的相互不影响引用类型除外
4) 当方法执行完毕后该方法开辟的栈就会被jvm机回收。
object MemState {
def main(args: Array[String]): Unit = {
val p1 = new Person2
p1.name = "jack"
p1.age = 10
val p2 = p1
println(p1 == p2) // true
p1.name = "tom"
println("p2.name=" + p2.name)
}
}
class Person2 {
var name = ""
var age: Int = _ //如果是用 _ 方式给默认值则属性必须指定类型
}
集合的aforeach方法
object Exercise04 {
def main(args: Array[String]): Unit = {
val n = 3
val res1 = (0 to n).reverse
println(res1) //返回了 Range(3, 2, 1, 0)
//foreach 函数 (f: Int => U),即接收一个输入参数为 Int,输出为U的函数
//res1.reverse.foreach(println)
res1.foreach(println)
(0 to n).reverse.foreach(println)
}
}
字符串截取
object Exercise08 {
def main(args: Array[String]): Unit = {
val str = "Hello"
val substr1 = str.take(1)
println(substr1) // "H"
var subStr2 = str.drop(1)
println(subStr2) // "ello"
}
}
方法
1) 当我们scala开始执行时先在栈区开辟一个main栈。main栈是最后被销毁
2) 当scala程序在执行到一个方法时总会开一个新的栈。
3) 每个栈是独立的空间变量基本数据类型是独立的相互不影响引用类型除外
4) 当方法执行完毕后该方法开辟的栈就会被jvm机回收。
class 和 object区别
1.在Scala中声明private变量,Scala编译器会自动生成get,set方法
2.在Scala中变量需要初始化
3.在Scala中没有静态修饰符,在object下的成员全部都是静态的,如果在类中声明了与该类相同的名字的object则该object是该类的”伴生对象”
可以理解为Scala把类中的static集中放到了object对象中,伴生对象和类文件必须是同一个源文件,可以用伴生对象做一些初始化操作.
4.在Java中可以通过interface实现多继承,在Scala中可以通过特征(trait)实现多重继承,但是与Java不同的是,它可以定义自己的属性和实现方法体
5.object不能提供构造器参数,也就是说object必须是无参的
///Scala中object与class的区别
1.在Scala中,类名可以和对象名为同一个名字,该对象称为该类的伴生对象,类和伴生对象可以相互访问他们的私有属性,但是它们必须在同一个源文件中
2.类只会被编译,不能直接执行,类的声明和主构造器在一起被声明,在一个类中,主构造器只有一个.
类和它的伴生对象可以相互访问其私有成员
class和object的一个差别是,单例对象不带参数,而类可以.因为你不能用new关键字实例化一个单例对象,你没有机会传递给它参数
构造器
Scala构造器的介绍
和Java一样Scala构造对象也需要调用构造方法并且可以有任意多个构造方法即scala中构造器也支持重载。
Scala类的构造器包括 主构造器 和 辅助构造器
Scala构造器的基本语法
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
//1. 辅助构造器 函数的名称this, 可以有多个编译器通过不同参数来区分.
object ConDemo02 {
def main(args: Array[String]): Unit = {
val a = new AA("jack")
//输出的顺序是
//1. b~~~ 父类
//2. AA() 主构造器
//3. A this(name:String) 辅助构造器
}
}
class BB(){
println("b~~~")
}
class AA() extends BB() {
println("AA()")
def this(name:String) {
this
println("A this(name:String)")
}
}
Scala构造器注意事项和细节
1.Scala构造器作用是完成对新对象的初始化构造器没有返回值。
2.主构造器的声明直接放置于类名之后 [反编译]
3.主构造器会执行类定义中的所有语句这里可以体会到Scala的函数式编程和面向对象编程融合在一起即构造器 也是方法函数传递参数和使用方法和前面的函数部分内容没有区别【案例演示+反编译】
4.如果主构造器无参数小括号可省略构建对象时调用的构造方法的小括号也可以省略
5.辅助构造器名称为this这个和Java是不一样的多个辅助构造器通过不同参数列表进行区分 在底层就是f构 造器重载。
6.如果想让主构造器变成私有的可以在()之前加上private这样用户只能通过辅助构造器来构造对象了
/*
class Person2 private() {
}
*/
7.辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)
object ConDemo03 {
def main(args: Array[String]): Unit = {
//xxx
val p1 = new Person2("jack")
}
}
//定义了一个Person类
//Person 有几个构造器 4
class Person2 private() {
var name: String = _
var age: Int = _
def this(name : String) {
//辅助构造器无论是直接或间接最终都一定要调用主构造器执行主构造器的逻辑
//而且需要放在辅助构造器的第一行[这点和java一样java中一个构造器要调用同类的其它构造器也需要放在第一行]
this() //直接调用主构造器
this.name = name
}
//辅助构造器
def this(name : String, age : Int) {
this() //直接调用主构造器
this.name = name
this.age = age
}
def this(age : Int) {
this("匿名") //调用主构造器,因为 def this(name : String) 中调用了主构造器!
this.age = age
}
def showInfo(): Unit = {
println("person信息如下:")
println("name=" + this.name)
println("age=" + this.age)
}
}
属性高级
1.Scala类的主构造器的形参未用任何修饰符修饰那么这个参数是局部变量。
2.如果参数使用val关键字声明那么Scala会将参数作为类的私有的只读属性使用
3.如果参数使用var关键字声明那么那么Scala会将参数作为类的成员属性使用,并会提供属性对应的xxx()[类似 getter]/xxx_$eq()[类似setter]方法即这时的成员属性是私有的但是可读写。
4.@BeanPropertity 自动生成get set方法
Bean属性
JavaBeans规范定义了Java的属性是像getXxx和setXxx的方法。许多Java工具框架都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。
注意:给某个属性加入@BeanPropetry注解后会生成getXXX和setXXX的方法
并且对原来底层自动生成类似xxx(),xxx_$eq()方法没有冲突二者可以共存。
//示例
import scala.beans.BeanProperty
object BeanPropertDemo {
def main(args: Array[String]): Unit = {
val car = new Car
car.name = "宝马"
println(car.name)
//使用 @BeanProperty 自动生成 getXxx 和 setXxx
car.setName("奔驰")
println(car.getName())
}
}
class Car {
@BeanProperty var name: String = null
}
包
1.基本语法
package 包名
2.Scala包的三大作用(和Java一样)
1) 区分相同名字的类
2) 当类很多时,可以很好的管理类
3) 控制访问范围
3.Scala中包名和源码所在的系统文件目录结构要可以不一致但是编译后的字节码文件路径和包名会保持一致(这个 工作由编译器完成)。