Scala核心基础二

Scala基础二

#一、类和对象

scala是支持面向对象的,也有类和对象的概念。我们依然可以基于scala语言来开发面向对象的应用程序。

#创建类和对象

#用法
  • 使用class来定义一个类
  • 使用new来创建对象
#示例

创建一个Person类,并创建它的对象

步骤

  1. 创建一个scala项目,并创建一个Object
  2. 添加main方法
  3. 创建类和对象

实现

  1. 在IDEA中创建项目,并创建一个Object(main方法必须放在Object中)
  2. 添加main方法
  3. 创建一个Person类
  4. 在main方法中创建Person类对象

参考代码

object _01ClassDemo {
  // 创建类
  class Person{}

  def main(args: Array[String]): Unit = {
    // 创建对象
    val p = new Person()
    println(p)
  }
}

#简写方式

#用法
  • 如果类是空的,没有任何成员,可以省略{}
  • 如果构造器的参数为空,可以省略()
#示例

使用简写方法重新创建Person类和对象

参考代码

object _02ClassDemo {

  // 创建类,省略花括号
  class Person

  def main(args: Array[String]): Unit = {
    // 创建对象,省略括号
    val person = new Person
  }
}

#1.1 定义和访问成员变量

一个类会有自己的属性,例如:人这样一个类,有自己的姓名和年龄。我们接下来学习在类中定义、和访问成员变量。

#用法

  • 在类中使用var/val来定义成员变量
  • 对象直接使用成员变量名称来访问成员变量

#示例

  1. 定义一个Person类,包含一个姓名和年龄字段
  2. 创建一个名为"张三"、年龄为20岁的对象
  3. 打印对象的名字和年龄

步骤

  1. 创建一个Object,添加main方法
  2. 创建Person类,添加姓名字段和年龄字段,并对字段进行初始化,让scala自动进行类型推断
  3. 在main方法中创建Person类对象,设置成员变量为"张三"、20
  4. 打印对象的名字和年龄

参考代码

object _03ClassDemo {
  class Person {
    // 定义成员变量
    var name = ""
    var age = 0
  }

  def main(args: Array[String]): Unit = {
    // 创建Person对象
    val person = new Person
    person.name = "zhangsan"
    person.age = 20

    // 获取变量值
    println(person.name)
    println(person.age)
  }
}

#1.2 使用下划线初始化成员变量

scala中有一个更简洁的初始化成员变量的方式,可以让代码看起来更加简洁。

#用法

  • 在定义

    var
    

    类型的成员变量时,可以使用

    _
    

    来初始化成员变量

    • String => null
    • Int => 0
    • Boolean => false
    • Double => 0.0
    • 需要注意的是, 使用_来初始化, 必须设置变量类型, 不可自动推导
  • val类型的成员变量,必须要自己手动初始化( 因为不可改动啊, 初始化后没办法改, 所以要手动set)

#示例

  1. 定义一个Person类,包含一个姓名和年龄字段
  2. 创建一个名为"张三"、年龄为20岁的对象
  3. 打印对象的名字和年龄

步骤

  1. 创建一个Object,添加main方法
  2. 创建Person类,添加姓名字段和年龄字段,指定数据类型,使用下划线初始化
  3. 在main方法中创建Person类对象,设置成员变量为"张三"、20
  4. 打印对象的名字和年龄

参考代码

object _04ClassDemo {

  class Person{
    // 使用下划线进行初始化
    var name:String = _
    var age:Int = _
  }

  def main(args: Array[String]): Unit = {
    val person = new Person
    
    println(person.name)
    println(person.age)
  }
}

#1.3 定义成员方法

类可以有自己的行为,scala中也可以通过定义成员方法来定义类的行为。

#定义

在scala的类中,也是使用def来定义成员方法

#示例

  1. 创建一个Customer类

    img

  2. 创建一个该类的对象,并调用printHello方法

步骤

  1. 创建一个Object,添加main方法
  2. 创建Customer类,添加成员变量、成员方法
  3. 在main方法中创建Customer类对象,设置成员变量值(张三、男)
  4. 调用成员方法

参考代码

object _05ClassDemo {

  class Customer {
    var name:String = _
    var sex:String = _

    // 定义成员方法
    def sayHi(msg:String) = {
      println(msg)
    }
  }

  def main(args: Array[String]): Unit = {
    val customer = new Customer
    customer.name = "张三"
    customer.sex = "男"
    customer.sayHi("你好")
  }
}

#1.4 访问修饰符

和Java一样,scala也可以通过访问修饰符,来控制成员变量和成员方法是否可以被访问。

#定义

Java中的访问控制,同样适用于scala,可以在成员前面添加private/protected关键字来控制成员的可见性。但在scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的

#案例

  • 定义一个Person类

    img

  • 注: 我们用-号表示 外部不可访问的, +号表示外部可以访问的

  • 在main方法中创建该类的对象,测试是否能够访问到私有成员

参考代码

object _02AccessDemo {

  class Person {
    // 定义私有成员变量
    private var name:String = _
    private var age:Int = _

    def getName() = name
    def setName(name:String) = this.name = name
    def getAge() = age
    def setAge(age:Int) = this.age = age

    // 定义私有成员方法
    private def getNameAndAge = {
      name -> age
    }
  }

  def main(args: Array[String]): Unit = {
    val person = new Person
    person.setName("张三")
    person.setAge(10)

    println(person.getName())
    println(person.getAge())
  }
}

Note拓展:

  1. Scala的private比Java更严格, java中, 外层类成员可以访问内部类的私有成员, 但是Scala不可以

    object _06AccessDemo_2 {
      // Define person class
      class Outer{
        private def f(){}
        class Inner{
          private var name: String = ""
          f()					// 正确, 内访外ok
        }
        (new Inner).name		// 错误, 外访内 报错
      }
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

  2. Protected, 同样比Java中更加严格, java中允许相同的包名下可以访问, 但是Scala只允许子类访问

    object _06AccessDemo_3 {
      class Person{
        protected var name: String = ""	// 父类保护成员
      }
    
      class Programmer extends Person{	// 我们后面会学到继承
        super.name						// 子类可以访问
      }
    
      class Monkey{
        val person = new Person
        person.name						// 同包名下的其他类不可访问(java可以)
      }
    }
    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    大家记住这些即可. 自己练习一两下.

    一般不常用.

// java中的 私有,外访内 内访外
public class A{
    public static void main(String[] args){
        new Outer().getInnerName();
    }
}

class Outer{

    private String name = "out";
    public void getInnerName() {
        System.out.println(new Inner().innerName);
        new Inner().getOutName();
    }
    class Inner{
        private String innerName = "Inner";
        private void getOutName() {
            System.out.println(new Outer().name);
        }
    }
}

#二、类的构造器

当创建类对象的时候,会自动调用类的构造器。之前使用的都是默认构造器,我们接下来要学习如何自定义构造器。

先有个概念: scala中构造器(也就是构造函数)分为主构造器 以及辅助构造器

其实就是java的构造方法重载, 只是会区分出来一个root(也就是主)

先有这样一个概念, 便于后面理解

#主构造器

我们学习过,Java的构造器,有构造列表和构造代码块

class Person {
    // 成员变量
    private String name;
    private Integer age;

    // Java构造器
    public Person(String name, Integer age) {
        // 初始化成员变量
        this.name = name;
        this.age = age;
    }
}

在scala中,我们可以使用更简洁的语法来实现。

示例1:Scala中定义主构造器(完全体)

class Student(_name:String, _age:Int) {
  var name:String = _
  var age:Int = _
  
  // 构造器的代码可以直接写在类中
  name = _name
  age = _age
}

示例2:Scala简化定义主构造器(简单体)

// 在主构造器中,可以直接定义成员变量
class Student(val name:String, val age:Int)

语法

class 类名(var/val 参数名:类型 = 默认值, var/val 参数名:类型 = 默认值){
    // 构造代码块
}

[!NOTE]

  • 主构造器的参数列表是直接定义在类名后面,添加了val/var表示直接通过主构造器定义成员变量
  • 构造器参数列表可以指定默认值
  • 创建实例,调用构造器可以指定字段进行初始化
  • 整个class中除了字段定义和方法定义的代码都是主构造器的代码 (也就是 除了定义成员变量 以及定义方法以外 其他的代码 都属于主构造器的代码.)

#示例

  1. 定义一个Person类,通过主构造器参数列表定义姓名和年龄字段,并且设置它们的默认值
  2. 在主构造器中输出"调用主构造器"
  3. 创建"张三"对象(姓名为张三,年龄为20),打印对象的姓名和年龄
  4. 创建"空"对象,不给构造器传入任何的参数,打印对象的姓名和年龄
  5. 创建"man40"对象,不传入姓名参数,指定年龄为40,打印对象的姓名和年龄

参考代码

object _06ConstructorDemo {

  // 定义类的主构造器
  // 指定默认值
  class Person(var name:String = "", var age:Int = 0) {
    println("调用主构造器")
  }

  def main(args: Array[String]): Unit = {
    // 给构造器传入参数
    val zhangsan = new Person("张三", 20)
    println(zhangsan.name)
    println(zhangsan.age)

    println("---")

    // 不传入任何参数
    val empty = new Person
    println(empty.name)
    println(empty.age)

    println("---")

    // 指定字段进行初始化
    val man40 = new Person(age = 40)
    println(man40.name)
    println(man40.age)
  }
}

讲解:

  1. 主构造器的定义 就相当于定义方法形参列表一样, 我们知道, 在java的构造器中也是定义构造函数的传入参数. 只是java中接收参数需要先定义成员变量以接收. 但在scala中只需要定义参数即可, 定义了参数, 根据参数描述 自动就会定义成员变量了

就像刚刚的示例代码, 在Person的代码块中, 并没有定义name 和 age的成员变量, 但是依旧可以访问这两个成员变量. 就说明,scala自动给我们定义好了 是隐式的.

  1. 思考: 那我们前面定义类 比如 class Person{ def println(“Hello”) } , 这样定义的类的主构造器呢?

其实 这里是有主构造器的 , 就是一个空参数的主构造器, 还记的吗, 我们会在里面也创建成员变量, 但是均是setXXX的形式 或者直接比如 person.name = "Taylor"的形式, 对成员变量赋值, 没有通过构造的时候赋值. 后面我们会学习如何在构造的时候赋值

  1. 除了在类名后面按参数的形式定义成员变量, 原本我们在代码块中正常定义成员变量的方式也是ok的, 两者可以搭配使用. 比如
class Person(var name: String = "", var age: Int = 0){
	var title: String = ""
}

1
2
3

我们可以new Person(“Taylor”, 20) 来给 name 和 age 赋值, 那么title呢?

这里就要引出辅助构造器的概念了.

#三、辅助构造器

在scala中,除了定义主构造器外,还可以根据需要来定义辅助构造器。例如:允许通过多种方式,来创建对象,这时候就可以定义其他更多的构造器。我们把除了主构造器之外的构造器称为辅助构造器

#语法

  • 定义辅助构造器与定义方法一样,也使用def关键字来定义

    就是和java一样, 我们的辅助构造器 乃至java中的构造函数(包括重载)都是方法

  • 这个方法的名字为this

    就像java中的构造方法不管怎么重载方法名必须是类名一样, 在scala中不写类名, 而是用this代替

def this(参数名:类型, 参数名:类型) {
    // **第一行需要调用主构造器或者其他构造器**
    // 构造器代码
}

[!DANGER]

辅助构造器的第一行代码,必须要调用主构造器或者其他辅助构造器

什么意思呢?

也就是 除了主构造器以外, 所有的辅助构造器必须首先调用主 或者 其他辅助构造器.

可以产生一个调用链条, 虽然我们允许辅助构造器不调用主构造器, 而是调用其他辅助构造器. 但是总会有一个辅助构造器是调用的主构造器的

比如第一个定义的辅助构造器, 只能调用主构造器, 因为没有其他辅助构造器给它用哦.

#示例

示例说明

  • 定义一个Customer类,包含一个姓名和地址字段
  • 定义Customer类的主构造器(初始化姓名和地址)
  • 并在Customer类代码块中定义一个成员变量age
  • 定义Customer类的辅助构造器,该辅助构造器接收一个数组参数以及一个Int类型参数,使用参数来初始化成员变量
  • 使用Person类的辅助构造器来创建一个"zhangsan"对象
    • 姓名为张三
    • 地址为北京
    • 年龄为20
  • 打印对象的姓名、地址 年龄

参考代码

object _07ConstructorDemo {

  class Customer(var name:String = "", var address:String = "") {
    var age:Int = 0
    // 定义辅助构造器
    def this(arr:Array[String], age: Int) = {
      // 辅助构造器必须要调用主构造器或者其他辅助构造器
      this(arr(0), arr(1))
      this.age = age
    }
  }

  def main(args: Array[String]): Unit = {
    val zhangsan = new Customer(Array("张三", "北京"), 20)

    println(zhangsan.name)
    println(zhangsan.address)
    println(zhangsan.age)
  }
}
#示例代码2
object _07ConstructorDemo {

  // 1. 定义一个class 定义主构造器
  class Person (var name:String, var age:Int){
    println("主构造器调用啦。。。")
    var title:String = _
    var dept:Int = _
    // 2. 定义一个辅助构造器

    def this (name:String, age:Int, title:String) = {
      // 辅助构造器第一行必须调用主或者其他辅助
      // 现在我们这个辅助是不是第一个辅助,没有其他辅助可以用, 只能用主对吧
      // 怎么调用主构造器呢? this(参数...)
      this(name, age) // 告诉主构造器, 大哥,帮忙给这两个变量赋值 我来对title赋值
      this.title = title
    }

    def this (name:String, age:Int, dept:Int, title:String) = {
//      this(name, age)
//      this.dept = dept
//      this.title = title
      this(name, age, title)
      this.dept = dept
    }
  }
  // 3. 使用辅助构造器 来创建对象
  def main(args: Array[String]): Unit = {
    // 这样调用的是主构造器
//    val person = new Person("zhangsan", 22)
//    println(person.name, person.age, person.title)

    // 这样调用的是我们的辅助构造器
    val person2 = new Person("Lisi", 20, "高级软件工程师")
    println(person2.name, person2.age, person2.title)
  }
}

#四、单例对象

scala中没有Java中的静态成员,我们想要定义类似于Java的static变量、static方法,就要使用到scala中的单例对象——object.

#定义单例对象

单例对象表示全局仅有一个对象(类似于Java static概念)

  • 定义单例对象和定义类很像,就是把class换成object

  • 在object中定义的成员变量类似于Java的静态变量

  • 可以使用object直接引用成员变量

  • 单例对象没有带参数的构造器(也就是只有一个主构造器,并且是无参数的)

    因为哦,Object本身是单例的,我们带有参数是为了每次创建的对象的成员变量内容都不一样

    但是对于单例对象而言,每次拿到的都是同一个对象,是不是就会导致这个对象 被频繁的修改成员变量的值

    在某个地方引用这个单例对象的时候,以为name是韩梅梅,谁知道别人又创建了一次单例对象传入了参数进来,因为静态是一个单例,那么以为name是韩梅梅的地方也同步被修改了。代码安全性不好,所以不允许传入参数

java main 方法 static, 因为scala中没有static关键字一说, object就是static的一个单例对象, main定义在其中

单例对象大家应该都能明白, 就是这个类的实例只能有一个, 也就是这个object也是静态的

scala 中的单例对象, 大家可以理解为一个 全部都是的类, 成员 方法均是static的

#示例

示例说明

  • 定义一个Dog单例对象,保存狗有几条腿
  • 在main方法中打印狗腿的数量

参考代码

object _08ObjectDemo {

  // 定义一个单例对象
  object Dog {
    // 定义腿的数量
    val LEG_NUM = 4
  }

  def main(args: Array[String]): Unit = {
    println(Dog.LEG_NUM)
  }
}

#在单例对象中定义成员方法

  • 在object中定义的成员方法类似于Java的静态方法

#示例

示例说明

  • 设计一个单例对象,定义一个能够打印分割线(15个减号)的方法
  • 在main方法调用该方法,打印分割线

参考代码``

object _09ObjectDemo {

  object PrintUtil {

    // 打印分割线
    def printSpliter() = {
      // 字符串乘法,表示返回多少个字符串
      println("-" * 10)
    }
  }

  def main(args: Array[String]): Unit = {
    PrintUtil.printSpliter()
  }
}

#4.1 工具类案例

#需求

  • 编写一个DateUtil工具类专门用来格式化日期时间
  • 定义一个方法,用于将日期(Date)转换为年月日字符串,例如:2030-10-05

#步骤

  • 定义一个DateUtil单例对象,定义日期格式化方法(format)
  • 使用SimpleDateFormat将日期转换为字符串

参考代码

object _10ObjectDemo {

  object DateUtils {
    // 在object中定义的成员变量,相当于Java中定义一个静态变量
    // 定义一个SimpleDateFormat日期时间格式化对象
    val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")

    // 相当于Java中定义一个静态方法
    def format(date: Date) = simpleDateFormat.format(date)
  }

  // main是一个静态方法,所以必须要写在object中
  def main(args: Array[String]): Unit = {
    println(DateUtils.format(new Date()))
  }
}

#4.2 main方法

scala和Java一样,如果要运行一个程序,必须有一个main方法。而在Java中main方法是静态的,而在scala中没有静态方法。在scala中,这个main方法必须放在一个单例对象中。

#定义main方法

main方法

def main(args:Array[String]):Unit = {
    // 方法体
}

#示例

示例说明

  • 创建一个单例对象,在该单例对象中打印"hello, scala"

参考代码

object Main5 {
  def main(args:Array[String]) = {
    println("hello, scala")
  }
}

// 如果把object 改成class, 就会发现在IDEA中, 可以运行的绿色小箭头消失了. 表明这不是一个可执行的程序入口, 因为main方法要求静态
//为什么要求静态, 大家应该都明白. 简单说就是: 程序执行前会先加载静态内容, 然后根据执行需要加载非静态内容.
//那么执行需要什么呢? 目前只有静态被加载, 所以只能从静态里面找.
// 所以如果一个静态都没有, 那么程序也不知道入口是谁. 因为谁都不加载.
// 也就是一个类似死锁的问题, 我要找执行逻辑然后去加载, 但是目前又没有可以让我找的(没静态)

#实现App Trait来定义入口

创建一个object,继承自App Trait(特质),然后将需要编写在main方法中的代码,写在object的构造方法体内。

object 单例对象名 extends App {
    // 方法体
}

#示例

示例说明

  • 继承App特质,来实现一个入口。同样输出"hello, scala"

参考代码

object Main5 extends App {
  println("hello, scala")
}

#4.3 伴生对象

在Java中,经常会有一些类,同时有实例成员又有静态成员。例如:

public class CustomerService {

    private static String SERVICE_NAME = "CustomerService";

    public void save() {
        // 保存客户
        System.out.println(SERVICE_NAME + ":保存客户");
    }

    public static void main(String[] args) {
        new CustomerService().save();
    }
}

在scala中,要实现类似的效果,可以使用伴生对象来实现。

我们还可以使用伴生对象来实现快速创建对象,例如:

// 无需使用new就可以快速来创建对象
val a = Array(1,2,3)
val b = Set(1,2,3)

简单概念:

可以这样理解, 因为scala中没有static关键字. class中只能有非静态的成员. 所以一个class中如果想同时包含静态和非静态的成员, 正常通过class是没办法实现的.

那么, 我们可以想, 因为object对象内是静态的, 我们可以把这两种成员分开存放, 非静态存在class中, 静态存在object中.

但问题又来了, object的对象 和class 的对象 有什么关系呢, 严格来说, 各自是各自, 没关系

所以我们要求如下定义:

  1. object定义名和class名相同
  2. 同时两者要在同一个source文件中

这样, scala可以帮我们把这两者关联起来.

大家都是为一个定义服务的, 只是你管非静态, 我管静态.

这样的object就叫做这个class的伴生对象

有了这样一个概念之后, 我们再来继续看如何定义伴生对象

#定义伴生对象

一个class和object具有同样的名字。这个object称为伴生对象,这个class称为伴生类

  • 伴生对象必须要和伴生类一样的名字

  • 伴生对象和伴生类在同一个scala源文件中

  • 伴生对象和伴生类可以互相访问private属性

    理解, 因为按照上面的概念, 它俩如果放到java中就是同一个class下的, 而在scala中我们分开存放静态和非静态, 但是它俩本是同源, 不是吗… 所以互相访问私有属性就是基本操作啦

#示例

示例说明

  • 编写一个CustomerService类,有一个save方法,打印

    服务类名称:保存客户
    
  • 编写一个CustomerService伴生对象,定义一个私有变量,用于保存服务类名称

  • 创建CustomerService对象,调用save方法

参考代码

object _11ObjectDemo {

  class CustomerService {
    def save() = {
      println(s"${CustomerService.SERVICE_NAME}:保存客户")
    }
  }

  // CustomerService的伴生对象
  object CustomerService {
    private val SERVICE_NAME = "CustomerService"
  }

  def main(args: Array[String]): Unit = {
    val customerService = new CustomerService()
    customerService.save()
  }
}

如上代码, 可能有同学就会问, 明白是明白, 但是还是不明白伴生对象在实际中有什么用途呢.

这个其实和你在java中class中定义静态是一个道理.

如果我这个CustomerService对象要被创建成千上万次 ,那么SERVICE_NAME这个变量不是静态的话, 这个变量是不是跟随者这个类同步创建成千上万次.

但是有了静态只有或者说有了伴生对象之后, 不管你对象创建多少次, 我SERVICE_NAME这个变量仅此一份.

节省空间, 提高性能.

#private[this]访问权限

如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问

#示例

示例说明

  • 定义一个Person类,包含一个name字段
  • 定义Person类的伴生对象,定义printPerson方法
  • 测试伴生对象是否能访问private[this]权限的成员

示例代码

  class Person(private[this] var name:String)
  
  object Person {
    def printPerson(person:Person): Unit = {
      println(person.name)
    }
  }
  
  def main(args: Array[String]): Unit = {
    val person = new Person("张三")
    Person.printPerson(person)
  }

上述代码,会编译报错。但移除掉[this]就可以访问了

#五、伴生对象 | apply方法

我们之前使用过这种方式来创建一个Array对象。

// 创建一个Array对象
val a = Array(1,2,3,4)

这种写法非常简便,不需要再写一个new,然后敲一个空格,再写类名。我们可以通过伴生对象的apply方法来实现。

#定义

定义apply方法

object 伴生对象名 {
	def apply(参数名:参数类型, 参数名:参数类型...) = new(...)
}

创建对象

伴生对象名(参数1, 参数2...)

为什么是伴生对象呢?

我们分两步理解, 先理解第一步:

我们看: Array(1, 2, 3) 这种形式并没有使用new,

那么我们第一步可以得到一个结论, 它肯定是object也就是一个单例对象(如果是class 是需要new才能产生实例的 对吧)

好, 先了解这一步的结论, 我们先继续向下看, 等会我们就会知道第二步, 了解为什么是伴生对象

#示例

示例说明

  • 定义一个Object, 并定义两个apply方法
    • 1个是无参数的
    • 1个接收1个参数
  • 在main方法中使用object() 以及 object(参数) 来测试

参考代码

object _016ApplyDemo {
  // 定义一个object, 并提供apply方法
  object Printer{
    def apply() = println("1 apply running without parameters")

    def apply(msg:String) = println(msg)
  }

  // 测试
  def main(args: Array[String]): Unit = {
    Printer()
    Printer("2 apply running with parameters")
  }
}

可以看出, scala自动帮我们调用了apply方法

什么意思呢, 就是当我们对单例对象使用()传入或者不传入参数的时候, scala会自动调用单例对象中的apply方法, 并根据方法重载去寻找对应的apply

那我们在看一个示例:

示例

  • 定义一个Person类,它包含两个字段:姓名和年龄
  • 重写apply方法,使用Person类名就可以创建对象
  • 在main方法中创建该类的对象,并打印姓名和年龄

参考代码

object _12ApplyDemo {
  class Person(var name:String = "", var age:Int = 0)

  object Person {
    // 定义apply方法,接收两个参数
    def apply(name:String, age:Int) = new Person(name, age)
  }

  def main(args: Array[String]): Unit = {
    // 使用伴生对象名称来创建对象
    val zhangsan = Person("张三", 20)
    println(zhangsan.name)
    println(zhangsan.age)
  }
}

我们看到, 现在是用了一个 伴生对象的apply, 去创建一个Person类的实例并返回.

其实, 我们不一定要求一定是伴生对象的. 只要我单例对象有权限new class, 那么哪个单例对象都可以new class并返回实例

但是我们一直强调用伴生 是因为, 在主观上, 一目了然

比如 Array(1, 2, 3) 我们一看就知道是创建Array

我们可以自行封装一个单例对象 比如 MyListGen, 但是apply中 是 new Array 并返回

在语法上是没毛病的.

但是阅读起来就很难了. 看字面意思还以为是要自定义一个List返回呢, 不看源码哪里知道是返回的Array呢.

如:

object _01 {
object MyArrayGen{
 def apply(): MyString = new MyString()
}

class MyString

def main(args: Array[String]): Unit = {
 val gen = MyArrayGen()
 println(gen) // 你在逗我?看名字不是返回MyArray吗 为何返回了一个MyString对象???
}
}

1
2
3
4
5
6
7
8
9
10
11
12

还有一种情况比如定义一个 单例对象 叫做 PersonFactory 在apply中返回person的实例

这样其实也是合理并可以用的.

所以我们一般规范是 在伴生对象中使用.

但是不强制, 比如我们想要定义工厂类, 就不一定非要是伴生对象

主要目的是为了代码的可读性, 因为语法糖虽然用起来很爽, 但是架不住乱定义啊, 代码就很难读了

比如以下代码, 编译运行没问题, 但是谁看到MyListGen会想到这是返回了Person对象呢.

object _016ApplyDemo3 {
  class Person(var name: String, var age: Int)

  object MyListGen{
    def apply(name: String, age: Int): Person = {
      new Person(name, age)
    }
  }

  def main(args: Array[String]): Unit = {
    val whoAmI = MyListGen("Taylor", 20)
    println(whoAmI.getClass)
    println(whoAmI.name)
    println(whoAmI.age)
  }
}

// 输出是:
class com.itheima.scala.oop._016ApplyDemo3$Person
Taylor
20

#六、继承

scala语言是支持面向对象编程的,我们也可以使用scala来实现继承,通过继承来减少重复代码。

#定义语法

  • scala和Java一样,使用extends关键字来实现继承
  • 可以在子类中定义父类中没有的字段和方法,或者重写父类的方法
  • 类和单例对象都可以从某个父类继承

语法

class/object 子类 extends 父类 {
    ..
}

#示例 | 类继承

  • 定义一个Person类,再定义一个Student类,继承自Person类

    img

  • 创建一个Student类对象实例,并设置name为“张三”

  • 打印姓名

参考代码

class Person {
  var name = "super"

  def getName = this.name
}

class Student extends Person

object Main13 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person()
    val p2 = new Student()

    p2.name = "张三"

    println(p2.getName)
  }
}

#示例 | 单例对象继承

示例说明

  • 创建一个Student单例对象,让单例对象继承示例1中的Person类
  • 设置单例对象的名字为"张三",调用Student单例对象的getName方法
class Person {
  var name = "super"

  def getName = this.name
}

object Student extends Person

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

    println(Student.getName)
  }
}

#6.1 override和super

类似于Java语言,我们在子类中使用override需要来重写父类的成员,可以使用super来引用父类

#用法

  • 子类要覆盖父类中的一个方法,必须要使用override关键字
  • 使用override来重写一个val字段 因为var可变, 不需要重写就能覆盖值
  • 使用super关键字来访问父类的成员方法

#示例

示例说明

  • 定义一个Person类,包含
    • 姓名字段(不可重新赋值)
    • 获取姓名方法
  • 定义一个Student类
    • 重写姓名字段
    • 重写获取姓名方法,返回"hello, " + 姓名(使用super获取父类的name)
  • 创建Student对象示例,调用它的getName方法

参考代码

class Person {
  val name = "super"

  def getName = "i'm super"
}

class Student extends Person {
  // 重写val字段
  override val name: String = "child"

  // 重写getName方法
  override def getName: String = "hello, " + super.getName
}

object Main13 {
  def main(args: Array[String]): Unit = {
    println(new Student().getName)
  }
}

public class B{
    public static void main(String[] args){
        System.out.println(new Student().getName());
    }
}

class Person{
    String name = "beijing";
    public String getName() {
        return this.name;
    }
}

class Student extends Person{
    String name = "shanghai";
    
    @Override
    public String getName() {
        return "hello: " + super.getName();
    }
}

#七、类型判断

有时候,我们设计的程序,要根据变量的类型来执行对应的逻辑。

img

在scala中,如何来进行类型判断呢?

有两种方式:

  • isInstanceOf
  • getClass/classOf

#isInstanceOf/asInstanceOf

在Java中,我们可以使用instanceof关键字来判断类型、以及(类型)object来进行类型转换,在scala中如何实现呢?

scala中对象提供isInstanceOf和asInstanceOf方法。

  • isInstanceOf判断对象是否为指定类的对象
  • asInstanceOf将对象转换为指定类型

用法

// 判断对象是否为指定类型
val trueOrFalse:Boolean = 对象.isInstanceOf[类型]
// 将对象转换为指定类型
val 变量 = 对象.asInstanceOf[类型]

注意, 在Scala中 凡是和类型相关 统统[] 而不是java中的<> 不要混淆了

#示例

示例说明

  • 定义一个Person类
  • 定义一个Student类继承自Person类
  • 创建一个Student类对象
  • 判断该对象是否为Student类型,如果是,将其转换为Student类型并打印该对象

参考代码

class Person3
class Student3 extends Person3

object Main3 {
  def main(args: Array[String]): Unit = {
    val s1:Person3 = new Student3

    // 判断s1是否为Student3类型
    if(s1.isInstanceOf[Student3]) {
      // 将s1转换为Student3类型
      val s2 =  s1.asInstanceOf[Student3]
      println(s2)
    }

  }
}

拓展, 判断student对象是否是Person类型对象, 结果也会是true的.

因为子类可以当成父类看

但反过来 父类不能看成子类

如下代码:

object _20IsInstanceOfAsInstanceOfDemo {
  // 1. 定义一个Person 类
  class Person
  // 2. 定义一个Student类, 从Person类继承
  class Student extends Person
  // 3. 判断类型 转换类型
  def main(args: Array[String]): Unit = {
    val student = new Student

    if(student.isInstanceOf[Person]){
      println("是Person类型")  // 会进来这个if , 子类可以看成父类
    }else if (student.isInstanceOf[Student]){
      println("是Student类型")
      val student1 = student.asInstanceOf[Student]
      println(student1)
    }else{
      println("既不是Person类型 也不是Student类型")
    }

    var person = new Person
    if(person.isInstanceOf[Student]){
      println("父类可以看成子类")
    }else{
      println("父类并不可以看成是子类哦")
    }
  }
}
/*
输出是:
是Person类型
父类并不可以看成是子类哦
*/

#7.1 getClass和classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 。

#用法

  • p.getClass可以精确获取对象的类型
  • classOf[x]可以精确获取类型
  • 使用==操作符可以直接比较类型

#示例

示例说明

  • 定义一个Person类
  • 定义一个Student类继承自Person类
  • 创建一个Student类对象,并指定它的类型为Person类型
  • 测试使用isInstance判断该对象是否为Person类型
  • 测试使用getClass/classOf判断该对象是否为Person类型
  • 测试使用getClass/classOf判断该对象是否为Student类型

参考代码

object _21GetClassAndClassOfDemo {
  // 1. 定义父类和子类
  class Person
  class Student extends Person
  // 2. 分别来判断
  def main(args: Array[String]): Unit = {
    val student = new Student

    // 1. 使用isInstanceOf判断student是否是Person
    if (student.isInstanceOf[Person]){
      println("isInstanceOf 判断 student是Person")
    }else{
      println("isInstanceOf 判断 student不是Person")
    }

    // 2. 使用GetClass 搭配 ClassOf判断student是否Person
    if (student.getClass == classOf[Person]){
      println("getClass\\classOf 认为student 是 Person")
    }else{
      println("getClass\\classOf 认为student 不是 Person")
    }

    // 3. 使用getClass 搭配classOf 判断student是否student
    if (student.getClass == classOf[Student]){
      println("getClass\\classOf 认为student 是 student")
    }else{
      println("getClass\\classOf 认为student 不是 student")
    }
  }
}

输出:
isInstanceOf 判断 student是Person
getClass\classOf 认为student 不是 Person
getClass\classOf 认为student 是 student

#八、抽象类

和Java语言一样,scala中也可以定义抽象类

#定义

如果类的某个成员在当前类中的定义是不包含完整的,它就是一个抽象类

不完整定义有两种情况:

  1. 方法没有方法体(抽象方法
  2. 变量没有初始化(抽象字段

定义抽象类和Java一样,在类前面加上abstract关键字

// 定义抽象类
abstract class 抽象类名 {
  // 定义抽象字段
  val 抽象字段名:类型
  // 定义抽象方法
  def 方法名(参数:参数类型,参数:参数类型...):返回类型
}

#抽象方法

示例

img

  • 设计4个类,表示上述图中的继承关系
  • 每一个形状都有自己求面积的方法,但是不同的形状计算面积的方法不同

步骤

  1. 创建一个Shape抽象类,添加一个area抽象方法,用于计算面积
  2. 创建一个Square正方形类,继承自Shape,它有一个边长的主构造器,并实现计算面积方法
  3. 创建一个长方形类,继承自Shape,它有一个长、宽的主构造器,实现计算面积方法
  4. 创建一个圆形类,继承自Shape,它有一个半径的主构造器,并实现计算面积方法
  5. 编写main方法,分别创建正方形、长方形、圆形对象,并打印它们的面积

参考代码

抽象类的抽象方法, 如果返回值不是Unit的话, 不可省略

// 创建形状抽象类
abstract class Shape {
  def area:Double
}

// 创建正方形类
class Square(var edge:Double /*边长*/) extends Shape {
  // 实现父类计算面积的方法
  override def area: Double = edge * edge
}

// 创建长方形类
class Rectangle(var length:Double /*长*/, var width:Double /*宽*/) extends Shape {
  override def area: Double = length * width
}

// 创建圆形类
class Cirle(var radius:Double /*半径*/) extends Shape {
  override def area: Double = Math.PI * radius * radius
}

object Main6 {
  def main(args: Array[String]): Unit = {
    val s1:Shape = new Square(2)
    val s2:Shape = new Rectangle(2,3)
    val s3:Shape = new Cirle(2)

    println(s1.area)
    println(s2.area)
    println(s3.area)
  }
}

#8.1 抽象字段

在scala中,也可以定义抽象的字段。如果一个成员变量是没有初始化,我们就认为它是抽象的。

#定义

语法

abstract class 抽象类 {
    val/var 抽象字段:类型
}

#示例

示例说明

  1. 创建一个Person抽象类,它有一个String抽象字段WHO_AM_I
  2. 创建一个Student类,继承自Person类,重写WHO_AM_I字段,初始化为学生
  3. 创建一个Policeman类,继承自Person类,重写WHO_AM_I字段,初始化警察
  4. 添加main方法,分别创建Student/Policeman的实例,然后分别打印WHO_AM_I

参考代码

// 定义一个人的抽象类
abstract class Person6 {
  // 没有初始化的val字段就是抽象字段
  val WHO_AM_I:String
}

class Student6 extends Person6 {
  override val WHO_AM_I: String = "学生"
}

class Policeman6 extends Person6 {
  override val WHO_AM_I: String = "警察"
}

object Main6 {
  def main(args: Array[String]): Unit = {
    val p1 = new Student6
    val p2 = new Policeman6

    println(p1.WHO_AM_I)
    println(p2.WHO_AM_I)
  }
}

#九、匿名内部类

匿名内部类是没有名称的子类,直接用来创建实例对象。Spark的源代码中有大量使用到匿名内部类。

scala中的匿名内部类使用与Java一致。

#定义

语法

val/var 变量名 = new/抽象类 {
    // 重写方法
}

#示例

示例说明

  1. 创建一个Person抽象类,并添加一个sayHello抽象方法
  2. 添加main方法,通过创建匿名内部类的方式来实现Person
  3. 调用匿名内部类对象的sayHello方法

参考代码

abstract class Person7 {
  def sayHello:Unit
}

object Main7 {
  def main(args: Array[String]): Unit = {
    // 直接用new来创建一个匿名内部类对象
    val p1 = new Person7 {
      override def sayHello: Unit = println("我是一个匿名内部类")
    }
    p1.sayHello
  }
}

#十、特质(trait)

scala中没有Java中的接口(interface),替代的概念是——特质

#定义

  • 特质是scala中代码复用的基础单元
  • 它可以将方法和字段定义封装起来,然后添加到类中
  • 与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。
  • 特质的定义和抽象类的定义很像,但它是使用trait关键字

概念理解:

trait 从字面上看, 就是特性,特质的意思, 那么不会随便应用单词啦.

在scala中trait也确实是trait的意思, 那么trait可以做什么呢?

我们在定义中说过, 它可以将方法和字段定义封装起来 然后添加在类中, 这是什么意思呢.

比方我们有两个特质 一个是 走路带风 的特性(特质) A 一个是 划船不用桨的特性(特质)B

然后我们有一个普通类Person ,我们可以把这两个特质添加给Person, 那么Person就拥有了 走路带风 以及划船不用桨的 两个特性(特质)

而添加的过程, 其实和继承很像, 就是我有什么 你添加我 你就有什么.

但是呢又有一点神奇的是, 特质trait中可以有抽象方法和抽象字段. 同时也可以有 已实现字段和已实现方法

那么谁添加这个特质 ,这一堆 已实现的 和未实现的都能获得.

比如还是说走路带风. 走路带风这个特性中我们封装了两个方法 一个是running也就是走路, 这个是已经实现的, 走起来就刮风. 还有一个是windWay 是一个抽象的未实现的, 就相当于, 带风是带风了 但是风是哪个方向的呢? 这个特性就不管了 谁添加了 谁实现即可.

那么通过这个比喻我们又可以总结出来trait的一些表象特性:

  1. 可以被继承(实际上是跟随或者附加 extends)
  2. 可以包含抽象 以及 非抽象
  3. 可以被多继承(多添加 或者说多跟随)(比如例子中的一个Person可以添加 走路带风 以及划船不用桨两个特性)(毕竟人民币玩家嘛不是)(多继承用with)

好, 那么既然对特质有了一个初步的概念. 那么我们后面学习起来就方便多啦.

语法

定义特质

trait 名称 {
    // 抽象字段
    // 抽象方法
}

继承特质

classextends 特质1 with 特质2 {
    // 字段实现
    // 方法实现
}
  • 使用extends来继承trait(scala不论是类还是特质,都是使用extends关键字)
  • 如果要继承多个trait,则使用with关键字

#trait作为接口使用

trait作为接口使用,与java的接口使用方法一样。

#示例 | 继承单个trait

示例说明

  1. 创建一个Logger特质,添加一个接受一个String类型参数的log抽象方法
  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
  3. 添加main方法,创建ConsoleLogger对象,调用log方法

参考代码

  trait Logger {
    // 抽象方法
    def log(message:String)
  }

  class ConsoleLogger extends Logger {
    override def log(message: String): Unit = println("控制台日志:" + message)
  }

  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger.log("这是一条日志")
  }
#示例 | 继承多个trait

示例说明

  1. 创建一个MessageSender特质,添加send方法
  2. 创建一个MessageReceiver特质,添加receive方法
  3. 创建一个MessageWorker实现这两个特质
  4. 在main中调用,分别调用send方法、receive方法

参考代码

trait MessageSender {
    def send(msg:String)
}

trait MessageReceive {
    def receive():String
}

class MessageWorker extends MessageSender with MessageReceive {
    override def send(msg: String): Unit = println(s"发送消息:${msg}")

    override def receive(): String = "你好!我叫一个好人!"
}

def main(args: Array[String]): Unit = {
    val worker = new MessageWorker
    worker.send("hello")
    println(worker.receive())
}
#示例 | object继承trait

示例说明

  1. 创建一个Logger特质,添加一个log抽象方法
  2. 创建一个ConsoleLogger的object,实现LoggerForObject特质,实现log方法,打印消息
  3. 编写main方法,调用ConsoleLogger的log方法

参考代码

trait Logger {
    def log(message:String)
}

object ConsoleLogger extends Logger {
    override def log(message: String): Unit = println("控制台消息:" + message)
}

def main(args: Array[String]): Unit = {
    ConsoleLogger.log("程序退出!")
}

#10.1 特质 | 定义具体的方法

和类一样,trait中还可以定义具体的方法

#示例

示例说明

  1. 定义一个Logger特质,添加info实现方法
  2. 定义一个UserService类,实现Logger特质
  • 添加add方法,打印"添加用户"
  1. 添加main方法
  • 创建UserService对象实例
  • 调用add方法

参考代码

object _04TraitDemo {
  // 1. 定义一个trait, 并实现内部log方法
  trait Logger{
    def info(msg: String): Unit = println("[INFO]: " + msg)
  }
  // 2. 定义UserService 类继承trait, 并添加add方法调用info方法
  class UserService extends Logger{
    // 并不报错, 说明, 不用实现info方法, 因为在trait中已经有了实现了, 如果要复写请用override
    def add = {
      // addUserToDB(user) 业务逻辑
      // 输出日志
      info("用户xxx已添加到数据库.")
    }
  }
  // 3. 测试
  def main(args: Array[String]): Unit = {
    val service = new UserService
    service.add
  }
}

#十一、trait中定义具体的字段和抽象的字段

#定义

  • 在trait中可以定义具体字段和抽象字段
  • 继承trait的子类自动拥有trait中定义的字段
  • 字段直接被添加到子类中

#示例

示例说明

通过trait来实现一个日志输出工具,该日志工具可以自动添加日志的日期

步骤

  1. 创建Logger特质
    • 定义一个SimpleDateFormat字段,用来格式化日期(显示到时间)
    • 定义一个TYPE抽象字段,用于定义输出的信息
    • 创建一个log抽象方法,用于输出日志
  2. 创建ConsoleLogger类,实现TYPE抽象字段和log方法
  3. 添加main方法
  • 创建ConsoleLogger类对象
  • 调用log方法

参考代码

  object _05TraitDemo {
  // 1. 定义一个trait, 包含日期格式化字段\抽象的TYPE字段\抽象的log方法
  trait Logger{
    val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

    val TYPE: String

    def log(msg: String): Unit
  }
  // 2. 定义一个控制台ConsoleLogger类 继承trait
  class ConsoleLogger extends Logger{
    override val TYPE: String = "ConsoleInfo"

    override def log(msg: String): Unit = {
      println(s"[${TYPE}] [${simpleDateFormat.format(new Date)}] ${msg}")
    }
  }
  // 3. 测试
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger.log("I am Programmer.")
  }
}

#十二、使用trait实现模板模式

要实现以下需求:

  • 实现一个输出日志的功能
  • 目前要求输出到控制台
  • 将来可能会输出到文件、输出到Redis、或者更多的需求

如何实现将来不修改之前的代码,来扩展现有功能呢?

img

#定义

在一个特质中,具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现,这种设计方式也称为模板模式

img

在scala中,trait是可以定义抽象方法,也可以定义具体方法的

  • trait中定义了一个抽象方法
  • trait中定义了其他的几个具体方法,会调用抽象方法
  • 其他实现类可以来实现抽象方法
  • 真正调用trait中具体方法的时候,其实会调用实现类的抽象方法实现

#示例

示例说明

  • 编写一个日志输出工具,分别有info、warn、error三个级别的日志输出
  • 日志输出的方式要求设计为可扩展的,例如:可以输出到控制台、将来也可以扩展输出到文件、数据库等

实现步骤

  1. 添加一个Logger特质
    • 添加一个log抽象方法
    • 添加一个info、warn、error具体方法,这几个方法调用log抽象方法
  2. 创建ConsoleLogger类,实现Logger特质
  3. 添加main方法
    • 创建ConsoleLogger类对象
    • 分别调用info、warn、error方法输出日志

参考代码

  trait Logger {
    def log(msg:String)
    def info(msg:String) = log("INFO:" + msg)
    def warn(msg:String) = log("WARN:" + msg)
    def error(msg:String) = log("ERROR:" + msg)
  }

  class ConsoleLogger extends Logger {
    override def log(msg: String): Unit = {
      println(msg)
    }
  }

  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger.info("信息日志")
    logger.warn("警告日志")
    logger.error("错误日志")
  }

#十三、对象混入trait

scala中可以将trait混入到对象中,就是将trait中定义的方法、字段添加到一个对象中

先理解一下概念:

前面我们说过特质就是类似增加新特性, 但是我们都是对类这一级别来说的, 比如Person类 如果给Person类增加特性, 那么所有基于这个Person创建出来的实例对象都会有这个特性.

但是出来对类级别以外呢. 我们也可以针对某一个个体来增加特性

比如倒立走路不是人类的特性 ,但是个别人类是特例, 他们个体拥有这个特性

理解了这个概念后 ,我们这个对象混入特性就非常简单好理解了.

#定义

语法

val/var 对象名 = newwith 特质

对象混入 实际上和类混入特质是一样的使用方法, 只是只有这个对象有特权, 其他对象还是正常普通的.

#示例

  • 给一个对象添加一些额外的行为

步骤

  1. 创建一个Logger特质
    • 添加一个log实现方法,打印参数
  2. 创建一个UserService类
  3. 添加main方法
    • 创建UserService对象,混入Logger特质
    • 调用log方法

参考代码

  trait Logger {
    def log(msg:String) = println(msg)
  }

  class UserService

  def main(args: Array[String]): Unit = {
    val service = new UserService with Logger
    service.log("混入的方法")
  }

#十四、trait实现调用链模式

我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:

  1. 进行支付签名校验
  2. 数据合法性校验

如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验代码,来实现扩展呢?

#责任链模式

img

#trait调用链

img

类继承了多个trait后,可以依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法在最后都依次执行super关键字即可。类中调用多个tait中都有这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条。

#示例

实现一个模拟支付过程的调用链

img

步骤

  1. 定义一个HandlerTrait特质
    • 定义一个具体的handler方法,打印"处理数据…"
  2. 定义一个DataValidHandlerTrait,继承HandlerTrait特质
    • 重写handler方法,打印"验证数据"
    • 调用父特质的handler方法
  3. 定义一个SignatureValidHandlerTrait,继承HandlerTrait特质
    • 重写Handler方法
    • 打印"检查签名"
    • 调用父特质的handler方法
  4. 创建一个PaymentService类
    • 继承DataValidHandlerTrait
    • 继承SignatureValidHandlerTrait
    • 定义pay方法
      • 打印"准备支付"
      • 调用父特质的handler方法
  5. 添加main方法
    • 创建PaymentService对象实例
    • 调用pay方法

参考代码

object _08TraitDemo {
  // 处理顺序: 发送 -> 签名 -> 数据校验 -> 处理
  // 1. 定义父trait(最后一步)
  trait Handler{
    def handler(data: String) = println("处理数据...")
  }
  // 2. 定义签名校验\数据校验trait

  trait SignatureValidateTrait extends Handler{
    override def handler(data: String): Unit = {
      println("签名校验...")
      super.handler(data)
    }
  }

  trait DataValidateTrait extends Handler{
    override def handler(data: String): Unit = {
      println("数据校验...")
      super.handler(data)
    }
  }
  // 3. 创建支付服务
  class PayService extends DataValidateTrait with SignatureValidateTrait{
    override def handler(data: String): Unit = {
      println("创建支付请求, 发送支付数据...")
      super.handler(data)
    }
  }
  // 4. 测试
  def main(args: Array[String]): Unit = {
    val service = new PayService
    service.handler("支付数据包")
  }
}

//创建支付请求, 发送支付数据...
//签名校验...
//数据校验...
//处理数据...

如果加入银行校验:

object _08TraitDemo_02 {
  // 处理顺序: 发送 -> 签名 -> 数据校验 -> 银行校验 -> 处理
  // 1. 定义父trait(最后一步)
  trait Handler{
    def handler(data: String) = println("处理数据...")
  }
  // 2. 定义签名校验\数据校验trait

  trait SignatureValidateTrait extends Handler{
    override def handler(data: String): Unit = {
      println("签名校验...")
      super.handler(data)
    }
  }

  trait DataValidateTrait extends Handler{
    override def handler(data: String): Unit = {
      println("数据校验...")
      super.handler(data)
    }
  }

  trait BankValidateTrait extends Handler{
    override def handler(data: String): Unit = {
      println("银行校验中...")
      super.handler(data)
    }
  }
  // 3. 创建支付服务
  class PayService extends BankValidateTrait with DataValidateTrait with SignatureValidateTrait{
    override def handler(data: String): Unit = {
      println("创建支付请求, 发送支付数据...")
      super.handler(data)
    }
  }
  // 4. 测试
  def main(args: Array[String]): Unit = {
    val service = new PayService
    service.handler("支付数据包")
  }
}
//输出为:
//创建支付请求, 发送支付数据...
//签名校验...
//数据校验...
//银行校验中...
//处理数据...

#十五、trait的构造机制

如果一个类实现了多个trait,那这些trait是如何构造的呢?

#定义

  • trait也有构造代码,但和类不一样,特质不能有构造器参数
  • 每个特质只有**一个无参数**的构造器。
  • 一个类继承另一个类、以及多个trait,当创建该类的实例时,它的构造顺序如下:
    1. 执行父类的构造器
    2. 从左到右依次执行trait的构造器
    3. 如果trait有父trait,先构造父trait,如果多个trait有同样的父trait,则只初始化一次
    4. 执行子类构造器

#示例

示例说明

  • 定义多个特质,然后用一个类去实现它们
  • 测试trait的构造顺序

步骤

  1. 创建一个Logger特质,在构造器中打印"执行Logger构造器!"
  2. 创建一个MyLogger特质,继承自Logger特质,,在构造器中打印"执行MyLogger构造器!"
  3. 创建一个TimeLogger特质,继承自Logger特质,在构造器中打印"执行TimeLogger构造器!"
  4. 创建一个Person类,在构造器中打印"执行Person构造器!"
  5. 创建一个Student类,继承自Person、MyLogger、TimeLogge特质,在构造器中打印"执行Student构造器!"
  6. 添加main方法,实例化Student_One类,观察输出。

大家觉得是什么顺序

5 4 23 1?

还是4 1 2 3 5?

参考代码

trait Logger {
    println("执行Logger构造器")
}

trait MyLogger extends Logger {
    println("执行MyLogger构造器")
}

trait TimeLogger extends Logger {
    println("执行TimeLogger构造器")
}

class Person{
    println("执行Person构造器")
}

class Student extends Person with TimeLogger with MyLogger {
    println("执行Student构造器")
}

def main(args: Array[String]): Unit = {
    new Student
}

// 程序运行输出如下:
// 执行Person构造器
// 执行Logger构造器
// 执行TimeLogger构造器
// 执行MyLogger构造器
// 执行Student构造器

#十六、trait继承class

#定义

trait也可以继承class的。特质会将class中的成员都继承下来。

#示例

示例说明

  • 定义一个特质,继承自一个class

步骤

  1. 创建一个MyUtils类,定义printMsg方法
  2. 创建一个Logger特质,继承自MyUtils,定义log方法
  3. 创建一个Person类,添加name字段
    • 继承Logger特质
    • 实现sayHello方法,调用log方法
  4. 添加main方法,创建一个Person对象,调用sayHello方法

参考代码

object _10TraitDemo {
  // 1. 定义一个MyUtil类 包含printMsg方法
  class MyUtil{
    def printMsg(msg: String) = println(msg)
  }
  // 2. 定义一个Trait, 继承MyUtil类, 并实现log方法调用printMsg方法
  trait Logger extends MyUtil{
    def log(msg: String) = printMsg(msg)
  }
  // 3. 定义这个Trait的实现类
  class Person(var name: String = "") extends Logger {
    def sayHello = log(name)
  }
  // 4. 测试
  def main(args: Array[String]): Unit = {
    val person = new Person("Taylor")
    person.sayHello
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值