Scala 第三篇 OOP篇

Scala 第三篇 OOP篇

上接:Scala 第二篇 算子篇

  • 前序
  • 一、类
    • 1、修饰符
    • 2、创建类示例
    • 3、类的继承
  • 二、抽象类
  • 三、单例对象
  • 四、特质
    • 1、动态混入
    • 2、抽象类 VS 特质
  • 五、内部类
  • 六、样例类
  • 七、枚举
  • 八、泛型
  • 九、隐式类
  • 十、包与包对象
  • 练习
    • 1、练习一
    • 2、练习二

前序

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)YYYYY
protectedYYYNN
privateYYNNN

2、创建类示例

  1. 创建类

    // 类自身:就是类的【主】构造器
    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. 创建对象与方法调用

    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. 类的继承

    // 继承关键字: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. 调用

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

二、抽象类

关键字:abstract

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

示例代码

// 抽象类
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. 伴生类,伴生对象(单例对象)的创建

    // 伴生类:与伴生对象具有相同名称的类被称为伴生类。
    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. 调用

    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 实现多混入

// 定义抽象类 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. 特质类的定义

    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. 调用,动态混入

    // 复合类型 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. 创建

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

    调用

    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(用于模式匹配)

普通类的模式匹配案例

case class Student(name:String, age:Int)	// 构造参数默认 val
case class Point(var x:Int,var y:Int)		// var 需要显式声明
// 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)
// 追加伴生对象并实现 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 实现枚举创建,简单易用,但不可扩展
通常用于定义一个有限取值范围的常量

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)
}
val wd = WeekDay.WEN
val info = wd match {
    case WeekDay.MON => "星期一"
    case WeekDay.TUE => "星期二"
    case WeekDay.WEN => "星期三"
    case _ => "不需要"
}

八、泛型

类型参数化,主要用于集合
不同于 Java 泛型被定义在 [] 中

  1. 测试类

    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. 泛型边界定义

    //	上边界: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] 默认

    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

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

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

    // 字符串的方法扩展,而在 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)")
    }
    
    val a:String = "12665473@qq.com"
    val incr: String = a.incr
    val isEmail: Boolean = a.isEmail
    

十、包与包对象

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

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 导包语句可以出现在任意地方

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

包对象

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

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. 类和伴生对象

    // 主构造器
    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实例,并查找

    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(电子书) 类和特质创建,与实现

    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. 调用

     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 补充 正则、异常处理…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/603577.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVA版本的ATM编程问题记录

前段时间用C语言写了个银行ATM系统&#xff0c;还写了一篇文章记录了一些&#xff0c;C语言的ATM文章。后来又用IDEA写了一个JAVA版本的银行ATM。有人就会问为啥浪费这个时间写ATM呢&#xff1f;&#x1f9d0;其实是我本科代码没学好&#xff0c;所以现在想利用比较熟悉的ATM系…

探秘编程之旅:Baidu Comate 智能代码助手的魔法揭秘

目录 Baidu Comate智能代码助手1.场景需求2.安装步骤3.功能介绍3.1 /指令3.2 插件3.3 #知识 4.使用体验5.总结 Baidu Comate智能代码助手 智能编程助手的意义在于提升编程体验和效率&#xff0c;使开发人员能够更轻松、更快速地完成编码任务&#xff0c;是如今人工智能技术的一…

Flink DataSink介绍

介绍 Flink DataSink是Apache Flink框架中的一个重要组件&#xff0c;它定义了数据流经过一系列处理后最终的输出位置。以下是关于Flink DataSink的详细介绍&#xff1a; 概念&#xff1a;DataSink主要负责对经过Flink处理后的流进行一系列操作&#xff0c;并将计算后的数据结…

Linux学习笔记1

1.背景认知 可能很多人还没有接触Linux&#xff0c;会有点畏惧&#xff0c;我们可以把Linux类比成Windows&#xff0c; 下面是Windows和Linux的启动对比 Windows&#xff1a;上电后一开始屏幕是黑黑的---bios在启动Windows----Windows之后找到c盘启动各种应用程序 Linux&am…

OFDM802.11a的FPGA实现(十)导频插入(含verilog和matlab代码)

原文链接&#xff08;相关文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA实现 目录 1.前言2.插入导频原理3.硬件实现4.Matlab仿真5.ModelSim仿真6.结果对比验证7.verilog代码 1.前言 前面一篇文章完成了星座图的映射&#xff0c;今天继续设计后面的模块。在接收机…

【Keil程序大小】Keil编译结果Code-RO-RW-ZI分析

【Keil程序大小】Keil编译结果Code-RO-RW-ZI分析 下图为keil编译后的结果&#xff1a; 单位为Byte。Code是程序大小。RO是常量大小。RW是读写变量占用大小&#xff0c;如已初始化的静态变量和全局变量。ZI是全零变量占用大小&#xff0c;如未初始化的static修饰的静态变量、全局…

聊聊BitLocker

最近有消息称微软决定在Windows 11 24H2中默认开启BitLocker&#xff0c;这个消息在网上引起了不小的波澜。有人说&#xff0c;对于我们这些普通用户来说&#xff0c;BitLocker真的有必要吗&#xff1f; 什么是BitLocker BitLocker 是一项 Windows 安全功能&#xff0c;可为整…

如何使用多协议视频汇聚/视频安防系统EasyCVR搭建智慧园区视频管理平台?

智慧园区作为现代化城市发展的重要组成部分&#xff0c;不仅承载着产业升级的使命&#xff0c;更是智慧城市建设的重要体现。随着产业园区竞争的逐渐白热化&#xff0c;将项目打造成完善的智慧园区是越来越多用户关注的内容。 然而我们往往在规划前期就开始面临众多难题&#…

如何制作有趣的gif?这个方法别错过

是否在社交媒体上看到过很多有趣好玩的gif动图&#xff0c;有的搞笑有趣有的又很可爱。大家有没有想过自己动手制作gif动画呢&#xff1f;接下来&#xff0c;就给大家分享一招gif在线制作&#xff08;https://www.gif5.net/&#xff09;的方法&#xff0c;超简单不需要下载任何…

什么牌子的洗地机质量最好?四款耐用高分产品推荐

洗地机具备了吸尘、擦拭、除菌等多种功能&#xff0c;可以一次完成多种清洁任务&#xff0c;帮助用户更高效地保持家居整洁&#xff0c;节省时间和精力&#xff0c;备受人们的喜爱。但是怎么挑选到优质的洗地机一直是大家关注的问题。今天&#xff0c;笔者将结合自己在家电行业…

什么是驱动数字签名?如何获取驱动数字签名?

Windows 驱动程序承载着计算机实现的各种内核和用户模式功能。如果驱动程序被黑客攻击&#xff0c;可能会产生很多问题。Windows通过数字签名来验证驱动程序包的完整性及发布者的身份。2020年10月的安全更新中&#xff0c;微软加强了对驱动软件的验证&#xff0c;如果Windows无…

【微积分听课笔记】全微分,二元极值,Double Integral

6.6 二元函数的极值_哔哩哔哩_bilibili 此笔记为听课笔记&#xff0c;宋浩老师微积分~ 最近诸事缠身&#xff0c;会有种会不会只做一件事好些。实际上&#xff0c;关键在于动力&#xff0c;我不可能每次都准备充分。动力&#xff0c;分配&#xff0c;这是目前进入大学我正在学…

【yolov8 项目打包】pyinstaller 打包pyQt5 界面为exe

创建一篇博客文章&#xff0c;介绍如何使用PyInstaller将PyQt5界面打包为exe文件&#xff0c;并且处理与YOLOv8模型相关的文件&#xff0c;可以按照以下结构进行&#xff1a; 标题&#xff1a;使用PyInstaller将PyQt5界面与YOLOv8模型打包为Windows可执行文件 引言 在机器学习…

vue视图不刷新强制更新数据this.$forceUpdate()

在vue中&#xff0c;更新视图数据&#xff0c;不刷新页面&#xff0c;需要强制更新数据才可以 前言 在对数据就行添加和删除时&#xff0c;发现页面视图不更新&#xff0c;排除发现需要强制更新才可以 点击添加或删除&#xff0c;新增数据和删除就行&#xff0c;但在不使用fo…

如何vscode中刷力扣

推荐你阅读 互联网大厂万字专题总结 Redis总结 JUC总结 操作系统总结 JVM总结 Mysql总结 微服务总结 互联网大厂常考知识点 什么是系统调用 CPU底层锁指令有哪些 AQS与ReentrantLock原理 旁路策略缓存一致性 Java通配符看这一篇就够 Java自限定泛型 技术分享 如何vscode中刷力扣…

视频号小店想要长久发展,做店的核心是什么?一篇详解!

大家好&#xff0c;我是电商小V 想要做好视频号小店&#xff0c;那么他的核心是什么呢&#xff1f; 视频号小店的核心还是商品&#xff0c;其实电商运营底层的逻辑都是一样的&#xff0c;都是以商品为核心去运营的&#xff0c;再说的浮夸一点就是&#xff0c;你的商品选择的好&…

【实战】采用jenkins pipeline实现自动构建并部署至k8s

文章目录 前言部署jenkins编写docker-compose-jenkins.yaml配置maven源启动jenkins解锁jenkins Jenkins默认插件及git、镜像仓库、k8s凭证配置host key verification configuration修改为不验证Gitee ssh阿里云镜像仓库ssh编写pipeline安装以下常用插件将kubectl命令文件拷贝到…

System Verilog通过CORDIC算法迭代16次求sin和cos值

求5~85度的sin和cos值 其它角度和Verilog实现代码类似&#xff0c;查表、移位和加法器 define DIE 16 //迭代次数 define PIE 3.1415926 define MUL 100_000_000 //同比放大 initial begin int die; int x[17]; int y[17]; int z[17…

学习软考----数据库系统工程师25

关系规范化 1NF&#xff08;第一范式&#xff09; 2NF&#xff08;第二范式&#xff09; 3NF&#xff08;第三范式&#xff09; BCNF&#xff08;巴克斯范式&#xff09; 4NF&#xff08;第四范式&#xff09; 总结

排序算法(Java版)

目录 1、直接插入排序2、希尔排序3、直接选择排序4、堆排序5、冒泡排序6、快速排序6.1 递归实现6.2 非递归实现 7、归并排序7.1 递归实现7.2 非递归实现 8、性能分析 今天我们学习一种算法&#xff1a;排序算法&#xff08;本文的排序默认是从小到大顺序&#xff09;&#xff0…
最新文章