Scala面向对象编程之静态成员、特质(Trait)及嵌套类

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体
     }**

  1. trait 命名 一般首字母大写. Cloneable , Serializable
  2. object T1 extends object T1 extends Serializable { } ,其中 Serializable 就是scala的一个特质。
  3. 在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面向对象编程之静态成员、特质(Trait)及嵌套类_第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)