Scala面向对象编程(高级部分)

1. 静态属性和静态方法

(1)回顾Java中的静态概念

public static 返回值类型 方法名(参数列表) {方法体} 静态属性… 说明:
Java中静态方法并不是通过对象调用的,而是通过类对象调用的,所以静态操作并不是面向对象的。

(2)Scala中静态的概念-伴生对象

①Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。也没有static关键字, 但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
  ②伴生对象的小结

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.方法
来实现调用

(3)伴生对象apply方法

在伴生对象中定义apply方法,可以实现: 类名(参数) 方式来创建对象实例.

class Child private (var name: String, var age: Int, var address: String) {
Child.nums += 1
}
object Child {
var nums: Int = _
// apply方法会通过类(实参列表)调用到
def apply(name: String, age: Int, address: String): Child = {
println("apply()...")
new Child(name, age, address)
}
}

object Test {
def main(args: Array[String]): Unit = {
//加入apply方法后可以将主构造器加上private修饰,这样就
//只能通过apply方法调用来生成对象了
//val child1 = new Child("小花1", 10, "昌平")
//println(child1.name)
val array1 = new Array[Int](8)
val array2 = Array(1, 3, 8, 7)

val child2 = Child("小明", 5, "朝阳")// 自动调用apply方法
println(child2.name)
}
}

2. 单例模式

定义:保证在整个的软件系统中,某个类只能存在一个对象实例。
应用场景:

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。
Akka [ActorySystem 单例] AKKA

Scala中没有静态的概念,所以为了实现Java中单例模式的功能,可以直接采用类对象(即伴生对象)方式构建单例对象

方式一:懒汉式

object TestSingleTon extends App{
val singleTon = SingleTon.getInstance
val singleTon2 = SingleTon.getInstance
println(singleTon.hashCode() + " " + singleTon2.hashCode())
}
//将SingleTon的构造方法私有化
class SingleTon private() {}

object SingleTon {
private var s:SingleTon = null
def getInstance = {
if(s == null) {
s = new SingleTon
}
s
}
}
object TestSingleTon extends App {
val singleTon = SingleTon.getInstance
val singleTon2 = SingleTon.getInstance
println(singleTon.hashCode() + " ~ " + singleTon2.hashCode())
println(singleTon == singleTon2)
}
//将SingleTon的构造方法私有化
class SingleTon private() {
println("~~~")
}
object SingleTon {
private val s: SingleTon = new SingleTon
def getInstance = {
s
}
}
//说明:饿汉式

3.接口

(1)回顾Java接口

声明接口
   interface 接口名
实现接口
   class 类名 implements 接口名1,接口2
(1)在Java, 一个类可以实现多个接口。
(2)在Java中,接口之间支持多继承
(3)接口中属性都是常量
(4)接口中的方法都是抽象的

(2)Scala接口的介绍

从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口, 也没有implements关键字。Scala语言中,采用trait(特质,特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。

(3)特质

trait 的声明
    trait 特质名 {
       trait体
     }**

trait 命名 一般首字母大写. Cloneable , Serializable
object T1 extends object T1 extends Serializable { } ,其中 Serializable 就是scala的一个特质。
在scala中,java中的接口可以当做特质使用

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接

没有父类:
    class 类名 extends 特质1 with 特质2 with 特质3 ..
有父类 :
   class 类名 extends 父类 with 特质1 with 特质2 with 特质3

(4)特质的再说明

Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。
特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
所有的java接口都可以当做Scala特质使用

abstract class Pet(var name:String, var age: Int, var weight: Double) {
var id: Int // 抽象的属性
// 普通方法
// 抽象方法, 没有=和方法体
def eat
override def toString(): String = {
name + "," + age + "," + weight
}
}
class Dog extends Pet("小黄", 2, 2.5) {
    // Pet(实参列表)作用是调用父类构造器,完成属性的初始化
var id = 20
override def eat = println("吃狗粮")
     // 如果是方法的实现, 可以省略override
def kanjia(): Unit = {
println("看家狗")
}
}
class Bird(name:String, age: Int, weight: Double)
                        extends Pet(name, age, weight) {
var id = 30
def eat = println("吃虫子")
}

object PetTest {
def main(args: Array[String]): Unit = {
//new Pet("小黑", 2, 20)
val dog = new Dog
val bird = new Bird("小飞", 1, 0.2)
println(dog)
println(bird)

var v : Pet = new Dog() // 多态
v = bird
//((Dog)v).kanjia
if (v.isInstanceOf[Dog]) {
v.asInstanceOf[Dog].kanjia // 造型
}
}
}

(5)带有特质的对象:动态混入
1.除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展对象的功能
2.此种方式也可以应用于对抽象类功能进行扩展
3.动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。
4.动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。

//写一个特质Flyer, 写两个抽象方法takeOff, fly, 具体方法land
trait Flyer {
var name:String // 这是抽象属性, 在特质中变成抽象方法
var speed: Int = 2000 // 普通属性, 在特质中变成抽象方法

def takeOff():Unit
def fly
def land = println("我要着陆了....")
}
//写一个子类Plane, 再混入其他特质 with后面只允许特质, extends后面可以是特质也可以是类
//在类中使用with是静态混入, 只要创建本类对象,都具备这些特质
class Plane extends Flyer with Serializable with Comparable[Plane] {
var name = "大飞机" // 覆盖属性
// 在子类中重新定义
// 特质中的普通属性, 在子类中也仍然需要重新定义, 提供了set方法, 会被特质的特殊类调用.
def takeOff(): Unit = println("飞机起飞中, 推背感强")
override def fly: Unit = println("平稳飞行中...")
override def compareTo(o: Plane) = 0
}

class Man {
type T
}

class Chinese extends Man {
override type T = String
}
class English extends Man {
override type T = Double
}

object TraitExer {

def main(args: Array[String]): Unit = {
type P = Plane
var p: P = new P
println(p)
p.fly
}

def main3(args: Array[String]): Unit = {
val man1 = new Man
// 在创建对象时使用with特质, 也可以实现混入,称为动态混入, 混入的特质只对当前对象有效
val man2 = new Man with Flyer with Serializable {
var name = "abc"
override def takeOff(): Unit = println("不知怎么就会飞了")
override def fly: Unit = println("引力对我没用")
}
man2.fly

val man3 = new Man{} // 匿名内部类

}

def main2(args: Array[String]): Unit = {
val plane = new Plane
plane.takeOff()
plane.land

val flyer2 = new Flyer {
var name = "yyy"
override def takeOff(): Unit = println("lll")
override def fly: Unit = println("fly")
}

}
}

(6)叠加特质
构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左。

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 = {
print("向数据库")
super.insert(id)
}
}
trait File4 extends Data4 {
println("File4")
override def insert(id : Int): Unit = {
print("向文件")
super.insert(id)
}}
class MySQL4 {}
// 1.Scala在叠加特质的时候,会首先从后面的特质开始执行
// 2.Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
val mysql = new MySQL4 with DB4 with File4
//val mysql = new MySQL4 with File4 with DB4
mysql.insert(888)

上述程序执行结果为:

Operate4...
Data4
DB4
File4
向文件向数据库插入数据 = 888

debug整个过程发现,最后一行程序执行时先到了trait File4中的insert方法,然后又去了DB4的insert方法,最后去了Data4的insert方法,执行打印语句完毕后按逆顺序出栈

叠加特质
叠加特质注意事项和细节
1.特质声明顺序从左到右。
2.Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
3.Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
4.如果想要调用具体特质的方法,可以指定:super[特质].xxx().其中的泛型必须是该特质的直接超类类型

将上述程序中Trait File 改为以下内容:

trait File4 extends Data4 {
println("File4")
override def insert(id : Int): Unit = {
print("向文件")
super[Data4].insert(id)
}
}

此时执行结果为:

Operate4...
Data4
DB4
File4
向文件插入数据 = 888

(7)在特质中重写抽象方法
方式1 : 去掉 super()…
方式2: 调用父特质的抽象方法,那么在实际使用时,没有方法的具体实现,无法编译通过,为了避免这种情况的发生。可重写抽象方法,这样在使用时,就必须考虑动态混入的顺序问题。

理解 abstract override 的小技巧分享:可以这里理解,当我们给某个方法增加了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 {}
val mysql5 = new MySQL5 with DB5 with File5

重写抽象方法时需要考虑混入特质的顺序问题和完整性问题 看4个案例,并判断结果。
var mysql2 = new MySQL5 with DB5 // ok
mysql2.insert(100)
var mysql3 = new MySQL5 with File5 // error
mysql2.insert(100)
var mysql4 = new MySQL5 with File5 with DB5// error
mysql4.insert(100)
var mysql4 = new MySQL5 with DB5 with File5// ok
mysql4.insert(100)

富接口概念:即该特质中既有抽象方法,又有非抽象方法

trait Operate {
def insert( id : Int ) //抽象
def pageQuery(pageno:Int, pagesize:Int): Unit = { //实现
println("分页查询")
}
}

特质中的具体字段

特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。

特质中的抽象字段

特质中未被初始化的字段在具体的子类中必须被重写。

特质构造顺序

特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”

第一种特质构造顺序(声明类的同时混入特质)
调用当前类的超类构造器
第一个特质的父特质构造器
第一个特质构造器
第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
第二个特质构造器
…重复4,5的步骤(如果有第3个,第4个特质)
当前类构造器
第2种特质构造顺序(在构建对象时,动态混入特质)
调用当前类的超类构造器
当前类构造器
第一个特质构造器的父特质构造器
第一个特质构造器.
第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
第二个特质构造器
…重复5,6的步骤(如果有第3个,第4个特质)
当前类构造器
分析两种方式对构造顺序的影响
第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。

扩展类的特质

特质可以继承类,以用来拓展该类的一些功能(和java的不同)

trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage()) // 方法来自于Exception类
}
}

所有混入该特质的类,会自动成为那个特质所继承的超类的子类

trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage()) // 方法来自于Exception类
}
}
//UnhappyException 就是Exception的子类.
class UnhappyException extends LoggedException{
// 已经是Exception的子类了,所以可以重写方法
override def getMessage = "错误消息!"
}

如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误。
Scala面向对象编程(高级部分)_第1张图片

自身类型

自身类型(self-type):主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
应用案例举例说明自身类型特质,以及如何使用自身类型特质

//Logger就是自身类型特质
trait Logger {
// 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用
this: Exception =>
def log(): Unit ={
// 既然我就是Exception, 那么就可以调用其中的方法
println(getMessage)
}
}

4. type关键字

使用type关键字可以定义新的数据类型名称
本质上就是类型的一个别名

type S = String
var v : S = “abc”
def test() : S = “xyz”

5. 嵌套类

在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。
嵌套类类似于Java中的内部类。

public class TestJavaClass {
public static void main(String[] args) {
//创建一个外部类对象
OuterClass outer1 = new OuterClass();
//创建一个外部类对象
OuterClass outer2 = new OuterClass();
// 创建Java成员内部类
// 说明在Java中,将成员内部类当做一个属性,因此使用下面的方式来创建 outer1.new InnerClass().
OuterClass.InnerClass inner1 = outer1.new InnerClass();
OuterClass.InnerClass inner2 = outer2.new InnerClass();

//下面的方法调用说明在java中,内部类只和类型相关,也就是说,只要是
//OuterClass.InnerClass 类型的对象就可以传给 形参 InnerClass ic
inner1.test(inner2);
inner2.test(inner1);

// 创建Java静态内部类
// 因为在java中静态内部类是和类相关的,使用 new OuterClass.StaticInnerClass()
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
}}
class OuterClass { //外部类
class InnerClass { //成员内部类
public void test( InnerClass ic ) {
System.out.println(ic);
}}

static class StaticInnerClass { //静态内部类
}}

scala嵌套类的使用1:

class ScalaOuterClass {
class ScalaInnerClass { //成员内部类
}
}
object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}
val outer1 : ScalaOuterClass = new ScalaOuterClass();
val outer2 : ScalaOuterClass = new ScalaOuterClass();

// Scala创建内部类的方式和Java不一样,将new关键字放置在前,使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
//创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)

scala嵌套类的使用2:

请编写程序,在内部类中访问外部类的属性和方法两种方法。
方式一:

class ScalaOuterClass {
var name : String = "scott"
private var sal : Double = 1.2
class ScalaInnerClass { //成员内部类
def info() = {
// 访问方式:外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass
        这个外部类的一个实例,
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别,学习java的同学可能更容易理解
         ScalaOuterClass.class 的写法.
println("name = " + ScalaOuterClass.this.name
+ " age =" + ScalaOuterClass.this.sal)
}
}
}
object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}
//调用成员内部类的方法
inner1.info()

方式二:
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即:访问方式:外部类名别名.属性名 【外部类名.this 等价 外部类名别名】

class ScalaOuterClass {
myOuter => //这样写,你可以理解成这样写,myOuter就是代表外部类的一个对象.
class ScalaInnerClass { //成员内部类
def info() = {
println("name = " + ScalaOuterClass.this.name
+ " age =" + ScalaOuterClass.this.sal)
println("-----------------------------------")
println("name = " + myOuter.name
+ " age =" + myOuter.sal)
}}
// 当给外部指定别名时,需要将外部类的属性放到别名后.
var name : String = "scott"
private var sal : Double = 1.2
}

object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}
inner1.info()类型投影
先看一段代码,引出类型投影


class ScalaOuterClass3 {
myOuter =>
class ScalaInnerClass3 { //成员内部类
def test(ic: ScalaInnerClass3): Unit = {
System.out.println(ic)
}
}
}

分析以下代码正确/错误:

object Scala01_Class {
    def main(args: Array[String]): Unit = {
        val outer1 : ScalaOuterClass3 = new ScalaOuterClass3();
        val outer2 : ScalaOuterClass3 = new ScalaOuterClass3();
        val inner1 = new outer1.ScalaInnerClass3()
        val inner2 = new outer2.ScalaInnerClass3()
        inner1.test(inner1) // ok
        inner1.test(inner2) // error 原因是scala内部类对象和外部类对象相关.
        //这时可以使用类型投影来屏蔽类型不同
}
}

解决方式-使用类型投影
类型投影是指:在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)

你可能感兴趣的:(scala,开发语言,后端)