文章目录
一、函数式编程
函数式编程充分利用函数、 支持的函数的多种使用方式;面向对象编程是以对象为基础的编程方式
1.1 函数的定义和使用
函数的定义:
def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
语句... //完成某个功能
return 返回值
}
返回值有多种形式:
- 返回值确定:
def 函数名(参数列表) : 数据类型 = {函数体}
,如
def test1(str: String): String = {return str}
- 有返回值,类型推断:
def 函数名(参数列表) = {函数体}
def test2(str: String) = {return str}
- 无返回值
Unit
:def 函数名(参数列表) {函数体}
def test3(str: String) = {println(str)}
- 如果没有
return
,默认以执行到最后一行的结果作为返回值
def test2(str: String) = {str}
函数使用注意事项:
- 函数的形参列表可以是多个,如果函数没有形参,调用时可以不带
()
;省略小括号,调用时一定不能使用小括号 - 如果将函数体的最后一行代码进行返回,那么
return
关键字可以省略 - 如果可以根据函数的最后一行代码推断类型,那么函数返回值类型也可以省略;如果了使用
return
关键字需明确返回值类型,若不明确,即使return
了返回值也是()
;同理返回值类型为Unit
,那么即使函数体中有return
也不起作用。 - 在
scala
中没有函数重载的概念 scala
中没有throw
关键字,所以函数汇总如果有异常发送,也不需要在函数声明时抛出异常
1.2 可变参数
def multi(name: String*): Unit = {
println(name)
}
multi("a", "b", "c")//WrappedArray(a, b, c)
multi()//List()
name
是集合,通过for
循环可以访问到各个值。- 可变参数需要写在形参列表的最后
1.3 有默认值的参数
def test(name: String, age: Int = 20) = {
println(name + "->" + age)
}
test("jack") //jack->20
test("rose", 18) //rose->18
test(name = "pick")//pick->20
- 如何没有指定参数,会使用默认参数的值
- 带名参数:指定实参的名字
1.4 函数作为返回值
def main(args: Array[String]): Unit = {
def f0(): Unit = println("function")
def f1() = f0 _ //函数作为返回值需要添加特殊符号 _
f1()() //function
}
1.5 函数作为参数
def f0(i: Int): Int = i
def f1(i: Int, f:(Int)=>Int): Int = f(i) * i
println(f1(2, f0 _)) //4
1.6 匿名函数
val f = (i: Int, j: Int) => i + j
println(f(1, 2))
1.7 闭包
def f3(i: Int) = {
def f4(j: Int): Int = i * j
f4 _
}
println(f3(2)(3))//6
1.8 函数柯里化
柯里化指的是将原来接受多个参数的函数变成新的接受一个参数的函数的过程,新函数的参数接受原来的第二个参数为唯一参数,如果有n
个参数, 就是把这个函数分解成n
个新函数的过程。
柯里化的实现基础就是闭包。
def f0(i: Int): Int = i
def f1(i: Int, j: Int): Int = f0(i) * j
println(f1(2, 3))//6
//=========>柯里化后<=========
def f2(i: Int)(j: Int): Int = i * j
println(f2(2)(3))//6
二、面向对象编程
2.1 类
定义:
[修饰符] class 类名 {
类体
}
scala
语法中,类并不声明为public
,所有这些类都具有公有可见性(即默认就是public
)- 一个
scala
源文件可以包含多个类
创建对象:
语法:val | var 对象名 [:类型] = new 类型()
说明:
- 通常对象的声明使用
val
,因为通常我们只是改变对象属性的值,而不是改变对象的引用 - 在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面
new
对象类型有继承关系即多态时,就必须写了
方式:
new
对象apply
方法,创建对象- 动态混入
- 匿名子类创建对象
- 反射
- 反序列化
- 工具类(
Unsafe.allocate
)
2.2 属性
属性的定义:[访问修饰符] var 属性名称 [:类型] = 属性值
例:
class Man {
var name: String = _
val age: Int = 20
}
scala
中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略- 如果赋值为
null
,则一定要加类型,因为不加类型, 那么该属性的类型就是Null
类型 - 给某个属性加入
@BeanPropetry
注解后,会生成getXXX
和setXXX
的方法 - 如果给属性增加
private
修饰符,那么属性无法在外部访问,因为底层生成的setter
和getter
方法都是私有的 - 如果在定义属性时,暂时不赋值,也可以使用符号
_
,让系统分配默认值:
类型 | _ 对应的值 |
---|---|
Byte Short Int Long | 0 |
Float Double | 0.0 |
String 和 引用类型 | null |
Boolean | false |
2.3 方法
语法:
def 方法名(参数列表) [:返回值类型] = {
方法体
}
方法的调用机制说明:当scala
开始执行时,先在栈区开辟一个main
栈。当scala
程序在执行到一个方法时,总会创建一个新的栈桢。每个栈桢是独立的空间,变量(基本数据类型)是独立的,相互不影响(引用类型除外)。当方法执行完毕后,该方法开辟的栈桢就会出栈。
2.4 构造器
scala
中可以有多个构造方法(构造器支持重载),构造器分为主构造器(一个) 和 辅助构造器(多个)
语法:
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
辅助构造器函数的名称this
,可以有多个,编译器通过不同参数(个数或类型)来区分。
例:
class Woman(str: String) {
var name: String = str
var age: Int = _
def this(str: String, int: Int) {
this(str)
age = int
}
}
说明:
- 主构造器的声明直接放置于类名之后
- 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
- 主构造器会执行类定义中的所有语句,这里可以体会到
scala
的函数式编程和面向对象编程融合在一起,即构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别 - 辅助构造器名称为
this
,多个辅助构造器通过不同参数列表进行区分, 在底层就是构造器重载 - 如果想让主构造器变成私有的,可以在
()
之前加上private
,这样用户不能直接通过主构造器来构造对象了 - 辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)
构造器参数说明:
scala
类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量- 如果参数使用
val
关键字声明,那么scala
会将参数作为类的私有的只读属性使用 - 如果参数使用
var
关键字声明,那么scala
会将参数作为类的成员属性使用,并会提供属性对应的xxx()
(类似getter
)和xxx_$eq()
(类似setter
)方法,即这时的成员属性是私有的,但是可读写
2.5 伴生对象
Scala
语言是完全面向对象的语言,所以并没有静态的操作,也没有static
关键字,但是为了能够和Java
语言交互,就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。
从语法角度来讲,所谓的伴生对象其实就是类的静态方法和静态变量的集合
从技术角度来讲,scala
还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。
从底层原理看,伴生对象实现静态特性是依赖于public static final MODULE$
实现的。
说明:
scala
中伴生对象采用object
关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生对象名称直接调用- 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
- 伴生对象中的属性和方法都可以通过伴生对象名直接调用访问
- 如果
class A
独立存在,那么A
就是一个类, 如果object A
独立存在,那么A
就是一个"静态"性质的对象[即类对象],在object A
中声明的属性和方法可以通过A.
属性和A.
方法来实现调用
伴生对象-apply方法:
可以通过伴生对象更直接简单的创建实例,在伴生对象中定义apply
方法,可以实现: 类名(参数) 方式来创建对象实例。
class Cap() {
def apply() = {
println("this is the apply method in class")
}
}
object Cap {
def apply(): Cap = {
println("this is the apply method in object")
new Cap
}
}
object CapTest {
def main(args: Array[String]): Unit = {
//object.apply()
val cap = Cap()//this is the apply method in object
//class.apply()
cap() //this is the apply method in class
}
}
2.6 package
在
scala
中,同样可以使用package
进行类的管理和访问权限控制,还有许多强大的功能。
基本语法:package 包名
包名命名规则: 只能包含数字、字母、下划线、小圆点.,但不能用数字开头, 也不要使用关键字
scala
中包名和源码所在的系统文件目录结构要可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)- 作用域原则:可以直接向上访问。即
scala
中子包中直接访问父包中的内容,类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可 - 父包要访问子包的内容时,需要
import
对应的类 - 可以在同一个
.scala
文件中,声明多个并列的package
(建议嵌套的pakage
不要超过3层)
包对象:
包可以包含类、对象和特质trait
,但不能包含函数/方法或变量的定义。这是Java
虚拟机的局限。为了弥补这一点不足,scala
提供了包对象的概念来解决这个问题。
注意:
- 每个包都可以有一个包对象。你需要在父包中定义它
- 包对象名称需要和包名一致,一般用来对包的功能补充
例:
package com.hc.obj {
//每个包都可以有一个包对象。你需要在父包中定义它,且名称与子包一样。
package object scala {
var name = "jack"
def sayOk(): Unit = {
println("package object sayOk!")
}
}
package scala {
class Test {
def test(): Unit = {
//这里的name就是包对象scala中声明的name
println(name)
sayOk() //这个sayOk 就是包对象scala中声明的sayOk
}
}
object PackageTest {
def main(args: Array[String]): Unit = {
val t = new Test()
t.test()
//因为PackageTest和scala这个包对象在同一包,因此也可以使用
println("name=" + name)
}
}
}
}
包的可见性和访问修饰符的使用:
- 默认为
public
访问权限,从底层看属性是private
的,但是因为提供了xxx_$eq()
和xxx()
方法,因此均能访问 private
为私有权限,只在类的内部和伴生对象中可用protected
为受保护权限,scala
中受保护权限比Java
中更严格,只能子类访问,同包无法访问- 在
scala
中没有public
关键字,即不能用public
显式的修饰属性和方法
import:
import
可以在任意的地方使用import
可以导入一个包中的所有的类,采用下划线代替星号- 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
- 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉:
import java.util.{ HashMap=>_, _}
2.7 继承
例:
class A{
println("I'm A")
def test{}
}
class B extends A{
def this(str:String){
this
println(str)
}
//重写需要加关键字override
override def test: Unit ={
}
}
object ExtendsTest {
def main(args: Array[String]): Unit = {
val b = new B//I'm A
}
}
- 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器
- 只有主构造器可以调用父类的构造器
- 方法有动态绑定,属性没有动态绑定
Scala继承层级图
说明:
- 在
scala
中,所有其他类都是AnyRef
的子类,类似Java
的Object
Any
类是根节点/根类型,AnyVal
和AnyRef
都扩展自Any
类。Any
中定义了isInstanceOf
、asInstanceOf
方法,以及哈希方法等。Null
类型的唯一实例就是null
对象。可以将null
赋值给任何引用,但不能赋值给值类型的变量Nothing
类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil
的类型是List[Nothing]
,它是List[T]
的子类型,T
可以是任何类
2.8 抽象
抽象类的价值更多是在于设计,是设计者设计好后,让子类继抽象类;抽象类本质上就是一个模板设计
scala
抽象代码翻译成java
:
说明:
- 一个属性没有初始化,那么这个属性就是抽象属性
- 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
- 如果是覆写一个父类的抽象属性,那么
override
关键字可省略(本质上是实现) - 抽象类不一定要包含
abstract
方法。一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
- 抽象方法和抽象属性不能使用
private
、final
来修饰,因为这些关键字都是和重写/实现相违背的。 - 子类重写抽象方法不需要
override
匿名子类:
val c = new C {
override var name: String = _
override def test(): Unit = {
println("Hello")
}
}
c.test()//Hello
2.9 单例模式
- 私有化构造器
- 提供公共的方法初始化和获取方法
懒汉式:
object TestSingleTon extends App {
val s1: SingleTon = SingleTon.getSingleTon
println(s1.hashCode())
}
class SingleTon private() {}
object SingleTon {
private var s: SingleTon = null
def getSingleTon = {
if (s == null) {
s = new SingleTon
}
s
}
}
饿汉式:
object TestSingleTon2 extends App {
val s2: SingleTon2 = SingleTon2.getSingleTon2
println(s2.hashCode())
}
class SingleTon2 private() {}
object SingleTon2 {
private val s: SingleTon2 = new SingleTon2
def getSingleTon2 = {
s
}
}
2.10 特质(trait)
scala
语言中,采用trait
(特质,特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait
声明。
trait的声明:
trait 特质名 {
trait体
}
trait的使用
- 没有父类
class 类名 extends 特质1 with 特质2 with 特质3
- 有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3
例:
trait MyTrait {
def test(str: String): Unit
def hello={
println("hello")
}
}
class A {}
class B extends A with MyTrait {
override def test(str: String): Unit = {
println("测试成功")
}
}
说明:
- 特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质
- 特质中没有实现的方法就是抽象方法。类通过
extends
继承特质,通过with
可以继承多个特质
动态混入:带有特质的对象
例:
object TraitTest {
def main(args: Array[String]): Unit = {
val a = new A with MyTrait
a.hello
}
}
说明:
- 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展对象的功能
- 此种方式也可以应用于对抽象类功能进行扩展
- 动态混入是
scala
特有的方式,可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 - 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能
叠加特质:
trait Operate1 {
println("Operate1...")
def insert()
}
trait Operate2 extends Operate1 {
println("Operate2...")
override def insert() = {
println(" 插入操作2 ")
}
}
trait Operate3 extends Operate2 {
println("Operate3...")
override def insert() = {
print(" 插入操作3 ")
super.insert()
}
}
trait Operate4 extends Operate2 {
println("Operate4...")
override def insert() = {
print(" 插入操作4 ")
super.insert()
}
}
class Operate5 {}
object TraitTest2 {
def main(args: Array[String]): Unit = {
/**
* Operate1...
* Operate2...
* Operate3...
* Operate4...
* 插入操作4 插入操作3 插入操作2
*/
val o1 = new Operate5 with Operate3 with Operate4
o1.insert()
/**
* Operate1...
* Operate2...
* Operate4...
* Operate3...
* 插入操作3 插入操作4 插入操作2
*/
val o2 = new Operate4 with Operate3 with Operate2
o2.insert()
}
}
- 构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左
scala
中特质中如果调用super
,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找- 如果想要调用具体特质的方法,可以指定:
super[特质].xxx(…)
,其中的泛型必须是该特质的直接超类类型
在特质中重写抽象方法:
调用父特质的抽象方法,那么在实际使用时,没有方法的具体实现,无法编译通过,为了避免这种情况的发生。可重写抽象方法,这样在使用时,就必须考虑动态混入的顺序问题。
当我们给某个方法增加了abstract override
后,就是明确的告诉编译器,该方法确实是重写了父特质的抽象方法,但是重写后,该方法仍然是一个抽象方法(因为没有完全的实现,需要其它特质继续实现[通过混入顺序]。
trait Operate5 {
def insert(id: Int)
}
trait File5 extends Operate5 {
abstract override def insert(id: Int): Unit = {
println("将数据保存到文件中..")
super.insert(id)
}
}
trait DB5 extends Operate5 {
def insert(id: Int): Unit = {
println("将数据保存到数据库中..")
}
}
class MySQL5 {}
object TraitTest3{
def main(args: Array[String]): Unit = {
val mysql5 = new MySQL5 with DB5 with File5
//error
// val mysql6 = new MySQL5 with File5 with DB5
}
}
特质中的具体字段:
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。
特质中的抽象字段:
特质中未被初始化的字段在具体的子类中必须被重写。
特质构造顺序:
object Test {
def main(args: Array[String]): Unit = {
val ff = new FF // E A B C D F
val gg = new GG with CC with DD//E A B C D G
val kk = new KK// E A B D C K
}
}
trait AA { println("A...") }
trait BB extends AA { println("B....") }
trait CC extends BB { println("C....") }
trait DD extends BB { println("D....") }
class EE { println("E...") }
class FF extends EE with CC with DD { println("F....") }
class KK extends EE with DD with CC { println("K....") }
class GG extends EE { println("GG")}
扩展类的特质:
- 特质可以继承类,以用来拓展该类的一些功能(和
java
的不同) - 所有混入该特质的类,会自动成为那个特质所继承的超类的子类
- 如果混入该特质的类,已经继承了另一个类(
A
类),则要求A
类是特质超类的子类,否则就会出现了多继承现象,发生错误