scala学习笔记10:面向对象(高级)

58 阅读7分钟

scala学习笔记


前言


一、静态属性与静态方法

Java的可以通过static 定义静态属性和静态方法,

public static  返回值类型 方法名( 参数列表) { 方法体}

通过类名.方法名直接调用而不用实例化或者说通过具体对象调用的,所以JAVA的静态操作并不是面向对象的。 Scala语言是完全面向对象( 万物皆对象)的语言,所以没有静态的概念和操作,但是为了能够和Java语言交互,就产生了一种特殊的对象来模拟类对象 ,我们称之为类的伴生对象。所有静态内容都可以放置在它的伴生对象中声明和调用。

//伴生类,编译后生成Dog.class文件
class Dog{
   var name : String = _
 }
//伴生对象,里面全是静态内容,编译后生成Dog$.class文件
object Dog{
   var sex : Boolean = true
    def eat(food:String)={
      println(name+"like eat "+food)
    }
  //在伴生对象中定义apply方法,可以实现,类名(参数)方式来创建对象实例
   def apply(name:String): Dog = {
      println("apply 被调用")
      new Dog(name)
    }
}


//不用实现类对象,自己使用伴生对象调用方法和属性
println(Dog.name)
println (Dog.eat("food"))
//类名(参数)方式来创建对象实例
val nancy = Dog.apply("nancy.pelosi")
  1. Scala中伴生对象采用object关键字声明,伴生对象中声明的全是 " 静态" 内容,可以通过 伴生对象名称直接调用。
  2. 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
  3. 伴生对象中的属性和方法都可以通过伴生对象名( 类名) 直接调用访问
  4. 从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的
  5. 伴生对象的声明应该和伴生类的声明在 同一个源码文件中,如果不在同一 个文件中会运行错误。 6.在伴生对象中定义apply方法,可以实现,类名(参数)方式来创建对象实例

二、接口与特质

1.trait定义

Java中的接口

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

在Scala中,没有接口,而采用特质trait(特征),等价于(interface + abstract class),特质可以同时拥有抽象方法和具体方法(对比:java接口中全是抽象方法),一个类可以实现/继承多个特质。

trait 的声明

trait  特质名(首字母大写) {
     trait
}

在scala中,java中的接口可以当做特质使用

2.trait的使用

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

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

3.动态混入

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

class oracledb {}
abstract class mysqldb{}
//创建对象时拓展特质
var oracle = new oracledb with Operate3
oracle.insert(999)
//抽象类时创建拓展特质
val mysql = new mysqldb with Operate3
mysql.insert(4)

4.叠加特质

同时如果混入多个特质,称之为 叠加特质,那么 特质声明顺序从左到右 ,方法执行顺序 从右到左 注意事项和细节

  1. 特质声明顺序从左到右
  2. Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
  3. Scala中特质中如果调用super,并不是表示调用父特质的方法,而是 向前 面(左边)继续查找特质,如果找不到,才会去父特质查找**
  4. 如果想要调用具体特质的方法,可以指定: super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
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)
   //如果想要调**用具体特质的方法,可以指定:
   //super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型*
    //super[Data4].insert(id)
  }}
  
class MySQL4 {}

  def main(args: Array[String]): Unit = {
  
    //特质堆叠顺序 Operate4>Data4>DB4>File4
    
    val mysql = new MySQL4 with DB4 with File4
    mysql.insert(888)
//    Operate4...
//    Data4
//    DB4
//    File4
//    向文件向数据库插入数据 = 888
//从结果看:先调用了file4,后调DB4,从右向左,或者说从后像前

val mysql = new MySQL4 with File4 with DB4
    mysql.insert(999)
//    Operate4...
//    Data4
//    File4
//    DB4
//    向数据库向文件插入数据 = 999
//从结果看:先调用了DB4,后调File4,从右向左,或者说从后像前
  }

5.在特质中重写抽象方法特例

trait Operate5 {
  def insert(id : Int)
}
trait File5 extends Operate5 {
  def insert( id : Int ): Unit = {
    println("将数据保存到文件中..")
    super.insert(id)
  }
}

这里会报错,因为super().insert 调用了Operate5的抽象方法。 在这里插入图片描述 解决办法: 方式1 : 去掉 super()... 方式2: 调用父特质的抽象方法,那么在实际使用时,没有方法的具体实现,无法编译通过,为了避免这种情况的发生。 可重写抽象方法,这样在使用时,就必须考虑动态混入的顺序问题

abstract override 就是明确的告诉编译器,该方法确实是重写了父特质的抽象方法,但是重写后,该方法仍然是一个抽象方法(因为没有完全的实现, 需要其它特质继续实现[ 通过混入顺序])

trait File5 extends Operate5 {
  abstract override def insert( id : Int ): Unit = {
    println("将数据保存到文件中..")
    super.insert(id)
  }

重写抽象方法时需要考虑混入特质的 顺序问题和完整性问题

object traitTest2 {
  def main(args: Array[String]): Unit = {
  
    var mysql2 = new MySQL5 with DB5 //ok,实现了抽象方法
    mysql2.insert(100)
    var mysql3 = new MySQL5 with File5 //error ,还是一个抽象方法,没有完全实现
    mysql3.insert(100)
    var mysql4 = new MySQL5 with File5 with DB5// error //先执行DB5,后执行Files5,调用super依然是抽象方法
    mysql4.insert(100)

    var mysql5 = new MySQL5 with DB5 with File5 // ok,没有问题,先调用FILE5,其中super实际是DB5中的insert,所有没有问题
    mysql5.insert(100)

  }

}
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{}

6.特质构造顺序

第一种:声明类的混入特质

  1. 调用当前类的超类构造器
  2. 第一个特质的父特质构造器
  3. 第一个特质构造器
  4. 第二个特质构造器的父特质构造器, 如果已经执行过, 就不再执行
  5. 第二个特质构造器
  6. .......重复4,5的步骤(如果有第3个,第4个特质)
  7. 当前类构造器

第二种:在构建对象时,动态混入特质

  1. 调用当前类的超类构造器 2) 当前类构造器
  2. 第一个特质构造器的父特质构造器
  3. 第一个特质构造器.
  4. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
  5. 第二个特质构造器
  6. .......重复5,6的步骤(如果有第3个,第4个特质) 8) 当前类构造器

7.扩展类的特质

特质可以继承类,以用来拓展该类的一些功能,所有混入该特质的类,会自动成为那个特质所继承的超类的子类(间接继承)。

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

但是 如果混入该特质的类,已经继承了另一个类(A 类) ,则要求A 类是特质超类的子类,否则就会出现了 多继承现象 ,发生错误

四、嵌套类

略,后续补充