Scala核心编程_第09章 面向对象编程(高级特性)

静态属性和静态方法

基本介绍

回顾下Java的静态概念

静态方法:public static 返回值类型 方法名(参数列表) {方法体}
静态属性:public static 属性名...

说明: Java中静态方法并不是通过对象调用的,而是通过类对象调用的,所以静态操作并不是面向对象的。

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

Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。

但是为了能够和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.方法 来实现调用

9. 如果不写伴生类只写半生对象,则不仅 有public static final MODULE$ 也有 public class MODULE类。


10.当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化

伴生对象-apply方法

如果我们想要得到某个类的对象实例,而不通过new的方式,而是直接通过 类名(参数) 方式直接来创建对象实例.

那么我们在伴生对象中定义apply方法,可以实现。

class ScalaPerson(var cname:String) { //伴生类
  var name: String = cname
}

object ScalaPerson {
  //伴生对象
  var sex: Boolean = true

  def sayHi(): Unit = {
    println("object ScalaPerson sayHI~~")
  }

  def apply(): ScalaPerson = {
    print("name:==>wqbin")
    new ScalaPerson("wqbin")
  }

  def apply(name:String): ScalaPerson = {
    print("name:==>",name)
    new ScalaPerson(name)
  }
}
object AccompanyObject {
  def main(args: Array[String]): Unit = {
    
    var p= ScalaPerson()
  }
}

scala的特质(trait)

回顾Java接口

声明接口

interface 接口名{}

实现接口

class 类名 implements 接口名1,接口2{ @override 全部抽象方法}

java接口的特点

  1. 在Java中, 一个类可以实现多个接口。
  2. 在Java中,接口之间支持多继承
  3. 接口中属性都是常量且必须初始化,实现子类可选择重写。
  4. 接口中的方法都是抽象的,且不用加abstract 修饰符。实现子类必须全部实现接口中全部抽象方法。
public class InterfaceDemo {
    public static void main(String[] args) {
        SonOfInf son = new SonOfInf();
        System.out.println(son.name);
    }
}

interface inf1{
    public String name="inf1";
    public void f1();
}

interface inf2{
    public String name = null;
    public void f2();
}

class SonOfInf implements inf1,inf2{
    public Object name;

    public void f1() {

    }

    public void f2() {

    }
}

Scala接口的介绍

从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。

Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。 理解trait 等价于(interface + abstract class)

trait 的声明

trait 特质名 {
    trait体
}

trait 特质名:首字母大写.

特质的本质与java接口的区别:

package com.xgo
trait TraitDemo {
  val sex:String
  val birth:Int=1994
  var age:Int
  var name:String="wqbin"
  def eat()
  def say(content :String)
  def cry(): Unit ={
    print("cry in abstract class Person ")
  }
}

1.java接口中不能有实现的方法,只能有抽象的方法与抽象和实现的属性(默认实现也可以),而scalad的trait是可以包含实现和抽象的方法与属性。富接口/富特质:即该特质中既有抽象方法,又有非抽象方法

2.trait的反编译诚java后,衍生出两个:一个是java抽象类,一个是java接口。java抽象类拥有特质的实现方法的同名静态方法和对实现的属性赋值的静态$init$方法,java接口包含特质所有方法和属性并且都是抽象的。

Scala中trait的使用

静态混入与动态混入

Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。

两种使用方式:

静态混入特质:在定义类的时候,类通过extends继承特质,通过with可以继承多个特质

动态混入特质:特质动态混入实例化过程,在实例化类对象的时候,通过动态混入with的方式,不改变继承关系的前提下,获取特质。

特质中没有实现的方法就是抽象方法,实现了的方法就是实现方法。

并且所有的java接口都可以当做Scala特质使用

静态混入

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

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

动态混入

除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能

new 类名() with 特质1 with 特质2 ...

特点:

此种方式也可以应用于对抽象类功能进行扩展,动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。

动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。

注意:

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

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

特质叠加

叠加特质的特点

  1. 构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左。
  2. 混入特质的实例化,与方法执行顺序相反。
  3. 混入特质是一种继承树,从左向右,从父类到子类,跳跃重复路径的构建方法。
  4. 可以理解为引用的嵌套,当执行方法的时候,恰好与构建相反。
trait Operate {
  def insert(id: Int)
}

trait File1 extends Operate {

  //说明
  //1. 如果在子特质中重写/实现了一个父特质的抽象方法,但是同时调用super
  //2. 这时父特质的抽象方法不是完全实现,因此需要声明为 abstract override
  //3. 这时super.insert(id) 的调用就和动态混入顺序有密切关系
  abstract override def insert(id: Int): Unit = {
    println("File1--将数据保存到文件中..")
    super.insert(id)
  }
}

trait DB1 extends Operate { //我们继承Operate,并实现了Operate的insert
  def insert(id: Int): Unit = {
    println("DB1--将数据保存到数据库中..")
  }
}

class MySQL1 {}

object MixInDemo {
  def main(args: Array[String]): Unit = {

    val mysql1 = new MySQL1 with DB1 with File1
    mysql1.insert(666)
    //1.将数据保存到文件中..
    //2. 将数据保存到数据库中..
    
    
    //下面的mysql23混入方法是错误--因为super
//    val mysql2 = new MySQL1 with File1 {
//      override def insert(id: Int): Unit = {
//        println("mysql2--将数据保存到数据库中..")
//      }
//    }
    
//    val mysql3 = new MySQL1 with File1 with DB1

  }
}

叠加特质的注意点:

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

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

方式1 : 去掉 super()...

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

特质构造顺序

特质也是有构造器的,构造器中的内容由"字段的初始化"和一些其他语句构成。

trait AA {
  println("A...")
}

trait BB extends AA {
  println("B....")
}

trait CC extends BB {
  println("C....")
}

trait DD extends BB {
  println("D....")
}

class EE { //普通类
  println("E...")
}

class FF1 extends EE with CC with DD { //先继承了EE类,然后再继承CC 和DD
  println("FF1....")
}

class FF2 extends EE { //KK直接继承了普通类EE
  println("FF2....")
}

静态混入特质:

 val ff1 = new FF1()
 println(ff1)

 

 动态混入特质:

    val ff2 = new FF2 with CC with DD
    println(ff2)

静态混入特质--特质构造顺序

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

动态混入特质--特质构造顺序

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

扩展类的特质--特质继承类

特质可以继承类,以用来拓展该类的一些功能

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

//UnExceptException 就是Exception的子类.
class UnExceptException extends LoggedException {
  // 已经是Exception的子类了,所以可以重写方法
  override def getMessage = "错误消息!"
}

object TraitExtendDemo {
  def main(args: Array[String]): Unit = {
    var error = new UnExceptException()
    println("error.isInstanceOf[LoggedException]:" + error.isInstanceOf[LoggedException])
    println("error.isInstanceOf[Exception]:" + error.isInstanceOf[Exception])
    print(error.getMessage)
  }
}

  

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

class NoExtendException{}
//LoggedException 是Exception的子类,但是NoExtendException不是Exception的子类
//所以UnExceptExceptionA违背了JVM单继承原则
class UnExceptExceptionA extends NoExtendException with LoggedException {

  override def getMessage = "错误消息!"
}

报错原因:

Error:(28, 57) illegal inheritance; superclass NoExtendException
is not a subclass of the superclass Exception
of the mixin trait LoggedException
class UnExceptExceptionA extends NoExtendException with LoggedException {

 OK!!如下:

class UnExceptExceptionB extends IndexOutOfBoundsException with LoggedException {
  override def getMessage = "错误消息!"
}

IndexOutOfBoundsException与LoggedException都属于Exception的子类。

自身类型

自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。

举例说明自身类型特质,以及如何使用自身类型特质:

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

正确:

class ConsoleException extends Exception with LoggerException

错误:

class Console extends  LoggerException {} 

  

原文地址:https://www.cnblogs.com/wqbin/p/13027056.html