当前位置: 首页 > news >正文

Scala(五)

本节课继续学习了Scala中的面向对象特质以及集合和数组

一、抽象类

(一)抽象属性和抽象方法

1.基本语法

  1. 定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
  2. 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
  3. 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法

例子:

2.继承 重写

  1. 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
  2. 重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
  3. 子类中调用父类的方法使用 super 关键字
  4. 子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
  5. 子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。
  6. 因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写

(二)匿名子类

通过包含带有定义或重写的代码块的方式创建一个匿名的子类。

例子:

二、单例对象(伴生对象)

(一)单例对象语法

1.基本语法

object Person{

val country:String="China"

}

2.说明

  1. 单例对象采用object 关键字声明
  2. 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
  3. 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。

例子:

(二)apply方法

说明

  1. 通过伴生对象的 apply 方法,实现不使用 new 方法创建对象。
  2. 如果想让主构造器变成私有的,可以在()之前加上 private。
  3. apply 方法可以重载。
  4. Scala 中 obj(arg)的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
  5. 当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的 apply 方法。

例子:

三、特质(Trait)

Scala 语言中,采用特质 trait(特征)来代替接口的概念

Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质

(一)特质声明

基本语法

trait 特质名 
{ trait 主体
}

例子:

(二)特质基本语法

1.基本语法

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

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

2.说明

  1. 类和特质的关系:使用继承的关系。
  2. 当一个类去继承特质时,第一个连接词是 extends,后面是with。
  3. 如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。

例子:

  1. 特质可以同时拥有抽象方法和具体方法
  2. 一个类可以混入(mixin)多个特质
  3. 所有的 Java 接口都可以当做Scala 特质使用
  4. 动态混入:可灵活的扩展类的功能
    1. 动态混入:创建对象时混入 trait,而无需使类混入该 trait
    2. 如果混入的 trait 中有未实现的方法,则需要实现
// 定义 PersonTrait 特质
trait PersonTrait {
  // 声明属性,初始化为默认值
  var name: String = _
  // 抽象属性
  var age: Int
  // 声明具体方法
  def eat(): Unit = {
    println("eat")
  }
  // 抽象方法
  def say(): Unit
}

// 定义 SexTrait 特质
trait SexTrait {
  var sex: String
}

// Teacher 类继承 PersonTrait 特质和 Java 接口 Serializable
class Teacher extends PersonTrait with java.io.Serializable {
  override def say(): Unit = {
    println("say")
  }
  // 实现抽象属性 age
  override var age: Int = 30 // 这里给 age 一个具体值,可按需修改
}

object TestTrait {
  def main(args: Array[String]): Unit = {
    val teacher = new Teacher()
    teacher.say()
    teacher.eat()

    // 动态混入 SexTrait 特质
    val t2 = new Teacher with SexTrait {
      override var sex: String = "男"
    }
    // 调用混入 trait 的属性
    println(t2.sex)
  }
}

(三)特质叠加

1.一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。

2.一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala 采用了特质叠加的策略。

所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来

例子:

(四)特质叠加执行顺序

当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的 super.describe()调用的实际上是排好序后的下一个特质中的 describe() 方法。,排序规则如下:

结论:

  1. 案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass 中的 super 指代 Color,Color 中的 super 指代Category,Category 中的super指代Ball。
  2. 如果想要调用某个指定的混入特质中的方法,可以增加约束: super[],例如super[Category].describe()。

(五)特质自身类型

自身类型可实现依赖注入的功能。

例子:

(六)特质和抽象类的区别

  1. 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
  2. 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。

四、扩展

(一)​​​​​​​类型检查和转换

说明:

  1. obj.isInstanceOf[T]:判断 obj 是不是T 类型。
  2. obj.asInstanceOf[T]:将 obj 强转成 T 类型。
  3. classOf 获取对象的类名。

例子:

(二)​​​​​​​枚举类和应用类

说明:

        枚举法:需要继承 Enumeration

        应用类:需要继承App

例子:

(三)​​​​​​​Type 定义新类型

使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名

例子:

五、集合

(一)集合简介

  1. Scala 的集合有三大类:序列 Seq、集Set、映射 Map,所有的集合都扩展自 Iterable特质。
  2. 对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包
    1. 不可变集合:scala.collection.immutable
    2. 可变集合: scala.collection.mutable
  3. Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象, 而不会对原对象进行修改。类似于 java 中的 String 对象
  4. 可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于 java 中 StringBuilder 对象

(一.一)不可变集合继承图

  1. Set、Map 是 Java 中也有的集合
  2. Seq 是 Java 没有的,我们发现 List 归属到Seq 了,因此这里的 List 就和 Java 不是同一个概念了
  3. 我们前面的for 循环有一个 1 to 3,就是 IndexedSeq 下的 Range
  4. String 也是属于 IndexedSeq
  5. 我们发现经典的数据结构比如 Queue 和 Stack 被归属到 LinearSeq(线性序列)
  6. 大家注意Scala 中的 Map 体系有一个 SortedMap,说明 Scala 的 Map 可以支持排序
  7. IndexedSeq 和LinearSeq 的区别:
    1. IndexedSeq 是通过索引来查找和定位,因此速度快,比如String 就是一个索引集合,通过索引即可定位​​​​​​​
    2. LinearSeq 是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找

(一.二)可变集合继承图

(二)数组

(二.一)不可变数组

1. val arr1 = new Array[Int](10)

(1)new 是关键字

(2)[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定Any

(3)(10),表示数组的大小,确定后就不可以变化

例子:

object TestArray {
  def main(args: Array[String]): Unit = {
    // (1)数组定义:创建一个长度为 4 的整数数组
    val arr01 = new Array[Int](4)
    println(s"数组 arr01 的长度: ${arr01.length}")

    // (2)数组赋值
    // (2.1)修改某个元素的值
    arr01(3) = 10
    // (2.2)采用方法的形式给数组赋值
    arr01.update(0, 1)

    // (3)遍历数组
    // (3.1)查看数组:将数组元素以逗号分隔的字符串形式输出
    println(s"数组 arr01 的元素: ${arr01.mkString(",")}")

    // (3.2)普通遍历
    println("普通遍历数组 arr01:")
    for (i <- arr01) {
      println(i)
    }

    // (3.3)简化遍历
    def printx(elem: Int): Unit = {
      println(elem)
    }
    println("使用 foreach 和自定义方法遍历数组 arr01:")
    arr01.foreach(printx)

    println("使用 foreach 和匿名函数遍历数组 arr01:")
    arr01.foreach((x) => println(x))

    println("使用 foreach 和占位符遍历数组 arr01:")
    arr01.foreach(println(_))

    println("使用 foreach 直接传入 println 方法遍历数组 arr01:")
    arr01.foreach(println)

    // (4)增加元素(由于创建的是不可变数组,增加元素,其实是产生新的数组)
    println(s"原始数组 arr01: ${arr01.mkString(",")}")
    val ints: Array[Int] = arr01 :+ 5
    println(s"添加元素后的新数组 ints: ${ints.mkString(",")}")
  }
}    

2. val arr1 = Array(1, 2)

(1)在定义数组时,直接赋初始值

(2)使用apply 方法创建数组对象

例子:

(二.二)可变数组

定义变长数组:   val arr01 = ArrayBuffer[Any](3, 2, 5)

(1)[Any]存放任意数据类型

(2)(3, 2, 5)初始化好的三个元素

(3)ArrayBuffer 需要引入 scala.collection.mutable.ArrayBuffer

例子:

  1. ArrayBuffer 是有序的集合
  2. 增加元素使用的是 append 方法(),支持可变参数
import scala.collection.mutable.ArrayBuffer

object TestArrayBuffer {
  def main(args: Array[String]): Unit = {
    // (1)创建并初始赋值可变数组
    val arr01 = ArrayBuffer[Any](1, 2, 3)
    println("初始数组 arr01 的元素:")
    for (i <- arr01) {
      println(i)
    }
    println(s"初始数组 arr01 的长度: ${arr01.length}")
    println(s"初始数组 arr01 的哈希码: arr01.hash=${arr01.hashCode()}")

    // (3)增加元素
    // (3.1)追加数据
    arr01 += 4
    // (3.2)向数组最后追加数据
    arr01.append(5, 6)
    // (3.3)向指定的位置插入数据
    arr01.insert(0, 7, 8)
    println(s"添加元素后数组 arr01 的哈希码: arr01.hash=${arr01.hashCode()}")

    // (4)修改元素
    arr01(1) = 9 // 修改第 2 个元素的值
    println("修改元素后数组 arr01 的元素:")
    for (i <- arr01) {
      println(i)
    }
    println(s"修改元素后数组 arr01 的长度: ${arr01.length}")
  }
}   

(二.三)​​​​​​​不可变数组与可变数组的转换

说明:

arr1.toBuffer //不可变数组转可变数组

arr2.toArray //可变数组转不可变数组

  1. arr2.toArray 返回结果才是一个不可变数组,arr2 本身没有变化
  2. arr1.toBuffer 返回结果才是一个可变数组,arr1 本身没有变化

例子:

(二.四)​​​​​​​多维数组

定义:     val arr = Array.ofDim[Double](3,4)

说明:二维数组中有三个一维数组,每个一维数组中有四个元素

例子:

相关文章:

  • 郑州网络营销顾问郑州seo代理外包
  • 电子政务与网站建设的经验软文广告发稿
  • 微信小程序电脑端打开黑帽seo联系方式
  • 营销型单页面网站制作seo技术分享博客
  • 如何建网站韩国热搜榜
  • 政府网站集群建设意义数据分析培训班
  • 【Anaconda环境绑定指南】3步将自定义环境注入Jupyter Notebook 内核 | 附详细命令与演示
  • [Redis]Redis学习开篇概述
  • Linux 容器环境磁盘空间不足问题及解决方案
  • 最新源支付V7开源1.9.9版
  • 零基础使用AI从0到1开发一个微信小程序
  • KisFlow-Golang流式实时计算案例(四)-KisFlow在消息队列MQ中的应用
  • 讲解机器学习中的 K-均值聚类算法及其优缺点
  • 哈希表+前缀和+滑动窗口高效查找——蓝桥杯例题
  • 【算法】反转单向链表 链表和数组的区分 时间复杂度
  • 野指针成因及避免方法
  • 制造OA系统怎么选?有没有简单易用的?
  • Android Fresco 框架编解码模块源码深度剖析(三)
  • 数据结构与算法面试题精讲)C++版——day4
  • day20 学习笔记
  • PyTorch中的损失函数
  • 【Django】教程-10-ajax请求Demo,结合使用
  • 算法导论(动态规划)——子数组系列
  • 了解Docker容器的常见退出状态码及其含义
  • dify新版本1.1.3的一些问题
  • 生成对抗网络(GAN)详解(代码实现)