Scala 类入门:主构造器、字段可变性与方法定义一次看懂

远山来客
远山来客
发布时间:2026-08-23 | 120人浏览过
原创
展示辅助构造器、默认参数和命名参数三种创建对象方式的关系与适用判断。
Scala 对象创建方式对比Scala 既支持通过 this 串联辅助构造器,也支持用默认参数和命名参数简化调用。
展示 Scala 类体的组成以及对象初始化时语句执行顺序,包括字段、私有常量、方法和构造阶段输出。
Scala 类体结构与初始化顺序Scala 类体不仅能定义字段和方法,还能直接写初始化语句;这些语句会在 new。
展示 Scala 主构造器参数如何直接成为字段,并对比 var 与 val 在可变性上的差异。
Scala 主构造器与字段可变性主构造器参数一旦带上 var 或 val,就会直接参与类字段定义;是否可修改,取决于这里的选择。

前言

刚开始学 Scala 面向对象时,很多人会觉得类的写法“比 Java 短很多”,但真正上手时又容易卡在几个细节:构造器参数为什么能直接当字段用,`var` 和 `val` 到底该怎么选,类体里的语句又会在什么时候执行。下面就按从定义到实例化、从构造器到方法的顺序,把这些高频语法点拆开讲清楚,并结合示例说明哪些写法更适合日常开发。

Scala 类为什么看起来更简洁

Scala 同样支持完整的面向对象编程,但它把很多在 Java 和 C# 里需要展开写的样板代码,直接压缩进了类定义本身。对刚接触 Scala 的读者来说,最容易困惑的地方通常不是“能不能写类”,而是“构造器参数、字段、方法和初始化代码到底各放在哪里”。

这篇文章按实际使用顺序梳理 Scala 类的几个核心点:先看最基础的类和主构造器,再看 var 与 val 的差别,接着理解类体结构、辅助构造器、默认参数和命名参数,最后补上类中方法的基本写法。看完之后,你可以更清楚地判断哪些写法适合传统面向对象场景,哪些写法更符合 Scala 的简洁风格。

主构造器怎么定义,参数为什么能直接当字段用

Scala 最常见的类定义方式,是直接把构造器参数写在类名后面。下面这个例子定义了一个带两个参数的 Person 类:

class Person(var firstName: String, var lastName: String)

// Use Class Person 
val person = new Person("Andrew", "Li")
println(person.firstName + " " + person.lastName)   // Andrew Li

这里最关键的一点是:构造器参数前面如果写了 var 或 val,编译时会自动把它们变成类中的字段。因此在创建对象后,可以直接通过 person.firstName 和 person.lastName 访问。

如果参数使用的是 var,字段就是可修改的:

person.firstName = "Shuai"

println(person.firstName + " " + person.lastName)   // Shuai Li

这也是 Scala 类定义显得更紧凑的原因之一:很多字段声明、构造器赋值和基础访问逻辑,都被合并到了这一行里。

和 Java 对比,Scala 省掉了哪些样板代码

把同一个简单类分别用 Scala 和 Java 表达,差异会非常直观。

// Create a class in Scala
class Person(var firstName: String, var lastName: String)
// Create a class in Java
public class Person {

    private String firstName;
    private String lastName;
    
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    public String getFirstName() {
        return this.firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public String getLastName() {
        return this.lastName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
}

这个对比说明的不是 Scala 功能更少,而是它把“字段 + 构造器 + 基本访问”这种高频写法做了语法收敛。对于数据结构简单、以传值和封装为主的类,这种写法会明显更省代码。

构造器里用 var 还是 val

前面的 Person 使用的是 var,所以字段可变。如果改成 val,字段就变成只读:

class Person(val firstName: String, val lastName: String)

// Use Person class
val person = new Person("Andrew", "Li")
person.firstName = "Shuai"    // error: reassignment to val

这里的报错 error: reassignment to val 很直接:val 一旦初始化,就不能再次赋值。

从使用场景看,可以这样理解:

  • 如果类主要服务于传统面向对象建模,字段需要在对象生命周期中被修改,var 更方便。
  • 如果数据创建后就不应该变化,val 会更稳妥。

原文还提到一个很重要的判断:在 Scala 的函数式编程风格里,通常更常见的是 case class,而不是普通 class。因为函数式编程更强调不可变数据,不可变对象往往更容易保证程序健壮性。

一个 Scala 类内部通常包含什么

理解 Scala 类,不能只看构造器参数。一个完整的类通常还包含下面几部分:

  • 类构造器参数
  • 类主体中定义的方法
  • 在对象初始化时会直接执行的语句和表达式

也就是说,类体并不只是“放方法的地方”,它本身也可以包含字段定义和初始化逻辑。下面这个例子把这些元素放在了一起:

class Person(var firstName: String, var lastName: String) {

  println("the constructor begins")

  // 'public' access by default
  var age = 0

  // some class fields
  private val AMOUNT_OF_EYES = 2

  // some methods
  override def toString(): String = s"$firstName $lastName is $age years old"

  def printAmountOfEyes(): Unit = println(s"AMOUNT_OF_EYES = $AMOUNT_OF_EYES")

  def printDescription(): Unit = println(this)

  printAmountOfEyes()
  printDescription()
  println("you've reached the end of the constructor")
}

创建对象时:

var person = new Person("Andrew", "Li")

// When executing the above code to init person object, we can see those logs as follows:

// the constructor begins
// AMOUNT_OF_EYES = 2
// Andrew Li is 0 years old
// you've reached the end of the constructor

这个例子可以帮助你抓住两个关键事实:

类体中的语句会在 new 时立即执行

println("the constructor begins")、printAmountOfEyes()、printDescription() 这些语句不是普通说明代码,而是在 new Person(...) 时就会执行的初始化逻辑。

字段、方法和访问控制可以直接写在类体中

例如 var age = 0 是实例字段,private val AMOUNT_OF_EYES = 2 是私有字段,def printDescription(): Unit 则是实例方法。Scala 默认访问级别是公开的,因此原例特别用注释标出了 // 'public' access by default。

辅助构造器怎么写,限制是什么

除了主构造器,Scala 还支持通过 this 定义辅助构造器。它适合在不同入参形式下创建同一个类实例:

// the primary constructor
class Person(var name: String, var age: Int) {

  // one-arg auxiliary constructor
  def this(age: Int) = {
    this("default name", age)
  }

  // one-arg auxiliary constructor
  def this(name: String) = {
    this(name, 0)
  }

  // zero-arg auxiliary constructor
  def this() = {
    this("default name", 0)
  }

  override def toString = s"The person called $name is $age years old"

}

写辅助构造器时,需要注意两条规则:

  • 每个辅助构造器都必须有不同的方法签名,最常见的区别就是参数列表不同。
  • 每个辅助构造器都必须调用一个已经定义好的构造器。

第二条尤其重要。也就是说,辅助构造器不能随意绕开已有初始化流程,它必须通过 this(...) 把调用链收束到某个已定义构造器上,最终回到主构造器。

默认参数很多时候比多个构造器更实用

虽然辅助构造器能解决不同初始化方式的问题,但在 Scala 里,很多时候更常用的是“给构造器参数设置默认值”。

class Socket(var timeout: Int = 2000, var linger: Int = 3000) {
  override def toString = s"timeout: $timeout, linger: $linger"
}

这样定义后,可以用多种方式创建对象:

new Socket()
new Socket(1000)
new Socket(4000, 6000)

这种写法的价值很明显:

  • 可以提前给参数提供首选默认值。
  • 调用方可以只覆盖自己关心的那部分配置。

因此,原文也特别提醒:辅助构造器是有用的特性,但因为 Scala 支持默认参数,很多场景下并不需要专门写多个构造器。

命名参数让调用点更容易读懂

当参数较多,或者多个参数类型相近时,命名参数能明显提升可读性:

val socket = new Socket(linger=3000, timeout=2000)

val socket = new Socket(timeout=2000)
val socket = new Socket(linger=3000)

相比之下,下面这种位置参数调用虽然更短,但读代码的人需要自己记住参数顺序:

val socket = new Socket(2000, 3000)

如果参数名本身已经能表达业务含义,那么命名参数在维护阶段通常会更占优势。

Scala 类中的方法怎么定义

Scala 中的方法定义在类里,基础写法如下:

def double(a: Int) = a * a

// Explicitly declare the return value
def double(a: Int): Int = a * a

这里展示的是同一个方法的两种写法:一种省略返回值类型,一种显式声明返回值类型。Scala 可以根据方法体做类型推断,但是否省略,要看代码复杂度和可读性。

一个方法通常包含这些部分:

  • def:声明方法的关键字
  • double:方法名,最好直接体现业务语义
  • a: Int:参数名和参数类型
  • : Int:显式声明的返回值类型
  • a * a:方法体

如果方法体只有一行,可以直接写在 = 后面;如果方法体有多行,就使用花括号:

def addThenDouble(a: Int, b: Int): Int = {
  val sum = a + b
  val doubled = sum * 2
  doubled
}

实际开发里,一个简单判断原则是:

  • 方法体很短、返回类型一眼可见时,可以依赖类型推断。
  • 方法逻辑较复杂,或者返回值是自定义类型时,显式写出返回类型更利于阅读和维护。

怎么理解这组 Scala 类语法

把整篇内容串起来看,Scala 类的核心优势在于“用更少的语法表达完整的对象结构”。主构造器参数可以直接成为字段,类体里既能放字段和方法,也能放初始化时立即执行的语句;在对象创建方式上,默认参数和命名参数通常比堆叠多个辅助构造器更简洁。

如果你正在从 Java 过渡到 Scala,最值得先建立的习惯有两个:一是先想清楚字段到底该用 var 还是 val,二是优先考虑默认参数和命名参数,减少不必要的构造器重载。这样更容易写出既符合 Scala 风格、又便于维护的类定义。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表