Scala的面向对象编程
一、类与对象
1,定义
[修饰符] class 类名 { 类体 }
1) scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public),[修饰符在后面再详解].
2) 一个Scala源文件可以包含多个类, 每个类默认都是public
2,属性
1)属性的定义语法同变量,示例:[访问修饰符] var 属性名称 [:类型] = 属性值 2)属性的定义类型可以为任意类型,包含值类型或引用类型[案例演示] 3)Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略(这点和Java不同)。 4)如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型. 5)如果在定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值, 这时要求属性必须给定数据类型。
3,创建对象
val | var 对象名 [:类型] = new 类型()
1)如果我们不希望改变对象的引用(即:内存地址), 应该声明为val性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。 2)scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面new 对象类型有继承关系即多态时,就必须写了
4,方法(参考上一个章节函数)
二、构造器
1,简介
1)和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法(即scala中构造器也支持重载)。 2)Scala类的构造器包括: 主构造器(一个) 和 辅助构造器(多个)
2,基本语法
class 类名(形参列表) { // 主构造器 // 类体 def this(形参列表) { // 辅助构造器,所有的辅助构造器必须调用主构造器 } def this(形参列表) { //辅助构造器可以有多个... } }
3,注意事项
1)Scala构造器作用是完成对新对象的初始化,构造器没有返回值。 2)主构造器的声明直接放置于类名之后 3)主构造器会执行类定义中的所有语句(把类中写的语句放入到主构造器),这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别。 4)如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略 5)辅助构造器名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分, 在底层就是构造器重载,辅助构造器无论是直接或间接,最终都一定要调用主构造器,执行主构造器的逻辑, 而且调用主构造器语句一定要放在辅助构造器的第一行6)如果想让主构造器变成私有的,可以在()之前加上private,这样用户不能直接通过主构造器来构造对象了【反编译】 7)辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)
4,属性
1)Scala类的主构造器的形参:未用任何修饰符修饰,则为局部变量;val关键字修饰,为scala类的私有只读属性;var关键字修饰,为可读写的成员变量。 2)给某个属性加入@BeanPropetry注解后,会生成getXXX和setXXX的方法,并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存。
三、包
1,作用
1) 区分相同名字的类 2) 当类很多时,可以很好的管理类 3) 控制访问范围
2,命名
1)只能包含数字、字母、下划线、小圆点.,但不能用数字开头, 也不要使用关键字。 2)一般是小写字母+小圆点一般是:com.公司名.项目名.业务模块名
3,scala会自动引包:java.lang;scala包;Predef包
4,包对象
//为了弥补包中不能直接定义函数/方法 和 变量的不足,scala提供包对象的概念来解决//在底层包对象(package object scala) 会生成两个类 class package class package$ package object mypackage { var name = "aaa" //变量 def hello(): Unit = { //方法 println("package object scala hello()") } }
四、面向对象
1,抽象
我们在前面去定义一个类时候(oo),实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
class用abstract修饰,方法就是没有方法体:def aaa()
2,封装、继承和多态
方法重写用override关键字:override def clone(): AnyRef = super.clone()
3,动态绑定(待补充)
4,isInstanceOf、asInstanceOf、classOf
val t = new MyTest val bool: Boolean = t.isInstanceOf[MyTest] if (bool) { val test: MyTest = t.asInstanceOf[MyTest] } //得到class对象类似于java的类名.class val clazz: Class[MyTest] = classOf[MyTest]
5,伴生类和伴生对象
1)Scala中伴生对象采用object关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生对象名称直接调用。 2)伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。伴生类中的变量可当做成员变量,而伴生对象中的变量可看作静态变量。 3)伴生对象中的属性和方法都可以通过伴生对象名直接调用访问 4)从语法角度来讲,所谓的伴生对象其实就是类的静态方法和静态变量的集合 5)从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。 6)从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。 7)伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误!),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。 8)如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态"性质的对象[即类对象], 在 object A中声明的属性和方法可以通过 A.属性 和 A.方法 来实现调用
9)在伴生对象中定义apply方法,可以实现: 类名(参数) 方式来创建对象实例. def apply(cAge: Int): Cat = new Cat(cAge)还是调用class Cat的构造方法,apply方法可以重载
6,特质(trait)
a)声明
trait 特质名 {
trait体
}
b)使用
#没有父类 class 类名 extends 特质1 with 特质2 with 特质3 .. #有父类 class 类名 extends 父类 with 特质1 with 特质2 with 特质3
c)动态混入
//创建ClassDemo对象,同时动态混入TraitDemo特质 val myClass= new ClassDemo with TraitDemo { override def traitMethod(): Unit = { println("traitMethod") } }
d)叠加特质
1)特质声明顺序从左到右。 2)Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行 3)Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找 4)如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
object MuTraitDemo { def main(args: Array[String]): Unit = { //1. Operate4... //2. Data4 //3. DB4 //4. File4 val mysql = new MySQL4 with File4 with DB4 //分析问题1: 动态混入时,构建对象实例的顺序是什么? //构建实例时,顺序是从左到右 -》 //分析问题2:动态混入创建对象,在执行方法时,顺序 //是 从右到左执行 // 1. 向数据库 // 2. 向文件 // 3. 插入数据 = 100 println("-----------------------") mysql.insert(100) } } trait Operate4 { println("Operate4...") def insert(id: Int) } trait Data4 extends Operate4 { println("Data4") override def insert(id: Int): Unit = { println("插入数据 = " + id) } } trait DB4 extends Data4 { println("DB4") override def insert(id: Int): Unit = { println("向数据库") super.insert(id) } } trait File4 extends Data4 { println("File4") override def insert(id: Int): Unit = { println("向文件") //理论上应该是调用其父特质的insert //这里的 super 含义: Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找 super.insert(id) //如果我们就是希望去让super指向自己的直接父特质,可以如下操作 //这里的Data4必须是File4 直接父特质 //super[Data4].insert(id) } } //普通的类 class MySQL4 {}
五、隐式转换、隐式函数和隐式值
1,隐式函数
a)定义
隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型
b)案例
//使用隐式函数 implicit def f1(d:Double): Int = { //底层会生成一个方法 f1$1 d.toInt } val n1: Int = 3.4 //=> val n1: Int = f1$1(3.4) val n2: Int = 5.6 println(n1 + "...." + n2 )
c)注意事项
1)隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型)有关。 2)隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别
2,隐式值
a)定义
隐式值也叫隐式变量,将某个形参变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数
b)案例
//这里的 str1就是隐式值 implicit val str1: String = "scala" //1.implicit name: String 就是一个隐式参数 //2.当调用hello的时候,没有传入实参,则编译器会自动的将隐式值关联到name上 def hello(implicit name: String): Unit = { println(name + " hello") } hello //用到隐式值 底层 hello$1(str1) //输出: scala hello
3,隐式类
a)特点
1)其所带的构造参数有且只能有一个 2)隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的(top-level objects) 3)隐式类不能是case class(样例类) 4)作用域内不能有与之相同名称的标识符
b)案例
object ImplicitClass { def main(args: Array[String]): Unit = { //一个隐式类, 可以返回一个隐式类的实例,然后就可以调用隐式类的方法 implicit class DB1(val m: MySQL1) { def addSuffix(): String = { //方法 m + " scala" } def sayHi(): Unit = { println("sayHi..") } def sayHello(): Unit = { println("hello") m.sayOk() } } val mySQL = new MySQL1 mySQL.sayOk() // // 1.底层 DB1$1(mySQL).addSuffix() // 2. DB1$1(mySQL) 返回的是 :ImplicitClass$DB1$2 实例 // 3. 通过返回的 ImplicitClass$DB1$2实例.addSuffix() println(mySQL.addSuffix()) //DB1$1(mySQL).addSuffix() mySQL.sayHi() mySQL.sayHello() } } class MySQL1 { //普通类 def sayOk(): Unit = { println("sayOk") } }
4,隐式解析机制
1)首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况) 2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(这种情况范围广且复杂在使用时,应当尽量避免出现): a) 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。 b) 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。 c) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索。 d) 如果T是个类型注入S#T,那么S和T都会被搜索。