Scala 学习笔记:从基础控制流到泛型变体

要点概括

  • 类型系统与控制结构:解析 Scala 的 Any / Unit / Nothing 类型 hierarchy、for 表达式推导以及 break 的替代策略。
  • 高效数组操作:深入分析定长 Array 与变长 ArrayBuffer 的性能差异及 for/yield 高效处理机制。
  • 映射与元组:总结不可变与可变 Map 的使用及 Tuple 访问规则。
  • 面向对象与反编译视角:透过 JVM javap 视角解读 Scala 的字段 Getter/Setter 生成机制、伴生对象及各种构造器类型。
  • 继承与初始化陷阱:剖析类继承中的字段重写规范,以及构造器调用时由于 val 重写导致的空指针/零值陷阱与解决方案。
  • 泛型与类型边界:介绍泛型上界/下界的定义与类型推导。

一、 控制结构与函数(Control Structures & Functions)

1. Scala 类型 Hierarchy 基础

  • 公共超类型:在 Scala 中,java.lang.String 与原生类型(如 Int)的公共超类型是 Any
  • Unit 类型:相当于 Java 与 C++ 中的 void,其唯一的值为 ()
  • 块表达式的值:由 {} 括起来的块,其返回值就是最后一个表达式的值
val distance = { 
  val dx = x - x0 
  val dy = y - y0 
  sqrt(dx * dx + dy * dy) 
}

  • 赋值语句的返回值:赋值语句(如 x = 1)的返回值为 Unit。因此 Scala 不支持连续赋值链:
x = y = 1 // 错误:不能把 Unit 赋值给 x

2. 字符串格式化

Scala 支持类似 C 语言 printf 的格式化输出:

printf("Hello, %s! You are %d years old.\n", "Fred", 42)

3. 控制流:Break 与 For 推导式

  • Break 的实现与建议:Scala 语言本身没有 break 关键字,而是通过异常机制实现:
import scala.util.control.Breaks._
breakable {
  for (ch <- "Hello World") {
    if (ch == ' ') break; // 跳出 breakable 块
    println(ch)
  }
}

设计建议:官方通常建议尽量避免使用 break,推荐通过循环条件或函数式编程(如 takeWhilefilter 等)来实现逻辑控制。在函数中需要提前返回时,可以使用 return 语句(但应谨慎使用)。

  • for ... yield 推导式
    推导式生成的集合类型,与第一个生成器(Generator)的类型保持兼容。
// 第一个生成器是 String,结果为 String
scala> for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
res26: String = HIeflmlmop

// 第一个生成器是 Range,结果为 Vector (IndexedSeq)
scala> for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
res27: scala.collection.immutable.IndexedSeq[Char] = Vector(H, e, l, l, o, I, f, m, m, p)

4. 函数参数特性

  • 默认参数与命名参数:支持为参数提供默认值,并且在调用时可以打乱顺序指定参数名:
def decorate(str: String, left: String = "[", right: String = "]") = left + str + right

// 颠倒参数顺序调用
println(decorate(left = "<<<", str = "Hello", right = ">>>"))

  • 变长参数(Variable Arguments)
    参数类型会被封装为 Seq[T]。若需传入一个区间或序列,需使用 : _* 语法将其展开为参数序列:
def sum(args: Int*) = {
  var result = 0
  for (arg <- args) result += arg
  result
}

// val s = sum(1 to 5)      // 错误:类型不匹配
val s2 = sum(1 to 5: _*)   // 正确:使用 : _* 将 Range 转换为参数序列

  • 过程(Procedure):在 Scala 中,把没有返回值(返回类型为 Unit)的函数称为过程。

二、 数组与集合操作(Working with Arrays)

1. 定长数组(Array)

Scala 中的 Array 在 JVM 底层是直接映射为 Java 原生类型数组(例如 Array(2,3,5,7) 在 JVM 中就是 int[]),性能极高。

// 两种实例化方式
val a = new Array[String](10) // 长度为 10 的 String 数组,初始值为 null
val s = Array("Hello", "World") // 利用 apply 方法构造

// 使用小括号 () 按索引(从 0 开始)访问和更新
s(0) = "Goodbye"

2. 变长数组(ArrayBuffer)

类似于 Java 的 ArrayList 或 C++ 的 std::vector。实例化时可省略 new

import scala.collection.mutable.ArrayBuffer

val b = ArrayBuffer[Int]()
b += (1, 2, 3, 5)        // 追加多个元素
b ++= Array(8, 13, 21)   // 追加集合
b.trimEnd(5)             // 移除末尾的 5 个元素

// Array 与 ArrayBuffer 相互转换
val a = b.toArray
val b2 = a.toBuffer

3. 高效处理数组算法对比(性能实测)

需求:删除数组缓冲区中除了第一个负数以外的所有负数。

  • 常规写法(直接在原 ArrayBuffer 上删除)
    每次调用 remove 都会导致后续元素前移,时间复杂度为 O(N2)O(N^2)O(N2)
def removeAllNegativesExceptTheFirst(a: ArrayBuffer[Int]) = {
  var first = true
  var n = a.length
  var i = 0
  while (i < n) {
    if (a(i) >= 0) i += 1
    else {
      if (first) { first = false; i += 1 }
      else { a.remove(i); n -= 1 }
    }
  }
}

  • 优化写法(利用 for/yield 收集索引,二次覆写)
    先收集保留元素的索引,再按顺序覆写并裁剪末尾,时间复杂度降至 O(N)O(N)O(N)
def removeAllNegativesExceptTheFirstOptimized(a: ArrayBuffer[Int]) = {
  var first = true
  val indexes = for (i <- 0 until a.length if first || a(i) >= 0) yield {
    if (a(i) < 0) first = false
    i
  }
  for (j <- 0 until indexes.length) a(j) = a(indexes(j))
  a.trimEnd(a.length - indexes.length) 
}

  • 实测耗时对比(数据量:40 万)
  • 第一种算法:耗时约 13.40 秒
  • 第二种优化算法:耗时约 0.47 秒(性能提升近 30 倍!)

4. 字符串输出与多维数组

  • toString 表现差异
    Array.toString 会直接调用 Java 原生数组无用的 toString(如 [I@b73e5);而 ArrayBuffer 则会输出可读的详细内容 ArrayBuffer(1, 7, 2, 9)
  • 多维数组:使用 Array.ofDim 创建多维数组:
val matrix = Array.ofDim[Double](3, 4) // 3行4列
matrix(0)(3) = 17.29


三、 映射与元组(Maps and Tuples)

1. Map 的使用

Scala 中的 Map 默认是不可变(immutable.Map)的:

val scores = Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)

// 安全查找
scores.contains("Fred")       // false
scores.getOrElse("Bob", 0)    // 3
scores.get("Fred")            // 返回 Option[Int] = None

  • var 变量修饰不可变 Map
    若 Map 声明为 var,可以通过 + / - 运算符对变量重新赋值(底层生成了新的 Map 对象对象):
var scores = Map("Alice" -> 10, "Bob" -> 3)
scores = scores + ("Bob" -> 10, "Fred" -> 7) // 增加/覆盖
scores = scores - "Alice"                  // 删除 key

2. 元组(Tuples)

元组是不同类型值的聚集。注意:元组的索引下标是从 1 开始的(如 t._1)。


四、 类设计与反编译(Classes & JVM Interop)

1. 类定义与统一访问原则

Scala 中类默认是 public 的。
在调用无参方法时:**建议对修改对象状态的方法(Mutator Method)保留 (),对仅读取状态的方法(Accessor Method)省略 ()**

2. 从 javap 视角看 Getter 与 Setter 生成

在 Scala 中,声明一个字段,编译器会自动在 JVM 层生成对应的私有变量及 Getter/Setter 方法:

class Person {
  var age = 0
}

使用 javap -private Person 反编译 .class 文件:

public class Person {
  private int age;
  public int age();              // Getter 方法
  public void age_$eq(int);      // Setter 方法(因为 JVM 不允许方法名包含 '=',转义为 $eq)
  public Person();
}

统一访问原则(Uniform Access Principle):由 Bertrand Meyer 提出。指出模块提供的所有服务都应通过统一的语法提供,而不泄露该服务是来自于“存储计算”还是“实时计算”。Scala 的 Getter/Setter 机制正是这一原则的体现。

字段访问控制汇总

声明类型生成的字段与方法
val name: String生成私有 final 字段,仅生成 public Getter
var name: String生成私有字段,生成 public Getter/Setter
private var name生成私有字段,生成 private Getter/Setter
private[this] var name对象私有字段,不生成 Getter/Setter

3. 构造器与嵌套类

  • 辅助构造器(Auxiliary Constructors)
  1. 名称均为 this
  2. 每一个辅助构造器的第一行代码,必须先调用之前已定义的辅助构造器或主构造器。
  • 主构造器参数带无 val/var 的区别
class Person(val name: String)  // 生成私有 final 字段 + public Getter
class Person1(name: String)     // 如果有方法使用到 name,则生成私有 final 字段;无 Getter
class Person2(name: String)     // 如果类中没有任何方法使用 name,则不保存为字段,仅作为构造参数

  • 嵌套类(Nested Classes)
    Scala 的内部类属于特定对象的实例(即 outer.Member),这与 Java 中内部类属于外部类(Outer.Member)的设计不同,更加严谨。

五、 对象与伴生(Objects & Enumerations)

1. 伴生对象(Companion Object)

Scala 没有 static 关键字。如果要同时提供实例方法和静态方法,可以通过类与同名的伴生对象结合实现,两者必须放在同一个源文件中。

class Account {
  val id = Account.newUniqueNumber()
  private var balance = 0.0
  def deposit(amount: Double) { balance += amount }
}

object Account { // 伴生对象:存放静态属性与方法
  private var lastNumber = 0
  private def newUniqueNumber() = { lastNumber += 1; lastNumber }
}

2. apply 方法

当使用 Object(...) 形式时,Scala 会自动将其转译为 Object.apply(...)

  • Array(100):调用 Array.apply(100),生成包含单元素 100Array[Int]
  • new Array(100):调用构造函数,生成一个含有 100 个 null 元素的 Array[Nothing]

3. 枚举(Enumerations)

继承 Enumeration 类来定义枚举:

object TrafficLightColor extends Enumeration {
  val Red, Yellow, Green = Value
}

// 查找方式
TrafficLightColor(0)                  // 按 ID 查找
TrafficLightColor.withName("Red")     // 按名称查找


六、 继承与类型体系(Inheritance)

1. 类型检查与转换

// 1. 类型检查与强转
if (p.isInstanceOf[Employee]) {
  val s = p.asInstanceOf[Employee]
}

// 2. 精确类型判断(非子类)
if (p.getClass == classOf[Employee])

// 3. 模式匹配(推荐)
p match {
  case s: Employee => // 按照 Employee 处理
  case _ => 
}

2. 重写字段的规则

  • def 只能重写另一个 def
  • val 只能重写另一个 val 或**不带参数的 def**
  • var 只能重写**抽象的 var**

3. 构造器初始化与 val 重写陷阱

在父类构造器中,如果初始化逻辑依赖了会被子类重写的 val 字段,会导致非预期的结果:

class Creature {
  val range: Int = 10
  val env: Array[Int] = new Array[Int](range)
}

class Ant extends Creature {
  override val range = 2
}

问题分析:当 Ant 被实例化时,会先调用 Creature 的主构造器。此时 Antrange 还未初始化(为默认值 0),导致 env 被分配为长度为 0 的数组!

解决方案

  1. 提前定义(Early Definitions)
class Ant extends {
  override val range = 2
} with Creature

  1. 使用 lazy val(懒加载)
class Creature {
  lazy val env: Array[Int] = new Array[Int](range)
}
class Ant extends Creature {
  override val range = 2
}

4. 特殊底层类型:NullNothingUnit

  • Null:其唯一实例是 null。可以赋值给任意引用类型(AnyRef),但不能赋值给值类型(AnyVal,如 Int。这有效避免了 Java 中自动装箱引起的 NPE 陷阱。
  • Nothing:没有任何实例,是所有类型的子类型。常用于泛型边界或表示非正常终止(如抛出异常)。
  • Unit:表示无有效返回值,唯一值是 ()

七、 泛型与类型边界(Generics & Type Bounds)

1. 泛型上界(Upper Bounds <:

Scala 中的原生类型(如 Int)并没有实现 java.lang.Comparable[Int],直接定义泛型约束可能报错:

class Pair[T <: Comparable[T]](val first: T, val second: T) {
  def smaller = if (first.compareTo(second) < 0) first else second
}

// new Pair(4, 2) // 类型不匹配报错:Scala 的 Int 未实现 Comparable

(注:Scala 通常使用视图界定 <% 或隐式转换 Ordering 来解决此问题)

2. 泛型下界(Lower Bounds >:)与类型推导

使用下界可以确保方法在进行元素替换或追加时,保持类型的协变安全性:

class Pair[T](val first: T, val second: T) {
  // 指定 R 为 T 的超类,当传入新元素时,类型会自动向上推导为 Pair[R]
  def replaceFirst[R >: T](newFirst: R) = new Pair(newFirst, second)
  override def toString = "(" + first + "," + second + ")"
}

若不写下界约束 [R >: T],编译器会将 R 直接推导为 Pair[Any],失去类型安全检查能力。


总结

Scala 巧妙地将面向对象(OOP)与函数式编程(FP)融合在一起。通过了解 JVM 层面的实现细节(如伴生对象、字段 Getter/Setter 生成逻辑),开发者不仅能写出简练优雅的 Scala 代码,还能有效地规避初始化顺序陷阱与类型系统中的潜在风险。

内容概要:本文围绕基于粒子群算法(PSO)的风电与水电(抽水蓄能)联合优化调度问题展开研究,旨在通过智能优化算法实现可再生能源的高效利用与电力系统的经济稳定运行。文中系统阐述了粒子群算法的核心原理及其在电力调度中的适用性,构建了综合考虑风电出力不确定性、抽水蓄能电站调节能力及系统运行约束的联合优化调度模。采用Matlab进行算法编程与仿真求解,验证了该方法在降低系统综合运行成本、提升新能源消纳水平、增强电网调峰调频能力等方面的优越性能。研究进一步设计了多种对比场景,分析不同调度策略下的系统表现,充分展示了所提模在应对复杂运行条件时的鲁棒性与实用价值。; 适合人群:具备一定电力系统分析基础和Matlab编程能力的研究生、科研人员,以及从事新能源并网调度、电力系统规划与运行等相关领域的工程师; 使用场景及目标:①应用于风电场与抽水蓄能电站的协同优化调度决策支持;②为高比例可再生能源接入的电力系统提供经济可靠的低碳调度方案;③服务于高校及科研院所中关于智能优化算法在能源系统中应用的教学与科研实验; 阅读建议:建议读者结合提供的Matlab代码深入理解算法实现细节与模构建逻辑,重点关注目标函数设计、约束条件处理及参数设置对优化结果的影响,并通过复现仿真结果来掌握粒子群算法解决复杂非线性调度问题的关键技术要点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅唱书令

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值