文章目录
方法与函数
方法定义:
尽量用规范的去写,便于维护
def 方法名(形参:参数类型):返回值类型 = {
函数体
最后一行为返回值,可以不指定return关键字
}
没有入参的函数,调用可以不用写括号,返回值类型也可以不写,没特殊必要,一般不写;
object helloworld {
def main(args: Array[String]): Unit = {
println(add(10,20))
sayhello()
sayhello // 没有参数的函数,调用可以不加括号
val num = 100
def num_sum(num2:Int) = num + num2 // 也可以在方法类再定义方法
def num2_sum = num_sum(5) + 1
}
def add(num1:Int,num2:Int):Int = {
num1 + num2
}
def sayhello(): Unit ={
println("say hello!!")
val num = 100
num // 指定为Unit,即使方法最后一行为变量返回也是空
}
def sum() = 1 + 2 // 方法的大括号也可以省略
def sum = 1 + 2 // 没有形参,小括号也是合法;
def pt(a:Int,b:Int) {
println(a + b) // println返回为Unit,返回时Unit时,函数定义的等于号可以不写;
}
}
函数定义
val 函数名称:函数类型 = 函数参数列表 => 输出列表
函数的类型可以不写;
val 函数名称:输入类型的形参列表 => 输出结果的类型列表 = 函数参数列表 => 输出列表
函数也有其类型与值
/*
* func:函数名称
* =:函数声明
* (a:Int,b:Int) => a + b:函数实现体
* (a:Int,b:Int):函数参数列表
* a + b:函数代码逻辑及返回
* =>:逻辑操作
*/
val func = (a:Int,b:Int) => a + b
// 如果函数值指定了变量类型,函数可以自动推断返回类型,函数的类型可以不显示指明;
val func:(Int,Int) => Int = (a,b) => a + b
// 函数类型指定了类型,函数的值可以自动推断类型,可以不显示指明函数值变量及返回类型
val func:(Int,Int) => Int = (a:Int,b:Int) => a + b
// 函数类型指定参数类型了,函数参数列表可以不再指定类型;
val func:(Int,Int) => Int = (a,b) => a + b
// (Int,Int) => Int 这个是函数类型
// (a,b) => a + b 这个是函数的值
val fun:(Int,Int,Int) => Int = (a,b,c) => a + b + c
// fun: (Int, Int, Int) => Int = <function3> function这个3代表的是三个参数
// 函数默认参数
def add(num1:Int = 1,num2:Int=2):Int = {
num1 + num2
}
add()
add // 有参数,这个括号省略报错
// 可变参数,参数类型后面添加“*”字符,类python的*args
def sum(numbers:Int*):Int = {
var result:Int = 0
for(number <- numbers) {
result += number
}
result
}
函数方法互相转化
// 函数也可以作为函数的参数传入,也可以作为您参数传入方法
val f2 = (a:Int,b:Int) => a + b
val f1 = (a:Int,b:Int,f2:(Int,Int) = > Int) = f2(a,b)
def m3(a:Int,b:Int) = a + b
// 将函数转化成方法
def m2 = f1
// 把方法转化成函数,右侧添加下空格+划线
val f3 = m3 _
// ff函数使用变量a返回匿名函数(b:Int) => a + b,匿名函数返回a+b未ff函数最终返回值;
val ff = (a:Int) => (b:Int) => a + b
匿名函数
(num:Int) => num + 1 // 一般称之为lambda匿名函数
闭包
- 闭包反应了一个从开放到封闭的过程,每次函数被调用都会生成一个新的闭包
- 函数内部可以去调用函数外部的值
var num = 100
val add = (a:Int) => a + num // 函数定义出现一个没有被定义的num变量被称之为闭包
add(100) // 返回200
num = 200
add(100) // 返回300,每次函数被调用都会生成一个新的闭包
部分应用
使用下划线,设置默认参数部分应用一个函数,返回另一个函数
// 定义函数
val add(a:Int,b:Int) => a + b
val add1 = add(_:Int,10) // 使用下划线设置默认参数
val result = add1(10) // 结果返回10
在python则称之为偏函数,函数设置默认参数,使用functools.partial方法定义偏函数;
import functools
# 定义int偏函数,base参数为2进制
int2 = functools.partial(int, base=2)
int2('1000000') # 返回64,2的6次方
柯里化函数
允许别人在函数上应用一些参数,同时也可以应用另外一些参数
def multiply(m: Int)(n: Int): Int = m * n
// multiply: (m: Int)(n: Int)Int
multiply(2)(3) // 返回6
设置第一个默认参数,部分应用第二个参数
val fun = multiply(10) _ // 下划线前面有一个空格
fun(20) // 返回20
对任何多参数函数执行柯里化
def add(a:Int,b:Int) = a + b
val add1 = add _ // 方法转化为函数
val add2 = (add _).curried // 使用函数的curried方法
val add2 = add1.curried
函数组合
- compose 组合其他函数形成一个新的函数 f(g(x))
- andThen 和 compose很像,但是调用顺序是先调用第一个函数,然后调用第二个,即g(f(x))
def f(str:String) = "f " + str + " f"
def g(str:String) = "g " + str + " g"
def func1 = f _ compose g _
func1("中心") // String = f g 中心 g f
def func2 = f _ andThen g _
func2("中心 ") // String = g f 中心 f g
类
类的定义与使用
object ClassDef {
def main(args: Array[String]): Unit = {
val person = new People() // 定义后面括号不加也可以
person.name = "007"
println(person.name)
println(person.age)
person.run()
person.playGame()
person.printInfo()
// person.gender // 私有属性外部无法访问,只能方法或函数内部访问
}
}
class People(){
// 定义属性
var name:String = _ // 值类型明确可以使用下划线占位符,如果是是String,返回null
val age:Int = 26
private [this] val gender = "male" // 定义私有变量,只能在方法内部调用
// 定义方法
def run()={
name + " is running......"
}
def playGame()={
println(name + " is playing game......")
}
def printInfo()={
println(name + " ganer is " + gender)
}
}
类的构造器
- scala的每个类都有主构造器,与java有所不同,scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需要的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入参数的值;
- 辅助构造器的名称为this。
- 每一个辅助构造器都必须以一个对先前已定义的其它辅助构造器或主构造器的调用开始。
- 一个类如果没有显式定义主构造器则自动拥有一个无参的主构造器。
- 主构造器执行除了方法定义外所有的语句都会执行
object ConstructorApp {
def main(args: Array[String]): Unit = {
val p1 = new Person("alice",18)
println(s"Person'name is ${p1.name},age is ${p1.age}......")
val p4 = new Person("yin",26)
println(s"Person'name is ${p4.name},age is ${p4.age}......") // name:yin;age:26
val p2 = new Person("bob",19,"male")
val p3 = new Person("mary",20,45)
}
}
// 主构造器跟在类名称后面,参数的val去掉后,外面无法访问该属性;类实例.属性名无法访问属性
class Person(val name:String,val age:Int) {
println("Person main constructor run...")
var gender:String = _ // 附属构造器传入的属性,先定义
var weight:Int = _
// 附属构造器
def this(name:String,age:Int,gender:String) {
this(name,age) // 附属构造器的第一行代码必须要调用主构造器或者其他附属构造器
this.gender = gender
println(s"附属构造器1:name:${name},age:${age},gender:${gender};" )
}
// 附属构造器2
def this(name:String,age:Int,weight:Int) {
this(name,age) // 调用主构造器
this.weight = weight
println(s"附属构造器2:name:${name},age:${age},weight:${weight};")
}
}
class Person2{
println("Person2 is run")
val age:Int = _
var name:String = _
def this(name:String) { // 辅助构造器1
this() // 调用无参数主构造器
this.name = name
}
def this(name:String,age:Int) {
this(name) // 调用前面的辅助构造器
this.age = age
}
}
继承
- 子类继承父类,会先调用父类的主构造方法,子类参数父类中没有的,需要var或者val关键字修饰。
- 继承使用extends关键字显示指明继承关系
class Person(var name:String,var age:Int) {
println("the main constructor is run......")
}
// 继承Person的name,age属性,weight属性Person类中没有,需要var修饰
class Student(name:String,age:Int,var weight:Int) extends Person(name,age) {
println("the Student constructor is run...")
}
抽象类
- 抽象类:类的一个或者多个方法没有完整的实现,或可理解为归纳抽象化的一般行为;
- 抽象类不能直接被实例化调用,只能被子类继承具体化通过子类实例调用;
- 使用abstract关键字定义;
- 关于继承override关键字使用,字段或方法重写:
- 重写超类的抽象方法时,不需要加上该关键字,不过加上编译也不会报错;
- 重写超类的非抽象方法时,必须使用该关键字;
- 重写超类字段,必须使用该关键字,不然会报错;
object AbstractClass {
def main(args: Array[String]): Unit = {
val student = new Student()
println(s"student's name is ${student.name},age is ${student.age}")
}
}
/**
* 这是一个抽象类,方法没有具体的实现
*/
abstract class Person3 {
def eat // 定义eat方法
val name:String
val age:Int
}
// 子类继承具体化方法、属性
class Student extends Person3 {
override def eat: Unit = { // 重写超类抽象方法,可以不加上override关键字
println("is speaking")
}
override val name = "alice"
override val age = 18 // 不能使用下划线占位符,需具体化,会报错
}
伴生对象及apply方法、update方法
- 如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类,类和它的伴生对象必须放在同一个文件中,彼此可以互相访问稀有成员(字段和方法);
- object定义的是单例对象,对象是静态的,可以直接运行;单例对象无需使用new关键字,可以直接objcet名称+其方法或字段调用即可;
- apply方法:用括号传递给变量(对象)一个或多个参数时,scala会把它转化成成对apply方法的调用;类似于python中的**call**魔法方法
- update方法:当对带有括号并包括一刀若干参数的对象进行赋值时,编译器将调用对象的update方法,在调用时,把括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用;
object ApplyApp {
def main(args: Array[String]): Unit = {
for(each <- 1 to 10) {
ApplyTest.incr // Object不需要new关键字定义,直接调用即可
}
println(ApplyTest.count) // 结果是10,说明object本身是一个单例对象
val a = ApplyTest() // object直接跟括号,调用object的apply方法
a() // 这里调用的也是类class中的apply方法,是因为a是已生成的实例;
val m = new ApplyTest
m() // 这里调用的类class中的apply方法,先用new创建实例,或者先生成实例再使用括号调用,调用的是类apply方法
}
}
object ApplyTest{
println("Object ApplyTest come in")
var count:Int = 0
def incr = {
count += 1
}
// 最佳实践:在Object的apply方法中定义new Class
def apply(): Unit = {
println("Object apply execute......")
// 在object中的apply方法中new class
new ApplyTest
}
println("Object ApplyTest leave......")
}
class ApplyTest{
def apply(): Unit ={
println("class apply execute......")
}
}
apply和update方法Array数组中的使用
val strArray = Array("python","vba","sql") // 这里实际调用了object的apply方法
strArray(0) = "scala" 这里实际调用了Array的update方法,执行intArray.update(0,"scala")
特质:Trait
- 在Scala中,Trait是一种特殊概念。首先,Trait可以被作为接口来使用,此时Trait与Java的接口非常类似。同时在Trait可以定义抽象方法,其与抽象类中的抽象方法一样,不给出方法的具体实现。
- 特质中方法和字段也可以具体实现,子对象 没有重写,调用则为调用特质中具体的方法和字段;
- 注意:类使用extends继承Trait,与Java不同,这里不是implement,在Scala中,无论继承类还是继承Trait都是用extends关键字。
- 在Scala中,类继承Trait后,必须实现其中的抽象方法,实现时不需要使用override关键字,同时Scala同Java一样,不支持类多继承,但支持多重继承Trait,使用with关键字即可。
- 使用方法:class extends trait1 with trait2 with trait3 with…(继承第一个使用extends关键字,后面使用with关键字)
object Alice{
def main(args:Array[String]): Unit ={
val alice = new Alice
println(s"alice'height is ${alice.height},weight is ${alice.weight}")
alice() // 伴生对象,调用类的apply方法
}
}
// 定义trait
trait Person{
var height:Double
var weight:Double
def eat
}
// 定义类Alice,继承Person
class Alice extends Person{
override var height = 170.0
override var weight = 47.5
def eat {println("alice is eating")} // 抽象方法不需要使用override关键字定义
def apply() = {println("正在调用Alice类的apply方法")}
}
getter和setter方法的实现
object GetSet {
def main(args: Array[String]): Unit = {
val v = new Value
v.value = 100 // 直接赋值,不是使用setter方法
println(v.value)
v.add(100) // 可以直接调用对方修改方法
println(v.value) // 直接访问:不是使用getter
}
}
class Value {
var value = 0 // 没有使用private私有修饰,外部可访问,修改
def add(num:Int) = {value += num}
}
- 变量通过private私有修饰,只能对象内部访问,scala中没有对应的getter和setter方法,但有类似的方法,value和value_=,示例如下:
object GetSet {
def main(args: Array[String]): Unit = {
val v = new Value
v.value = 100 // 这里实际是调用对象内部value_=方法去修改值
println(v.value)
}
}
class Value {
private var privateValue = 0 // 私有修饰,只能类内部访问
def value = privateValue // 定义一个方法,方法名称是我们原来想要的字段名称,通过内部方法getter对应的value
def value_=(newValue) = {
if(newValue.isInstanceOf[Int]) privateValue = newValue // 如果是整数,才允许修改
}
}
case class
- 样本类case class实例时不需要new关键字定义,直接使用类名即可;
- 使用场景:通常用在模式匹配里面
object CaseClass {
def main(args: Array[String]): Unit = {
val studet = new Student1("bob")
println(studet.name) // class,需要先实例,构造方法需要val或者var关键字指定
println(Dog("wangcai").name) // case class不需要new,构造也不需要传入val或者var关键字
}
}
case class Dog(name:String) // case class
class Student1(var name:String)
文件编译与执行
scala文件默认是以scala结尾的;
新建一个scala文件,命名为Test.scala,假设完整路径在F:\桌面\Test.scala
class Test{
val n = 100
val m = 10
def sum = n + m
}
val test = new Test
println(test.sum)
- 方法一:在命令行终端输入scala F:\桌面\Test.scala
scala F:\桌面\Test.scala
- 方法二:也可以先终端输入scala,再使用**:load**+文件名
退出scala解释器命令::quit:load F:\桌面\Test.scala
- 方法三:先使用scalac 文件名编译,再使用scala -classpath . 包含main方法的对象执行;旧版本不加**-classpath . **可能会报错;
- 需要注意的是,所有语句必须包含在类对象里面,不然无法被编译,比如上面类定义下面的,类实例及方法调用语句;
- 其次,包含main方法的对象,在java中命名是等同于文件命名的,但在scala中,main方法对象名称可以与文件名称不一致,这里并非文件名称(下方示例的TestObject);
- 如果切换到scala文件目录下,或者文件在环境变量下,文件名可以直接使用文件名.scala,无需使用完整路径;
object TestObject{ // 定义主函数 def main(args: Array[String]): Unit = { val cls = new ClassExample println(cls.sum(100)) } } class ClassExample{ val n = 100 def sum(m:Int) = n + m }
函数圆括号与花括号区别
- 转载链接:https://my.oschina.net/stackoom/blog/3197639/print
- 如果你要调用的函数有两个或两个以上的参数那么你只能使用小括号
- 如果你要调用的函数只有单一参数那么通常情况下小括号和花括号是可以互换的
- 在调用单一参数函数时小括号和花括号虽然等效但还是有差异的,在调用一个单一参数函数时如果参数本身是一个通过case语句实现的 偏函数你只能使用花括号
- 花括号可以使用代码块,代码块最后一行为返回值
// 这里只能使用花括号调用filter方法 pairs.filter{case (k,v) => v.length < 10} // 定义一个单变量方法 def add(num:Int):Int = num + 1 // 可以圆括号也可以方括号调用:只有一个参数 add(1) add{1} add{println("正在执行add方法");1} // 花括号最后一行为返回值:1,返回值作为参数返回给add调用 def add(a:Int,b:Int) = a + b add{1,2} // 有两个变量,使用花括号,报错; add(1,2)