1、类
类和对象是Java、C++等面向对象编程的基础概念。类是用来创建对象的蓝图。定义好类以后,就可以使用new关键字来创建对象。
-
类定义
- class class_name
class HelloWorld{ // attributes and methods }
-
类属性和方法定义
-
属性:public/private var att_name
-
方法:def method_name(): return_value = {method body}
class Counter { private var value = 0 def increment(): Unit = { value += 1} def current(): Int = {value} }
注:
Scala 的 Unit 表示不返回任何值;
Scala 类方法的返回值不需要return,方法里面的最后一个表达式的值就是方法的返回值;- Scala 类方法的方法体若只有一条语句,那么可以直接去掉大括号,或者,还可以去掉返回值类型和等号,只保留大括号,如下所示:
class Counter { private var value = 0 def increment(): Unit = value += 1 //去掉了大括号 def current(): Int = {value} // 作为对比,这里依然保留原来形式 }
class Counter { private var value = 0 def increment() {value += 1} //去掉了返回值类型和等号,只保留大括号 def current(): Int = {value} //作为对比,这里依然保留原来形式 }
- Scala 类方法的方法体若只有一条语句,那么可以直接去掉大括号,或者,还可以去掉返回值类型和等号,只保留大括号,如下所示:
-
-
对象创建与方法调用
-
创建对象:var(val) obj = new ClassName()
- val counter = new Counter()
主构造器无参 val counter = new Counter
- val counter = new Counter()
-
方法调用:与 Java 不同,Scala 的无参方法可以不带括号调用
- counter.increament()
- counter.increament
-
-
编译和执行
- 编写Scala代码(vim count.scala),如下所示
class Counter { private var value = 0 def increment(step: Int): Unit = { value += step} def current(): Int = {value} } object MyCounter{ def main(args:Array[String]){ val myCounter = new Counter myCounter.increment(5) //这里设置步长为5,每次增加5 println(myCounter.current) } }
- 编译:scalac count.scala 并 运行:scala -classpath . MyCounter
- 也可以这样运行:scala count.scala
- 需要注意的是,如下形式的代码(count_nomain.scala)是不能通过编译的:
class Counter { private var value = 0 def increment(): Unit = { value += 1} def current(): Int = {value} } val myCounter = new Counter myCounter.increment() println(myCounter.current)
- 但是,上述形式代码可以直接通过scala filename.scala运行
- 或者可以通过以下形式编译
- scalac -Xscript Main count_nomain.scala(Main是自己定义的main类名称,可以修改)
- scala -classpath . Main
- 当然,通过:load也可以在Scala解释器运行
- 但是,上述形式代码可以直接通过scala filename.scala运行
- 编写Scala代码(vim count.scala),如下所示
注意,对于一个Scala应用程序而言,必须包含main方法,由于上面代码中没有包含main方法,因此,不能使用scalac命令进行编译,而是直接使用scala命令运行代码,就可以得到结果;如果使用scalac命令去编译test.scala文件,就会报错。
- Scala的getter与setter方法
- 与Java不同,Scala的getter与setter方法并没有定义成getXxx()与setXxx()的形式,Scala是通过value与value_=定义类似的getter与setter方法,如下所示(getter_setter.scala);
class Counter { private var privateValue = 0 //私有字段 def value = privateValue // getter def value_=(newValue: Int){ // setter if (newValue > 0) privateValue = newValue } def increment(step: Int): Unit = { value += step} def current(): Int = {value} } object MyCounter{ def main(args:Array[String]){ val myCounter = new Counter println(myCounter.value) myCounter.value = 3 println(myCounter.value) myCounter.increment(1) println(myCounter.current) } }
- 运行结果如下:
- 运行结果如下:
- 与Java不同,Scala的getter与setter方法并没有定义成getXxx()与setXxx()的形式,Scala是通过value与value_=定义类似的getter与setter方法,如下所示(getter_setter.scala);
- 辅助构造器
- Scala构造器包含1个主构造器和若干个(0个或多个)辅助构造器;
- 辅助构造器的名称为this,每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器,如下所示(assitant_constructor.scala):
class Counter { private var value = 0 //value用来存储计数器的起始值 private var name = "" //表示计数器的名称 private var mode = 1 //mode用来表示计数器类型(比如,1表示步数计数器,2表示时间计数器) def this(name: String){ //第一个辅助构造器 this() //调用主构造器 this.name = name } def this (name: String, mode: Int){ //第二个辅助构造器 this(name) //调用前一个辅助构造器 this.mode = mode } def increment(step: Int): Unit = { value += step} def current(): Int = {value} def info(): Unit = {printf("Counter Name is %s and mode is %d\n",name,mode)} } object MyCounter{ def main(args:Array[String]){ val myCounter1 = new Counter //主构造器 val myCounter2 = new Counter("Runner") //第一个辅助构造器 val myCounter3 = new Counter("Timer",2) //第二个辅助构造器 myCounter1.info myCounter1.increment(1) printf("Current Value is: %d\n",myCounter1.current) myCounter2.info myCounter2.increment(2) printf("Current Value is: %d\n",myCounter2.current) myCounter3.info myCounter3.increment(3) printf("Current Value is: %d\n",myCounter3.current) } }
- 运行结果
- 运行结果
- 主构造器
- Scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入的参数的值;
- Scala主构造器的方法不需要编写getter与setter方法;
- 例子如下:
class Counter(val name: String, val mode: Int) { private var value = 0 def increment(step: Int): Unit = { value += step} def current(): Int = {value} def info(): Unit = {printf("Counter name is %s and mode is %d\n",name,mode)} } object MyCounter{ def main(args:Array[String]){ val myCounter = new Counter("Timer",2) myCounter.info myCounter.increment(1) printf("Current Value is: %d\n",myCounter.current) } }
- 运行结果如下
- 运行结果如下
2、对象
-
单例对象
- Scala并没有提供Java那样的静态方法或静态字段,但是,可以采用object关键字实现单例对象,具备和Java静态方法同样的功能。
- 定义单例对象 Person,如下所示(person.scala):
object Person { private var lastId = 0 //一个人的身份编号 def newPersonId() = { lastId +=1 lastId } } printf("The first person id is %d.\n",Person.newPersonId()) printf("The second person id is %d.\n",Person.newPersonId()) printf("The third person id is %d.\n",Person.newPersonId())
- 运行结果
- 运行结果
-
伴生对象
- 在Java中,经常需要用到同时包含实例方法和静态方法的类,在Scala中,我们通过伴生对象来实现。
- 当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”;类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。
- 实例(obj.scala):
class Person{ private val id = Person.newPersonId() //伴生对象方法 private var name = "" def this(name: String){ // 辅助构造器 this() this.name = name } def info(){ printf("The id of %s is %d.\n", name, id) } } object Person{ private var lastId = 0 private def newPersonId() = { lastId += 1 lastId } def main(args: Array[String]){ val lishi = new Person("Lishi"); val zhangsan = new Person("Zhangsan") lishi.info() zhangsan.info() } }
-
编译:scalac obj.scala
-
运行:scala -classpath . Person
-
运行结果:
- Scala源代码编译后都会变成JVM字节码,实际上,在编译上面的源代码文件以后,在Scala里面的class和object在Java层面都会被合二为一,class里面的成员成了实例成员,object成员成了static成员。
- 把伴生对象Person的定义中newPersonId()的private修饰符去掉(如果不去掉,作为伴生对象的私有方法,在javap反编译后,在执行结果中是看不到这个方法newPersonId()的),编译,然后反编译Person.class,我们可以看到:经过编译后,伴生类Person中的成员和伴生对象Person中的成员都被合并到一起,并且,伴生对象中的方法newPersonId(),成为静态方法。
-
-
应用程序对象
- 每个Scala应用程序都必须从一个对象的main方法开始(HelloWorld);
- 上述HelloWorld是先编译后执行的,但是,在Scala代码中若没有定义类,就是一个单例对象,因此,可以不用编译,直接使用scala命令运行即可得到结果。
-
apply方法和update方法
- 在Scala中,apply方法和update方法都会遵循相关的约定被调用:
- 用括号传递给变量(对象)参数时,Scala 会把它转换成对apply方法的调用;
- 由于Scala的Array对象定义了apply方法,故可以通过如下方式初始化一个数组:
- val myStrArr = Array(“BigData”,“Hadoop”,“Spark”)
- Scala伴生对象的重要用途:通常将伴生对象作为工厂使用,就不需要使用关键字new来创建实例化对象
- vim apply_usage.scala
class Car(name: String){ def info() {println("Car name is "+ name)} } object Car { // apply方法会调用伴生类Car的构造方法创建一个Car类的实例化对象 def apply(name: String) = new Car(name) } object MyTest{ def main (args: Array[String]) { // 这里会调用伴生对象中的apply方法,apply方法会创建一个Car类的实例化对象 val mycar = Car("BMW") mycar.info() } }
- vim apply_usage.scala
- 运行结果:
- 由于Scala的Array对象定义了apply方法,故可以通过如下方式初始化一个数组:
- 当对带有括号并包括一到若干参数的对象进行赋值时,编译器将调用对象的update方法,在调用时,是把括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用;
- 声明一个长度为3的字符串数组myStrArr,每个数组元素初始化为null:
val myStrArr = new Array[String](3)
- myStrArr赋值:
- myStrArr(0) = “BigData”
- 实际上,调用了伴生类Array中的update方法,执行myStrArr.update(0,“BigData”)
- myStrArr(1) = “Hadoop”
- 实际上,调用了伴生类Array中的update方法,执行myStrArr.update(1,“Hadoop”)
- myStrArr(2) = “Spark”
- 实际上,调用了伴生类Array中的update方法,执行myStrArr.update(2,“Spark”)
- myStrArr(0) = “BigData”
- 声明一个长度为3的字符串数组myStrArr,每个数组元素初始化为null:
- 用括号传递给变量(对象)参数时,Scala 会把它转换成对apply方法的调用;
- 在Scala中,apply方法和update方法都会遵循相关的约定被调用:
附:暂略
3、继承
-
Scala中的继承与Java的区别:
- 重写一个非抽象方法必须使用override关键字;
- 只有主构造器可以调用超类的主构造器;
- 在子类重写超类的抽象方法时,不需要使用override关键字;
- Scala可以重写超类中的字段;
Scala 同样不允许多继承。
-
Scala 抽象类
- 定义一个抽象类,需要使用关键字abstract;
- 定义一个抽象类的抽象方法,不需要关键字abstract,不写方法体即可;
- 抽象类中定义的字段,只要没有给出初始化值,就表示是一个抽象字段,但是,抽象字段必须要声明类型,比如:val carBrand: String,就把carBrand声明为字符串类型,这个时候,不能省略类型,否则编译会报错。
abstract class Car{ val carBrand: String def info() def greeting() {println("Welcome to my car!")} }
-
扩展类
- 抽象类不能直接被实例化,需要定义几个抽象类的扩展类,或者说继承抽象类;
- 重写超类字段,需要使用override关键字,否则编译会报错;
- 重写超类的抽象方法时,不需要使用override关键字(但是,加上override编译也不错报错);
- 重写超类的非抽象方法,必须使用override关键字;
- 下面的 BMWCar 类扩展了Car抽象类:
class BMWCar extends Car { override val carBrand = "BMW" def info() {printf("This is a %s car. It is on sale", carBrand)} override def greeting() {println("Welcome to my BMW car!")} }
- 下面是抽象类、扩展类的运行实例:
abstract class Car{ val carBrand: String def info() def greeting() {println("Welcome to my car!\n")} } class BMWCar extends Car { override val carBrand = "BMW" def info() {printf("This is a %s car. It is on sale\n", carBrand)} override def greeting() {println("Welcome to my BMW car!\n")} } object TheCar{ def main(args: Array[String]){ val car = new BMWCar() car.info() car.greeting() } }
- 运行结果
4、特质(trait)
-
引入
Java中提供了接口,允许一个类实现任意数量的接口。在Scala中没有接口的概念,而是提供了“特质(trait)”,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,却可以实现多个特质,从而重用特质中的方法和字段,实现了多重继承。
-
特质
- 特质定义使用关键字trait:
- 注意,抽象方法不需要使用abstract关键字,特质中没有方法体的方法,默认就是抽象方法;
- 特质可以包含具体实现:即,特质中的字段和方法不一定要是抽象的
- 特质定义使用关键字trait:
-
特质简单应用
- 特质混入类:extends与with(使用extends关键字混入第1个特质,后面可以反复使用with关键字混入更多特质)
- vim trait_try.scala
- 定义两个特质CarId与CarGreeting:
trait CarId{ var id: Int def currentId(): Int //定义了一个抽象方法 } trait CarGreeting{ def greeting(msg: String) {println(msg)} }
- 特质混入:
class BYDCarId extends CarId with CarGreeting{ override var id = 10000 def currentId(): Int = {id += 1; id} } class BMWCarId extends CarId with CarGreeting{ override var id = 20000 def currentId(): Int = {id += 1; id} }
- main方法:
object MyCar { def main(args: Array[String]){ val myCarId1 = new BYDCarId() val myCarId2 = new BMWCarId() myCarId1.greeting("Welcome my first car.") printf("My first CarId is %d.\n",myCarId1.currentId) myCarId2.greeting("Welcome my second car.") printf("My second CarId is %d.\n",myCarId2.currentId) } }
- scalac trait_try.scala
- scala -classpath . MyCar
- 运行结果:
- 定义两个特质CarId与CarGreeting:
5、模式匹配
-
简单匹配
在模式匹配的case语句中可以使用变量。
-
类型模式
-
守卫(guard)语句
- 可以在模式匹配中添加一些必要的处理逻辑
- 可以在模式匹配中添加一些必要的处理逻辑
-
for表达式中的模式
- 见 Scala编程基础 A map部分
-
case类的匹配
- case类是一种特殊的类,它们经过优化以被用于模式匹配;
- case类是一种特殊的类,它们经过优化以被用于模式匹配;
-
Option类型
- 标准类库中的Option类型用case类来表示那种可能存在、也可能不存在的值。
- 一般而言,对于每种语言来说,都会有一个关键字来表示一个对象引用的是“无”,在Java中使用的是null。Scala融合了函数式编程风格,因此,当预计到变量或者函数返回值可能不会引用任何值的时候,建议使用Option类型。Option类包含一个子类Some,当存在可以被引用的值的时候,就可以使用Some来包含这个值,例如Some(“Hadoop”)。而None则被声明为一个对象,而不是一个类,表示没有值。
- Option类型还提供了getOrElse方法,这个方法在这个Option是Some的实例时返回对应的值,而在是None的实例时返回传入的参数。
Source
中国大学MOOC-厦门大学林子雨-Spark编程基础(侵删)