Scala 第三篇 OOP篇

Scala 第三篇 OOP篇

上接: Scala 第二篇 算子篇

前序

1、Scala 为纯粹OOP

1.1、不支持基本类型:一切皆为对象 Byte, Int,...

1.2、不支持静态关键字:static

1.3、支持类型推断,和类型预定,动静结合

一、类

关键字:class

创建对象:new

内含:成员变量和方法

区别:

1、默认访问修饰符为 public,也支持 private 和 protected

2、没有构造方法,通过构造参数列表实现对象创建

1、修饰符

修饰符 类(class) 伴生对象(object) 子类(subclass) 同包(package) 全局(world)
public(default) Y Y Y Y Y
protected Y Y Y N N
private Y Y N N N

2、创建类示例

  1. 创建类

    scala 复制代码
    // 类自身:就是类的【主】构造器
    class Student(name:String, age:Int) {
      // 属性
      private var _name:String = name
      private var _age:Int = age
    
      // 辅助构造器:基于主构造器的重载
      def this() = this("Unknown", 0)
      def this(name:String) = this(name, 18)
        
      // 方法
      def play(): Unit = {
        println("play computer game")
      }
        
      def getName:String = _name
      def getAge:Int = _age
    
      def setName(name: String):Unit = _name = name
      def setAge(age: Int):Unit = _age = age
    
      override def toString(): String = s"Student{name=${_name}, age=${_age}}"
    }
  2. 创建对象与方法调用

    scala 复制代码
    val stu1 = new Student("张三", 20)
    val stu2 = new Student()
    val stu3 = new Student("leaf")
    
    stu1.play()				// play computer game
    stu2.setName("aaa")			
    stu2.setAge(19)	
    println(stu2)		 	// Student{name=aaa, age=19}
    println(stu3.getAge)	// 18

3、类的继承

  1. 类的继承

    scala 复制代码
    // 继承关键字:extents,  继承Student类
    class EliteStudent(name: String, age: Int, score: Int) extends Student(name, age){
      // 增添属性
      private var _score: Int = score
      // 重载方法
      override def play(): Unit = {
        println("Do homework")
      }
      override def toString(): String = s"${super.toString()}, score: ${score}"
    }
  2. 调用

    scala 复制代码
    val eliteStudent = new EliteStudent("李四", 12, 100)
    eliteStudent.play()			// 输出:Do homework
    println(eliteStudent)		// Student{name=李四,age=12}, score: 100

二、抽象类

关键字:abstract

  • 抽象类中可以有抽象方法(没有方法体的方法即抽象方法)
  • 无法实例化
  • 使用 abstract 关键字修饰
  • 子类重写父类【抽象】方法可以省略 override 关键字,但不推荐省略
  • 子类重写父类【非抽象】方法必须写 override 关键字

示例代码

scala 复制代码
// 抽象类
abstract class Animal {
  def eat(): Unit	// 抽象方法
  def play(): Unit	
}

class Dog extends Animal {
  override def eat(): Unit = {
    println("大口吃肉")
  }
  override def play(): Unit = {
    println("抓蝴蝶")
  }
}

class Cat extends Animal{
  override def eat(): Unit = {
    println("优雅的吃")
  }

  override def play(): Unit = {
    println("滚毛球")
  }
}

三、单例对象

代替 Java 中的 static 关键字

1、关键字:object

2、可以包含属性和方法,且可以通过单例对象名直接调用

3、采取惰性模式,第一次被访问时创建

4、无构造器,且不能 new

5、程序入口方法必须定义在单例对象中

6、同一个文件中同名的类和单例对象形成绑定关系,并称之为伴生类和伴生对象

  1. 伴生类,伴生对象(单例对象)的创建

    scala 复制代码
    // 伴生类:与伴生对象具有相同名称的类被称为伴生类。
    class Student(name:String, age:Int) {
      private var _name:String = name
      private var _age:Int = age
    
      def this() = this("Unknown", 0)
      def this(name:String) = this(name,18)
    
      def getName:String = _name
      def getAge:Int = _age
        
      def setName(name: String):Unit = _name = name
      def setAge(age: Int):Unit = _age = age
        
      def play(): Unit = {
        println("play computer game")
      }
      override def toString(): String = s"Student{name=${_name},age=${_age}}"
    } 
    // 伴生对象:伴生对象是一个单例对象,它与一个类具有相同的名称。
    // 通常用于存放与该类相关的静态方法或字段。
    object Student{
      var products:Array[String] = Array("BigData","Cloud")
      // apply方法常用于作为创建类实例的工厂方法,省去了使用new关键字的麻烦
      def apply(name: String, age: Int): Student = new Student(name, age)
      def add(a:Int,b:Int) = a + b
    }
  2. 调用

    scala 复制代码
    Student.products.foreach(e => print(e + " "))	// 输出静态属性:BigData Cloud
    val stu = Student("张三", 12)					   // 省略 new 
    print("\n" + Student.add(1, 5))					// 调用静态方法

四、特质

类似 java 的接口 (interface)

关键字:trait

1、包含字段、方法,亦可包含字段和方法的实现

2、类、单例对象、普通对象都可以扩展特质

3、没有构造器,不能实例化

4、单根继承,借助 with 实现多混入

scala 复制代码
// 定义抽象类 Animal
abstract class Animal {
  var _name: String
}

// 定义特质 ByFoot
trait ByFoot {
  def eat(): Unit
  def play(): Unit
}

// 定义类 Cat,实现了 ByFoot 特质
class Cat extends ByFoot {
  override def eat(): Unit = println("吃西瓜")
  override def play(): Unit = println("抓老鼠")
}

// 定义类 Dog,继承了 Animal 抽象类并实现了 ByFoot 特质
class Dog(name: String) extends Animal with ByFoot {
  override var _name: String = name
  override def eat(): Unit = println("大口吃肉")
  override def play(): Unit = println("抓蝴蝶")
}

1、动态混入

  1. 特质类的定义

    scala 复制代码
    trait Animal {
      val name:String
      def cry():Unit
    }
    
    trait ByFoot {
      def jog():Unit
      def run():Unit
    }
    // 动态强制混入特质:只能定义一个强制混入特质,且必须位于类内首行
    // self 是 this 的别名
    class Penguin {
      self: Animal => // 强制混入特质语法
      val brand: String = "企鹅"
    }
    
    // 动态非强制混入特质 with,支持多混入
    class Bear(nickName: String) {
      val _name: String = nickName
    }
  2. 调用,动态混入

    scala 复制代码
    // 复合类型 Penguin with Animal
    val penguin: Penguin with Animal = new Penguin() with Animal {
      override val name: String = "阿童木"
    
      override def cry(): Unit = println(s"$brand $name is crying")
    }
    penguin.cry()
    
    val bear: Bear = new Bear("熊大") with Animal with ByFoot {
      override val name: String = "狗熊"
    
      override def cry(): Unit = println(s"$name ${_name} is crying")
    
      override def jog(): Unit = println(s"$name ${_name} is jogging")
    
      override def run(): Unit = println(s"$name ${_name} is running")
    }

2、抽象类 VS 特质

一般【优先使用特质】:

1、抽象类在于多类公共属性和行为的抽象,重点在于封装思想,本质为类,单继承,不支持多混入

2、接口在于一类事物的属性和行为标准定义,重点在于多态思想,支持多混入,动态混入

若需要带参构造,只能使用抽象类

五、内部类

内部类是定义在另一个类内部的类。内部类可以访问外部类的成员,包括私有成员。

内部类的主要优点之一是它们可以更轻松地访问外部类的状态,而不需要显式地传递引用

Java中内部类是【外部类的成员】:

InClass ic = new OutClass.InClass()

Scala中内部类是【外部类对象的成员】:

val oc = new OutClass();

val ic = new oc.InClass();

  1. 创建

    scala 复制代码
    class OutClass(name:String,age:Int,gender:String,school:String,major:String) {
      class InnerClass(age:Int,gender:String){
        private var _age:Int = age
        private var _gender:String = gender
    
        def getAge = _age
        def getGender = _gender
    
        def setAge(age:Int) = _age = age
        def setGender(gender:String) = _gender = gender
      }
    
      private val _name:String = name
      private var _in:InnerClass = new InnerClass(age, gender)
      var _in2:OutClass.Inner2Class = new OutClass.Inner2Class(school, major)
    
      def setAge(age:Int) = _in.setAge(age)
      def setGender(gender:String) = _in.setGender(gender)
      def setIn(in:InnerClass) = _in = in
      def setIn2(in2:OutClass.Inner2Class) = _in2 = in2
    
      override def toString: String = 
        s"${_name},${_in.getAge},${_in.getGender},${_in2._school},${_in2._major}"
    }
    object OutClass{
      class Inner2Class(school:String,major:String){
        val _school:String = school
        val _major:String = major
      }
    }

    调用

    scala 复制代码
    val oc = new OutClass("henry",22,"male","xx","通信")
    oc.setAge(33)
    oc.setGender("female")
    println(oc)
    
    val in = new oc.InnerClass(30, "female") // 外部类对象.内部类(...)
    oc.setIn(in)
    
    val in2 = new OutClass.Inner2Class("xxx","人工智能")
    oc.setIn2(in2)

六、样例类

描述【不可变值】的对象

样例类构造参数默认声明为 val,自动生成 getter

样例类的构造参数若声明为 var,自动生成 getter & setter

样例类自动生成伴生对象

样例类自动实现的其他方法:toString,copy,equals,hashCode

样例类伴生对象实现的方法:apply, unapply(用于模式匹配)

普通类的模式匹配案例

scala 复制代码
case class Student(name:String, age:Int)	// 构造参数默认 val
case class Point(var x:Int,var y:Int)		// var 需要显式声明
scala 复制代码
// val obj: Any = Student("张三", 18)
val obj: Any = Point(10, 20)
val info = obj match {
    case Student(_, 22) => "年龄22"
    case Student(name, _) if name.startsWith("张") => "姓张"
    case Point(a, _) => s"$a"
}
println(info)
scala 复制代码
// 追加伴生对象并实现 apply & unapply 
object Point{
  def apply(x: Int, y: Int): Point = new Point(x, y)
  def unapply(arg: Point): Option[(Int, Int)] = Some((arg._x,arg._y))
}

七、枚举

单例对象通过继承 Enumeration 实现枚举创建,简单易用,但不可扩展

通常用于定义一个有限取值范围的常量

scala 复制代码
object WeekDay extends Enumeration {
  val MON = Value(0)
  val TUE = Value(1)
  val WEN = Value(2)
  val THU = Value(3)
  val FRI = Value(4)
  val SAT = Value(5)
  val SUN = Value(6)
}
scala 复制代码
val wd = WeekDay.WEN
val info = wd match {
    case WeekDay.MON => "星期一"
    case WeekDay.TUE => "星期二"
    case WeekDay.WEN => "星期三"
    case _ => "不需要"
}

八、泛型

类型参数化,主要用于集合

不同于 Java 泛型被定义在 [] 中

  1. 测试类

    scala 复制代码
    class GrandFather(name:String) {
      val _name:String = name
      override def toString: String = _name
    }
    object GrandFather{
      def apply(name: String): GrandFather = new GrandFather(name)
    }
    
    class Father(name:String) extends GrandFather(name:String) {}
    object Father{
      def apply(name: String): Father = new Father(name)
    }
    
    class Son(name:String) extends Father(name:String) {}
    object Son{
      def apply(name: String): Son = new Son(name)
    }
    
    class GrandSon(name:String) extends Son(name:String){}
    object GrandSon{
      def apply(name: String): GrandSon = new GrandSon(name)
    }
  2. 泛型边界定义

    scala 复制代码
    //	上边界:T<:A	泛型为某个类型的子类
    //	下边界:T>:A	泛型为某个类型的父类
    class MyArray[T <: Father](items:T*) {
      def join(sep:String) = items.mkString(sep)
    }
    // Type GrandFather does not conform to 【 upper bound 】 Father of type parameter T
    val arr:MyArray[GrandFather] = ...
    
    class MyArray[T >: Son](items:T*) {
      def join(sep:String) = items.mkString(sep)
    }
    // Type GrandSon does not conform to 【 lower bound 】 Son of type parameter T
    val arr:MyArray[GrandSon] = ...
  3. 型变:多态

    协变:[+T] 若A是B的子类,则 C[A]为C[B]的子类

    逆变:[-T] 若A是B的子类,则 C[B]为C[A]的子类

    不变:[T] 默认

    scala 复制代码
    class MyArray[+T](items:T*) {
      def join(sep:String) = items.mkString(sep)
    }
    // Father 是 Son 的父类,则 MyArray[Father] 就是 MyArray[Son] 的父类
    val arr:MyArray[Father] = new MyArray[Son](Son("henry"),Son("ariel"))
    
    class MyArray[-T](items:T*) {
      def join(sep:String) = items.mkString(sep)
    }
    // Father 是 Son 的子类,则 MyArray[Son] 就是 MyArray[Father] 的子类
    val arr:MyArray[Son] = new MyArray[Father](Son("henry"),Son("ariel"))
    
    class MyArray[T](items:T*) {
      def join(sep:String) = items.mkString(sep)
    }
    // 所有泛型都必须为 Son
    val arr:MyArray[Son] = new MyArray[Son](Son("henry"),Son("ariel"))

九、隐式类

用implicit关键字修饰的类,扩展其主构造器唯一参数类型的功能

只能在类、Trait、对象(单例对象、包对象)内部定义

构造器只能携带一个非隐式参数

隐式类不能是 case class

在同一作用域内,不能有任何方法、成员或对象与隐式类同名

隐式类必须有主构造器且只有一个参数

  1. 隐式参数:隐式传入

    场景:多个函数共享同一个参数,选择柯里化,将最后一个列表设计为该共享参数的唯一参数,并将该参数设置为 implicit

    scala 复制代码
    implicit order:Ordering[Int] = Ordering.Int.reverse
    val sorted = Array(8,1,3,2,5).sorted(implicit order:Ording[Int])
  2. 隐式函数:隐式类型转换

    scala 复制代码
    implicit def strToInt(str:String) = str.toInt
    val a:Int = "12"
  3. 隐式类:扩展

    scala 复制代码
    // 字符串的方法扩展,而在 Java 中 String 是final的,无法扩展
    implicit class StrExt(str:String){
        def incr() = str.map(c=>(c+1).toChar)
        def isEmail = str.matches("\\w+@[a-z0-9]{2,10}\\.(com(.cn)?|cn|edu|org)")
    }
    scala 复制代码
    val a:String = "12665473@qq.com"
    val incr: String = a.incr
    val isEmail: Boolean = a.isEmail

十、包与包对象

包命名规则:字母、数字、下划线、点,不能以数字开头,在【一个类文件中可以定义多个并列的包】

导包的不同方式

scala 复制代码
import com.org.Person				// 方便使用类 Person
import com.org._					// 方便使用 com.kgc 包中的所有类
import com.org.Person._				// 方便使用类 Person 中的所有属性和方法
import com.org.{Person=>PS,Book}	// 只导入包中 Person和Book,并将Person重命名为PS

不同于Java:import 导包语句可以出现在任意地方

可以导入包、类、类成员

单个文件多包结构:资源按包名语义分类存放,方便管理和使用

测试样例,import 导包语句可以出现在任意地方

scala 复制代码
package cha03{
  import cha03.util.Sorts				// 导包
  object PackageTest {
    def main(args: Array[String]): Unit = {
      val array: Array[Int] = Array(3, 1, 5, 4, 2)
      Sorts.insertSort(array)
      array.foreach(println)
    }
  }
}

package cha03.util{
  object Sorts{
    def insertSort(array: Array[Int]): Unit ={
      import scala.util.control.Breaks._	// 导包	
      for(i<- 1 until array.length){
        val t = array(i)
        var j = i-1
        breakable({
          while (j>=0){
            if(array(j)>t){
              array(j+1) = array(j)
            }else{
              break()
            }
            j-=1
          }
        })
        array(j+1) = t
      }
    }
  }
}

包对象

包中可以包含:类、对象、特质...

包对象可以包含:除了类、对象、特质外,还可以包含变量和方法

scala 复制代码
package test {
  // 导包(包在下面定义)
  import test.util.Constants._ // 导入 test.util.Constants 包对象中的所有成员,包括 PI 和 getQuarter 方法
  import test.util.Constants.{DataFormat => DF} // 导入 test.util.Constants 包对象中的 DataFormat 类,并将其重命名为 DF
  object PackageTest {
    def main(args: Array[String]): Unit = {
      println(PI * 2 * 2)
      println(getQuarter(5))
      val format: DF = DF(2024, 3, 29)
      println(format.stdYMD())
      println(format.stdFull())
      println(format.timestamp())
    }
  }
}

package test.util {
  import java.util.Calendar

  // 包对象
  package object Constants {
    // 变量
    val PI: Float = 3.14f
    // 方法
    def getQuarter(month: Int) = (month - 1) / 3 + 1
    // 类
    class DataFormat(
                      year: Int, month: Int, day: Int,
                      hour: Int, minute: Int, second: Int,
                      millis: Int) {
      private var _year: Int = year
      private var _month: Int = month
      private var _day: Int = day
      private var _hour: Int = hour
      private var _minute: Int = minute
      private var _second: Int = second
      private var _millis: Int = millis

      def this(year: Int, month: Int, day: Int) {
        this(year, month, day, 0, 0, 0, 0)
      }

      def stdYMD(): String = s"${_year}-${_month}-${_day}"

      def stdFull(): String = s"${_year}-${_month}-${_day} ${_hour}:${_minute}:${_second}.${_millis}"

      def timestamp(): Long = {
        val cld = Calendar.getInstance()
        cld.set(_year, _month, _day, _hour, _minute, _second)
        cld.set(Calendar.MILLISECOND, 555)
        cld.getTimeInMillis
      }
    }
    object DataFormat {
      def apply(year: Int, month: Int, day: Int,
                hour: Int, minute: Int, second: Int, millis: Int): DataFormat
      = new DataFormat(year, month, day, hour, minute, second, millis)

      def apply(year: Int, month: Int, day: Int): DataFormat
      = new DataFormat(year, month, day)
    }
  }
}

练习

1、练习一

需求说明

  • 假设类Book有属性 title(标题) 和 author(作者,多个)
    • 实现Book类,同时使用主构造器与辅助构造器
    • 实现Book的伴生对象,使用伴生对象创建Book实例
  • 创建books,使用List[Book]初始化5个以上Book实例
  • 找出books中书名包含"xxx"的书,并打印书名
  • 找出books中作者(多个,一个含有即可) 名以"xxx"开头的书,并打印书名
  1. 类和伴生对象

    scala 复制代码
    // 主构造器
    class Book(title:String, authors:Array[String]) {
      private val _title:String = title
      private val _authors:Array[String] = authors
        
      // 判断标题是否含有sub子串
      def titleContains(sub:String): Boolean = _title.contains(sub)			
      // 多个作者姓名中是否有以name为开头的姓名
      def authorContains(name:String): Boolean = _authors.count(_.startsWith(name))>0 
        
      // 辅助构造器
      def this(title:String, author:String) = this(title, Array(author))
        
      def getTitle: String = _title
      def getAuthors: Array[String] = _authors
      override def toString: String = s"$title\t${_authors.mkString("、")}"
    }
    // 伴生对象
    object Book{
      def apply(title: String, authors: Array[String]): Book = new Book(title, authors)
      def apply(title: String, author: String): Book = new Book(title, author)
    }
  2. 创建Book实例,并查找

    scala 复制代码
    def main(args: Array[String]): Unit = {
        // 使用伴生对象创建Book实例(没有new),初始化5个Book实例
        val list = List(
            Book("武侠:最强小保安",Array("张三","李四","王五")),
            Book("都市:上门赘婿",Array("阿强","洞冥福","花花")),
            Book("武侠:翔龙会",Array("阿庆嫂","黄世仁")),
            Book("都市:缘起",Array("徐世明","张丘月")),
            Book("武侠:小李飞刀",Array("王栋","李宏","张明")),
        )
    	// 找到标题中含有 "都市" 的书 打印书名
        list.collect({
            case book if book.titleContains("都市") => book.getTitle
        }).foreach(println)
    	// 找到作者有以 "阿" 开头的书 打印书名
        list.collect({
            case book if book.authorContains("阿") => book.getTitle
        }).foreach(println)
    	// 找到标题中含有 "武侠" 的书 打印书名
        list
            .filter(_.titleContains("武侠"))
            .foreach(book=>println(book.getTitle))
    }

2、练习二

需求说明(续练习一)

  • 现在Book拥有电子版本(EBook类继承Book),可以在多终端上播放
    • 属性:作者author:String,书名title:String,类型bookType:String,内容chapters:Array[String]
    • 方法:简介 resume():Unit
  • 定义特质,方法:play()
  • 使EBook动态混入该特质,实现play()方法
  1. EBook(电子书) 类和特质创建,与实现

    scala 复制代码
    import scala.collection.mutable.ArrayBuffer
    import scala.io.StdIn.readLine              // 控制台输入
    import scala.util.control.Breaks.breakable  // 循环控制
    import scala.util.control.Breaks.break
    
    trait EAction{
      // 阅读第 chapterNo 章节
      def play(chapterNo: Int): Boolean
      // 阅读全部,默认从第一章开始阅读
      def play(): Unit
    }
    
    class EBook(title:String, authors:Array[String], bookType: String) extends Book(title, authors) with EAction {
      private val _bookType: String = bookType
      private val _chapters: ArrayBuffer[String] = ArrayBuffer()
    
      // 只有一个作者时的辅助构造器
      def this(title:String, authors: String, bookType: String) = this(title, Array(authors), bookType)
      private def chapterCount = _chapters.size
    
      // 添加章节
      def addChapter(chapter: String): Unit = {
        _chapters.append(chapter)
      }
    
      // 简介 readme
      def readme: String = s"类型: ${_bookType}, 标题: ${getTitle}, 作者: ${getAuthors.mkString("、")}, 章节数: ${chapterCount}"
    
      // 去除段落前换行,并且每行30个字符输出
      private def showChapter(chapter: String): Unit = {
        val pat = "[^\n]{1,30}".r
        val lines = pat.findAllIn(chapter)
        lines.foreach(println)
      }
    
      override def play(chapterNo: Int): Boolean = {
        if(chapterNo >= 1 && chapterNo <= chapterCount){
          showChapter(_chapters(chapterNo - 1))
          true
        }else{
          println("无此章节")
          false
        }
      }
    
      override def play(): Unit = {
          breakable{
            var characterNo = 1
            while (play(characterNo)) {
              print("是否继续阅读(y|Y): ")
              characterNo += 1
              if(!readLine().matches("y|Y")){
                break()
              }
            }
        }
    
      }
    }
    // 伴生对象,辅助创建对象
    object EBook{
      def apply(title: String, authors: String, bookType: String): EBook = new EBook(title, authors, bookType)
    }
  2. 调用

    scala 复制代码
     def main(args: Array[String]): Unit = {
        val chapters = Array(
          "我是一个失败者,几乎不怎么注意阳光灿烂还是不灿烂,因为没有时间。\n\n\n        \n我的父母没法给我提供支持,我的学历也不高,孤身一人在城市里寻找着未来。\n\n\n        \n我找了很多份工作,但都没能被雇佣,可能是没谁喜欢一个不擅长说话,不爱交流,也未表现出足够能力的人。\n\n\n        \n我有整整三天只吃了两个面包,饥饿让我在夜里无法入睡,幸运的是,我提前交了一個月房租,还能继续住在那个黑暗的地下室里,不用去外面承受冬季那异常寒冷的风。\n\n\n        \n终于,我找到了一份工作,在医院守夜,为停尸房守夜。\n\n\n        \n医院的夜晚比我想象得还要冷,走廊的壁灯没有点亮,到处都很昏暗,只能靠房间内渗透出去的那一点点光芒帮我看见脚下。\n\n\n        \n那里的气味很难闻,时不时有死者被塞在装尸袋里送来,我们配合着帮他搬进停尸房内。\n\n\n        \n这不是一份很好的工作,但至少能让我买得起面包,夜晚的空闲时间也可以用来学习,毕竟没什么人愿意到停尸房来,除非有尸体需要送来或者运走焚烧,当然,我还没有足够的钱购买书籍,目前也看不到攒下钱的希望。\n\n\n        \n我得感谢我的前任同事,如果不是他突然离职,我可能连这样一份工作都没法获得。\n\n\n        \n我梦想着可以轮换负责白天,现在总是太阳出来时睡觉,夜晚来临后起床,让我的身体变得有点虚弱,我的脑袋偶尔也会抽痛。\n\n\n        \n有一天,搬工送来了一具新的尸体。\n\n\n        \n听别人讲,这是我那位突然离职的前同事。\n\n\n        \n我对他有点好奇,在所有人离开后,抽出柜子,悄悄打开了装尸袋。\n\n\n        \n他是个老头,脸又青又白,到处都是皱纹,在非常暗的灯光下显得很吓人。\n\n\n        \n他的头发不多,大部分都白了,衣服全部被脱掉,连一块布料都没有给他剩下。\n\n\n        \n对于这种没有家人的死者,搬工们肯定不会放过额外赚一笔的机会。\n\n\n        \n我看到他的胸口有一个奇怪的印记,青黑色的,具体样子我没法描述,当时的灯光实在是太暗了。\n\n\n        \n我伸手触碰了下那个印记,没什么特别。\n\n\n        \n看着这位前同事,我在想,如果我一直这么下去,等到老了,是不是会和他一样......\n\n\n        \n我对他说,明天我会陪他去火葬场,亲自把他的骨灰带到最近的免费公墓,免得那些负责这些事的人嫌麻烦,随便找条河找个荒地就扔了。\n\n\n        \n这会牺牲我一个上午的睡眠,但还好,马上就是周日了,可以补回来。\n\n\n        \n说完那句话,我弄好装尸袋,重新把它塞进了柜子。",
          "不好意思,我不知道会是这样的情况。莱恩很有礼貌地对卢米安道了声谦。\n\n\n        \n卢米安嘿嘿笑道:\n\n\n        \n这是不是值又一杯'绿仙女'?\n\n\n        \n不等莱恩回答,他转移了话题:\n\n\n        \n外乡人,你们来科尔杜做什么,收购羊毛、皮革?\n\n\n        \n科尔杜有不少居民以牧羊为生。\n\n\n        \n莱恩无声松了口气,抓住这个契机道:\n\n\n        \n我们来拜访你们村'永恒烈阳'教会的本堂神甫纪尧姆.贝内,可他既不在家里,也不在教堂。\n\n\n        \n不用说是哪个教会的,科尔杜只有一家教会。喝了莱恩免费苦艾酒的皮埃尔好心提醒了一句。\n\n\n        \n吧台周围的其他本地人各自喝着酒,没谁回答莱恩的问题,似乎那个名字代表着某种禁忌或者权威,不能随便谈论。\n\n\n        \n卢米安喝了口酒,思索了几秒道:\n\n\n        \n我大概能猜到本堂神甫在哪里,需要我带你们去吗?\n\n\n        \n那就麻烦你了。莉雅没有客气。\n\n\n        \n莱恩跟着点了点头:\n\n\n        \n等你喝完这一杯。\n\n\n        \n好的。卢米安端起酒杯,咕噜咕噜喝完了淡绿色的液体。\n\n\n        \n他把杯子一放,站了起来:\n\n\n        \n走吧。\n\n\n        \n真是太感谢了。莱恩边招呼瓦伦泰和莉雅起身,边向卢米安致意。\n\n\n        \n卢米安脸上露出了笑容:\n\n\n        \n没关系,你们听了我的故事,我又喝了伱们的酒,大家算是朋友了,对吧?\n\n\n        \n是的。莱恩轻轻点头。\n\n\n        \n卢米安脸上的笑容愈发灿烂,伸出双臂,似乎要给对方一個拥抱。\n\n\n        \n与此同时,他热忱说道:"
        )
        // 创建ebook对象
        val ebook = EBook("宿命之环", "爱潜水的乌贼", "玄幻")
        chapters.foreach(chapter =>  ebook.addChapter(chapter))  // 给书添加章节
        println(ebook.readme)     	 // 输出简介
        // ebook.play(1)			 // 阅读第一章节
        ebook.play()				 // 阅读全部
    }

下接: Scala 补充 正则、异常处理...

相关推荐
凡人的AI工具箱1 分钟前
每天40分玩转Django:实操图片分享社区
数据库·人工智能·后端·python·django
数据小小爬虫13 分钟前
Python爬虫获取AliExpress商品详情
开发语言·爬虫·python
小爬虫程序猿14 分钟前
利用Python爬虫速卖通按关键字搜索AliExpress商品
开发语言·爬虫·python
一朵好运莲20 分钟前
React引入Echart水球图
开发语言·javascript·ecmascript
Eiceblue33 分钟前
使用Python获取PDF文本和图片的精确位置
开发语言·python·pdf
Q_192849990637 分钟前
基于Spring Boot的个人健康管理系统
java·spring boot·后端
liutaiyi837 分钟前
Redis可视化工具 RDM mac安装使用
redis·后端·macos
xianwu54341 分钟前
反向代理模块。开发
linux·开发语言·网络·c++·git
Q_192849990644 分钟前
基于Springcloud的智能社区服务系统
后端·spring·spring cloud
xiaocaibao7771 小时前
Java语言的网络编程
开发语言·后端·golang