Scala
面向对象
案例
-
定义类表示矩形(Rectangle),提供对外获取周长(girth)和面积(area)的函数,并且为这个矩形类提供一个子类表示正方形(Square)
package com.fesco.exer object ObjectDemo1 { def main(args: Array[String]): Unit = { val r = new Rectangle(5.2, 8.5)println(r.girth)println(r.area) val s = new Square(4.8)println(s.girth)println(s.area) } } // 定义一个类表示矩形 // 矩形肯定要包含两个属性:height和width // 矩形定义好之后,宽和高不能发生改变 // 定义在构造器中的常量属性,允许在创建对象的时候给值 class Rectangle(val height: Double, val width: Double) { def girth: Double = (height + width) * 2 def area: Double = height * width } // 子类:正方形 class Square(override val width: Double) extends Rectangle(width, width)
抽象类和抽象方法
-
在Scala中,依然是通过
abstract
来定义抽象类。不同的地方在于,Scala中不需要使用abstract
来定义抽象方法;如果一个方法在定义的时候没有函数体,那么默认就是抽象方法,不需要写abstract
-
Scala中,抽象类中还可以定义抽象属性!!!
-
案例
// 定义类表示图形Shape // 凡是图形,都应该有获取面积和周长的方法 // 问题在于,不同的图形,获取面积和周长的方式是不同的 // 那么也就意味着,需要在Shape中提供函数来获取面积和周长 // 但是函数不需要具体的实现,而是由不同的子类来具体实现 abstract class Shape { // 抽象属性// 子类继承抽象类的时候来覆盖这个属性// 抽象属性,就意味着子类必须有这个属性,但是父类不确定这个属性的值val versionUID: Long // 周长def girth: Double // 面积def area: Double }
-
抽象类中可以包含抽象方法和实体方法
-
子类继承抽象类,就需要覆盖其中的抽象函数
package com.fesco.abstractx object AbstractDemo1 { def main(args: Array[String]): Unit = { val s:Shape = new Circle(5)println(s.area)println(s.girth) } } // 定义类表示图形Shape // 凡是图形,都应该有获取面积和周长的方法 // 问题在于,不同的图形,获取面积和周长的方式是不同的 // 那么也就意味着,需要在Shape中提供函数来获取面积和周长 // 但是函数不需要具体的实现,而是由不同的子类来具体实现 abstract class Shape { // 抽象属性// 子类继承抽象类的时候来覆盖这个属性// 抽象属性,就意味着子类必须有这个属性,但是父类不确定这个属性的值val versionUID: Long // 周长def girth: Double // 面积def area: Double } // 圆形 class Circle(val radius: Double) extends Shape { override val versionUID: Long = 4589512675L override def girth: Double = 2 * radius * Math.PI override def area: Double = Math.PI * radius * radius }
伴生对象(object
)
-
在Java中,提供了
static
表示静态,可以通过类来调用静态属性或者静态方法,但是Scala作为一门完全面向对象的语言,认为静态是不合理的,因为静态和对象是无关的,Scala就认为static
破坏了面对象对象的原则,因此Scala干脆不支持static
-
为了实现和
static
类似的效果,在Scala提供了伴生对象(object
)。可以为每一个类提供一个同名的object(伴生对象),定义在伴生对象中的属性和函数,在编译之后会自动变成静态的。此时和object同名的类称之为伴生类// 伴生类 class Person{} // 伴生对象 object Person{}
-
伴生类和伴生对象必须同名,编译的时候才会编译到一个class文件中
package com.fesco.objectx object CalcDemo { def main(args: Array[String]): Unit = { // add可以通过类名来调用println(Calculator.add(5, 8))// test必须通过对象来调用val c = new Calculatorc.test() } } object Calculator { def add(x: Int, y: Int): Int = x + y } class Calculator { def test(): Unit = println("running") }
-
案例:单例模式
package com.fesco.objectx object SingletonDemo { def main(args: Array[String]): Unit = { val a = A.getInstanceprintln(a)val b = B.getInstanceprintln(b) } } // 单例模式:全局过程中只存在唯一实例 // 不能对外创建对象 - 构造器私有化 class A private() // 定义伴生对象 object A {// 提供本类对象,一般不会直接对外操作,所以需要私有化// 饿汉式private val a = new A // 对外提供函数来获取这个对象def getInstance: A = a } class B private() object B {private var b: B = _ // 懒汉式def getInstance: B = {if (b == null) b = new Bb} }
特质/特征(trait
)
-
在Scala中,没有接口的说法,而是提供了类似的机制:
trait
-
定义结构
trait 特质名 {特质体 }
-
不同于Java的地方在于,特质中可以有抽象方法和抽象属性,也可以有实体方法和实体属性
-
在Scala中,一个类可以混入(mixin)多个特质!所以,特质可以看作是对单继承的补充
-
注意:在Scala中,如果一个类本身没有继承父类,那么在混入一个特质的时候需要使用
extends
,混入其他特质的时候使用with
package com.fesco.traitx object TraitDemo { def main(args: Array[String]): Unit = { val s = new Students.study()s.break()s.play() } } trait Study {// 抽象函数def study(): Unit } trait Break {def break(): Unit } trait Play {def play(): Unit } // 没有父类 // 使用第一个特质的时候,需要使用extends关键字 // 类混入特质之后,需要覆盖特质中的抽象方法 // 从第二个特质开始,需要使用with来关联 class Student extends Study with Break with Play { override def study(): Unit = println("学习中~~~") override def break(): Unit = println("休息中~~~") override def play(): Unit = println("玩耍中~~~") }
-
如果一个类本身已经有了父类,那么此时所有的特质只能使用
with
来混入package com.fesco.traitx object TraitDemo2 { def main(args: Array[String]): Unit = { val c = new Circle(5)println(c.girth)println(c.area) } } trait Girth {def girth: Double } trait Area {def area: Double } class Shape {val name: String = "Shape" } // 如果一个类本身已经有了父类,那么此时所有的特质只能使用with来混入 class Circle(val r: Double) extends Shape with Girth with Area { override val name: String = "Circle" override def girth: Double = 2 * Math.PI * r override def area: Double = Math.PI * r * r }
-
需要注意的是,在Scala中,不只是类中可以混入特质,在声明对象的时候也可以单独的混入特质,这种方式称之为动态混入
package com.fesco.traitx object TraitDemo3 { def main(args: Array[String]): Unit = { // Teacher对象 - 是否能够确定这个老师对应的科目// val t: Teacher = new Teacher// 就希望在定义对象的时候,能够顺便指定老师对应的科目val t1: Teacher with Chinese = new Teacher with Chinese {}println(t1.subject)val t2:Teacher with Maths = new Teacher with Maths {}println(t2.subject) } } // 代表老师的类 class Teacher // 语文课程 trait Chinese {def subject: String = "语文" } // 数学课程 trait Maths {def subject: String = "数学" }
-
正因为一个类中可以混入多个特质,且特质中还可以定义实体方法,所以在混入特质的时候就可能会产生冲突
package com.fesco.traitx object TraitDemo4 { def main(args: Array[String]): Unit = { val d = new Dprintln(d.m(5))val e = new Eprintln(e.m(5))val f = new Fprintln(f.m(5)) } } trait A {def m(x: Int): Int = x } trait B {def m(x: Int): Int = x * 2 } trait C {def m(x: Int): Int = x * 3 } // D混入特质A、B、C // 如果不指定,编译的时候会从右到左来依次寻找这个函数,只要能够找到,编译就不报错 // 此时建议在类中重写这个函数 class D extends A with B with C {override def m(x: Int): Int = x * 4 } class E extends A with B with C {// 需要使用B特质的逻辑// 调用特质B中的m函数override def m(x: Int): Int = super[B].m(x) } // 不指定,那么会先使用C中的特质 --- 从右到左 class F extends A with B with C {override def m(x: Int): Int = super.m(x) }
-
注意:如果混入了多个特质,且特质中有同名函数,但是函数的返回值类型不同,那么此时混入后会报错
package com.fesco.traitx object TraitDemo5 { def main(args: Array[String]): Unit = { val t = new T3t.m(5) } } trait T1 {def m(x: Int): Int = x } trait T2 {def m(x: Int): Unit = println(x) } class T3 extends T1 with T2 {// 此时返回值无论是Unit还是Int都会报错// override def m(x: Int): Int = x * 2 }
-
因此,在混入特质的时候,需要检查特质中的同名同参数列表的函数的返回值类型是否一致
-
在Java中,接口之间是可以通过extends来基础,同样,Scala中,trait之间也可以通过extends来继承
trait A trait B extends A
-
正因为特质之间允许被继承,所以存在菱形继承的问题
package com.fesco.traitx object TraitDemo6 { def main(args: Array[String]): Unit = { val s = new SubAprintln(s.m(5)) } } trait SuperA {def m(x: Int): Int = x } // SubA1继承了特质SuperA trait SubA1 extends SuperA {override def m(x: Int): Int = x * 2 } // SubA2继承了特质SuperA trait SubA2 extends SuperA {override def m(x: Int): Int = x * 3 } // 由于SubA1和SubA2有共同的父特质,所以构成了菱形继承关系 // 此时由于子特质SubA1和SubA2中重写了函数,没有调用父特质中的函数 // 所以调用的时候是从右到左来寻找这个函数 class SubA extends SubA1 with SubA2
-
菱形继承中的特质叠加
package com.fesco.traitx object TraitDemo7 { def main(args: Array[String]): Unit = { val s = new SubBprintln(s.repeat("abc")) } } trait SuperB { def repeat(str: String): String = str } trait Sub1 extends SuperB { override def repeat(str: String): String = {println("Sub1 running ~~~")super.repeat(str) * 3} } trait Sub2 extends SuperB { override def repeat(str: String): String = {println("Sub2 running ~~~")super.repeat(str) + str} } // 如果混入了多个特质,且混入的特质之间有公共父特质,那么此时就会形成"特质叠加"现象: // 从右到左,Sub2中的super不是SuperB,而是Sub1,Sub1中的super才是SuperB class SubB extends Sub1 with Sub2
其他
自身类型(self-type)
-
自身类型,指的是在另一个类或者特质中指定一个其他类型,那么这个类或者这个特质的子类或者子特质就需要满足指定类型
class 类名 {别名:类型 =>函数 } // 或者 trait 特质名 {别名:类型 =>函数 }
-
案例
package com.fesco.otherobject SelfTypeDemo {def main(args: Array[String]): Unit = {val r = new Register("Bob", "abc12343", "13547894512")r.register()}}// 代表用户的类 class User(val username: String, val password: String, val phoneNumber: String)// 产生用户数据之后,需要将数据放入数据库中 // 这个类负责和数据库来进行交互 trait UserDao {// UserDao的子类或者子特质需要满足使用User类的要求// 凡是本类中使用到User的地方,都可以用u来表示User对象// u:User =>// 如果需要将User类和本类结合this: User =>def insert(): Unit = println(s"username:${this.username}, password:${this.password}, phone number:${this.phoneNumber}") }// 表示注册的类 class Register(override val username: String, override val password: String, override val phoneNumber: String) extends User(username, password, phoneNumber) with UserDao {def register(): Unit = super.insert() }
APP类
-
APP类是Scala中提供的一个特殊的特质。当一个类混入了APP之后,那么这个类就变成了一个快速启动类,利用这个特质,可以省略主函数的书写
package com.fesco.otherobject APPDemo extends App {println("Hello world")}
type
-
在Scala中,可以通过
type
关键字来定义数据类型,其实就是给这个类起别名package com.fesco.otherobject TypeDemo {def main(args: Array[String]): Unit = {val d1: Double = 3.2println(d1)// 起别名// Double类型的别名就是Dtype D = Doubleval d2: D = 5.87println(d2)}}
枚举类
-
当一个类中有多个固定个数的实例,并且可以一一列举的时候 ,可以定义为枚举。例如:季节、星期、月份等
-
案例
package com.fesco.otherobject EnumDemo {def main(args: Array[String]): Unit = {val w = Week.MONDAYprintln(w)// 获取这个类中的所有的枚举常量,然后放入一个Set集合中返回val values: Week.ValueSet = Week.valuesfor (elem <- values) {println(elem)}}}// 定义一个枚举表示星期 // 在Scala中,没有enum关键字,而是通过object来定义枚举 // 在Scala中,所有的枚举都有一个顶级父类:Enumeration object Week extends Enumeration {// val MONDAY = Value(1)// val MONDAY = Value("Monday")// 实际过程中,习惯上枚举常量都是大写的val MONDAY: Week.Value = Value(1, "Monday")val TUESDAY: Week.Value = Value(2, "Tuesday")val WEDNESDAY: Week.Value = Value(3, "Wednesday")val THURSDAY: Week.Value = Value(4, "Thursday")val FRIDAY: Week.Value = Value(5, "Friday")val SATURDAY: Week.Value = Value(6, "Saturday")val SUNDAY: Week.Value = Value(7, "Sunday")}
密封类(sealed)
-
sealed
是Scala中提供的一个特殊的关键字,可以用于修饰类或者特质。如果sealed
修饰类或者特质,形成如下限制-
被修饰的类或者特质的子类或者子特质必须必须和当前类处在同一个scala文件中,不能跨文件
-
被修饰的类或者特质参与模式匹配的时候,那么会进行检查,如果没有罗列所有的情况,那么会警告
-
-
案例
package com.fesco.other
object SealedDemo { }sealed trait Student {def study(): Unit }class Pupil extends Student {override def study(): Unit = println("小学生学习中") }
-
sealed
增强了封装特性