1. apply方法的基本使用
Scala 中的 apply 方法是特殊方法:
- 可直接通过「对象名 (参数)」或「类名 (参数)」调用(无需显式写
.apply); - 常用于对象创建、参数转换、简化调用;
- 区别于 JavaScript:Scala 的
apply不直接改变this指向(Scala 是静态类型语言,this由上下文确定),核心作用是「简化实例化 / 方法调用」。
核心语法
- 伴生对象的 apply:用于创建类实例(替代
new关键字); - 类 / 特质的 apply:用于实例的函数式调用(类似方法重载)。
基本使用案例
scala
object ApplyBasicDemo extends App {
// 1. 伴生对象的 apply:简化类实例化(无需 new)
class Person(val name: String, val age: Int) {
// 类的 apply:实例可直接像函数一样调用
def apply(hobby: String): String =
s"我是 $name,年龄 $age,爱好 $hobby"
}
// 伴生对象(Scala 中与类同名的对象)
object Person {
// apply 方法:接收参数,返回类实例
def apply(name: String, age: Int): Person = new Person(name, age)
}
// 直接通过 类名(参数) 调用 apply 创建实例(无需 new)
val zhangsan = Person("张三", 20)
println(zhangsan.name) // 输出:张三
// 实例直接调用 apply(简化为 实例(参数))
val intro = zhangsan("编程")
println(intro) // 输出:我是 张三,年龄 20,爱好 编程
// 2. 工具类的 apply:简化参数处理
object MathUtils {
// apply 实现求和(接收可变参数,模拟 JavaScript 数组传参)
def apply(numbers: Int*): Int = numbers.sum
}
// 调用 apply 求和(直接传多个参数,或数组转可变参数)
val sum1 = MathUtils(1, 2, 3)
val sum2 = MathUtils.apply(Array(4,5,6): _*) // 数组转可变参数
println(sum1) // 输出:6
println(sum2) // 输出:15
// 3. 集合中的 apply:Scala 内置集合大量使用 apply 创建实例
val list = List(1,2,3) // 等价于 List.apply(1,2,3)
val map = Map("name" -> "李四", "age" -> 25) // 等价于 Map.apply(...)
println(list(0)) // 输出:1(调用 List 的 apply 按索引取值)
println(map("name")) // 输出:李四(调用 Map 的 apply 按 key 取值)
}
2. apply实现单例模式
Scala 单例模式的核心是「伴生对象 + apply 方法」:
- 伴生对象是天然单例(Scala 保证全局唯一);
- 通过伴生对象的
apply方法控制类实例的创建,确保仅生成一个实例。
实现思路
- 私有化类的构造器(
private修饰),禁止外部直接new; - 伴生对象中维护唯一实例(闭包缓存);
- 伴生对象的
apply方法:判断实例是否存在,不存在则创建,存在则直接返回。
代码实现
scala
object SingletonWithApply extends App {
// 目标类:私有化构造器(private 修饰)
class User private(val name: String, val age: Int) {
override def toString: String = s"User($name, $age)"
}
// 伴生对象:实现单例逻辑
object User {
// 闭包缓存唯一实例(Option 类型避免空指针)
private var instance: Option[User] = None
// apply 方法:控制实例创建
def apply(name: String, age: Int): User = {
instance match {
case Some(user) => user // 已存在,返回缓存实例
case None =>
val newUser = new User(name, age) // 首次创建
instance = Some(newUser)
newUser
}
}
// 可选:提供获取实例的方法(非必须,apply 已简化调用)
def getInstance: Option[User] = instance
}
// 测试:多次调用 apply,返回同一个实例
val user1 = User("张三", 20)
val user2 = User("李四", 25) // 后续参数无效,返回缓存的 user1
println(user1 == user2) // 输出:true(同一实例)
println(user1) // 输出:User(张三, 20)
println(user2) // 输出:User(张三, 20)
// 验证实例唯一性
println(User.getInstance) // 输出:Some(User(张三, 20))
}
关键说明
- 类构造器
private:确保外部无法通过new User(...)创建实例,只能通过伴生对象的apply; Option[User]:Scala 中推荐用Option处理空值,避免null引发的空指针异常;- 伴生对象的
apply是「工厂方法」,统一实例创建入口,控制单例逻辑。
3. 案例-日志类的基本实现
需求:实现单例日志类,支持日志级别(INFO/WARN/ERROR)、格式化输出、控制台打印,后续可对接 Writer 类写入文件。
实现思路
- 单例模式:通过伴生对象
apply控制唯一实例; - 日志级别:定义枚举类型(
LogLevel),规范日志级别; - 日志格式化:统一格式
[时间] [级别] 内容; - 核心方法:
info/warn/error对应不同级别日志。
完整代码
scala
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
// 日志级别枚举(Scala 3 枚举,Scala 2 可用 sealed trait + case object)
enum LogLevel:
case INFO, WARN, ERROR
object LoggerDemo extends App {
// 日志类:私有化构造器,单例模式
class Logger private {
// 时间格式化器(线程安全,复用)
private val dateFormatter: DateTimeFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
// 私有方法:格式化日志内容
private def formatLog(level: LogLevel, message: String): String = {
val time = LocalDateTime.now().format(dateFormatter)
s"[$time] [${level.toString}] $message"
}
// 公共方法:不同级别日志
def info(message: String): Unit = {
val log = formatLog(LogLevel.INFO, message)
println(log) // 控制台打印(后续可对接文件写入)
}
def warn(message: String): Unit = {
val log = formatLog(LogLevel.WARN, message)
println(log)
}
def error(message: String): Unit = {
val log = formatLog(LogLevel.ERROR, message)
println(log)
}
}
// 伴生对象:单例工厂(apply 方法)
object Logger {
private var instance: Option[Logger] = None
def apply(): Logger = instance match {
case Some(log) => log
case None =>
val newLog = new Logger()
instance = Some(newLog)
newLog
}
}
// 测试:获取单例日志实例并使用
val logger = Logger()
logger.info("应用启动成功")
logger.warn("内存使用率过高(80%)")
logger.error("数据库连接失败:Cannot connect to MySQL")
// 验证单例
val logger2 = Logger()
println(s"是否单例:${logger == logger2}") // 输出:true
}
4. wirter类来实现文件写入功能
基于 Scala 标准库 java.io 和 scala.io,实现通用文件写入类,支持:
- 同步写入、追加写入;
- 自动创建目录;
- 字符编码配置;
- 兼容日志类调用。
设计思路
- 职责单一:仅处理文件写入逻辑(创建目录、写入内容、异常处理);
- 健壮性:自动创建文件所在目录,捕获 IO 异常;
- 易用性:提供简洁 API(
write/append),支持字符串内容; - 可复用:独立类,可对接日志类或单独使用。
完整代码
scala
import java.io.{BufferedWriter, File, FileWriter}
import java.nio.charset.StandardCharsets
import scala.util.control.NonFatal
object WriterDemo extends App {
/**
* 通用文件写入类
* @param filePath 文件路径(绝对路径或相对路径)
* @param encoding 字符编码(默认 UTF-8)
*/
class Writer(
private val filePath: String,
private val encoding: String = StandardCharsets.UTF_8.name()
) {
// 初始化:自动创建目录
private val file = new File(filePath)
createParentDirs()
/**
* 私有方法:创建文件所在的父目录(递归创建)
*/
private def createParentDirs(): Unit = {
val parentDir = file.getParentFile
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs() // 递归创建多级目录
println(s"[Writer] 目录已创建:${parentDir.getAbsolutePath}")
}
}
/**
* 同步写入文件(覆盖模式:清空原有内容)
* @param content 要写入的内容
* @return 写入成功返回 true,失败返回 false
*/
def write(content: String): Boolean = {
writeToFile(content, append = false)
}
/**
* 同步追加写入文件(保留原有内容,新增内容追加到末尾)
* @param content 要追加的内容
* @return 写入成功返回 true,失败返回 false
*/
def append(content: String): Boolean = {
writeToFile(content, append = true)
}
/**
* 私有核心方法:执行文件写入
* @param content 内容
* @param append 是否追加(true=追加,false=覆盖)
*/
private def writeToFile(content: String, append: Boolean): Boolean = {
var writer: BufferedWriter = null
try {
// 创建 FileWriter(append 参数控制写入模式)
writer = new BufferedWriter(
new FileWriter(file, encoding, append)
)
writer.write(content)
writer.flush() // 强制刷新缓冲区
println(s"[Writer] 写入成功:${file.getAbsolutePath}")
true
} catch {
case NonFatal(e) => // 捕获非致命异常(避免程序崩溃)
println(s"[Writer] 写入失败:${e.getMessage}")
false
} finally {
if (writer != null) writer.close() // 关闭流,释放资源
}
}
/**
* 获取文件绝对路径
*/
def getAbsolutePath: String = file.getAbsolutePath
}
// 伴生对象:简化 Writer 实例创建(apply 方法)
object Writer {
def apply(filePath: String, encoding: String = StandardCharsets.UTF_8.name()): Writer =
new Writer(filePath, encoding)
}
// ------------------------------
// 测试 Writer 类
// ------------------------------
// 1. 创建 Writer 实例(目标文件:logs/app.log)
val writer = Writer("logs/app.log")
// 2. 测试追加写入(日志格式内容)
val infoLog = "[2025-11-10 16:00:00] [INFO] 应用启动成功\n"
val errorLog = "[2025-11-10 16:00:01] [ERROR] 数据库连接失败\n"
writer.append(infoLog)
writer.append(errorLog)
// 3. 测试覆盖写入(替换原有内容)
val newContent = "[2025-11-10 16:01:00] [INFO] 覆盖写入测试\n"
writer.write(newContent)
// 4. 查看文件路径
println(s"文件路径:${writer.getAbsolutePath}")
}
关键特性
- 自动创建目录:若文件所在目录不存在(如
logs/app.log中的logs目录),会自动递归创建; - 异常安全:使用
try-catch-finally捕获 IO 异常,确保流关闭,避免资源泄露; - 编码支持:默认 UTF-8,可自定义编码(如
GBK); - 写入模式:
write(覆盖)和append(追加)分离,适配不同场景。
五、整合:日志类 + Writer 类(实现文件写入日志)
将前面的日志类与 Writer 类整合,让日志不仅打印到控制台,还能写入文件:
scala
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
enum LogLevel:
case INFO, WARN, ERROR
object LoggerWithWriter extends App {
// 1. 复用前面的 Writer 类(省略重复代码,直接使用)
class Writer(/* 同前 */) { /* 同前 */ }
object Writer { /* 同前 */ }
// 2. 日志类:依赖 Writer 实现文件写入
class Logger private(writer: Writer) {
private val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
private def formatLog(level: LogLevel, message: String): String = {
val time = LocalDateTime.now().format(dateFormatter)
s"[$time] [${level.toString}] $message\n" // 换行,便于日志分行
}
// 日志方法:同时打印控制台 + 写入文件
def info(message: String): Unit = {
val log = formatLog(LogLevel.INFO, message)
println(log.trim) // 控制台去换行(避免重复空行)
writer.append(log) // 写入文件(追加模式)
}
def error(message: String): Unit = {
val log = formatLog(LogLevel.ERROR, message)
println(log.trim)
writer.append(log)
}
}
// 伴生对象:单例 + 初始化 Writer
object Logger {
private var instance: Option[Logger] = None
// apply 方法:接收日志文件路径,初始化 Writer 和 Logger
def apply(logFilePath: String = "logs/app.log"): Logger = instance match {
case Some(log) => log
case None =>
val writer = Writer(logFilePath) // 初始化 Writer
val newLog = new Logger(writer)
instance = Some(newLog)
newLog
}
}
// 测试:日志同时打印到控制台和文件
val logger = Logger()
logger.info("应用启动成功(整合 Writer 类)")
logger.error("数据库连接失败(写入文件)")
// 验证文件写入结果:查看 logs/app.log 文件
}
总结
Scala 实现核心要点:
- apply 方法:简化实例创建(伴生对象)和方法调用(类实例),无「改变 this 指向」的概念(Scala 静态类型);
- 单例模式:通过「私有化类构造器 + 伴生对象 apply + 闭包缓存实例」实现,天然适配 Scala 语法;
- 日志类:枚举定义日志级别,格式化日志内容,单例模式确保全局唯一;
- Writer 类:基于 Java IO 封装,自动创建目录,支持覆盖 / 追加写入,异常安全,可独立复用。
所有代码均可直接在 Scala 3 环境中运行(Scala 2 需调整枚举定义为 sealed trait + case object),贴合 Scala 函数式 + 面向对象的混合编程风格。