1. 概述
Kotlin 的 数据类(data class) 是一种非常适合用于封装数据的类,它默认实现了 equals()
、toString()
、copy()
等常用方法。
在本教程中,我们将探讨如何使用空构造函数(empty constructor)来实例化一个 Kotlin 的数据类,并介绍几种实现方式。
2. 问题背景
我们知道,Kotlin 的数据类必须满足一个条件:主构造函数至少要有一个参数,这意味着它没有类似 Java 的“默认构造函数”。
在实际开发中,有时我们可能希望像 Java 一样通过无参构造函数创建一个数据类的实例。比如在某些框架中,例如 Spring 或 JPA,经常需要通过无参构造函数反射创建对象。但 Kotlin 数据类的这个限制使得直接使用空构造函数变得不可行。
为了解决这个问题,我们可以尝试以下几种方式:
- 添加一个空的次构造函数
- 为主构造函数参数设置默认值
- 使用 Kotlin 的 no-arg 编译器插件
3. 添加一个空的次构造函数
Kotlin 的数据类要求主构造函数必须至少有一个参数。例如:
data class ArticleWithoutDefault(
var title: String,
var author: String,
var abstract: String,
var words: Long
)
如果我们想使用空构造函数创建实例,可以添加一个次构造函数并调用主构造函数,传入默认值:
data class ArticleWithoutDefault(
var title: String,
var author: String,
var abstract: String,
var words: Long
) {
constructor() : this("dummy title", "dummy author", "dummy abstract", 0)
}
这样我们就可以通过无参构造函数创建对象:
val myInstance = ArticleWithoutDefault()
assertThat(myInstance).isInstanceOf(ArticleWithoutDefault::class.java)
.extracting("title", "author", "abstract", "words")
.containsExactly("dummy title", "dummy author", "dummy abstract", 0L)
✅ 优点:简单直观
❌ 缺点:需要手动维护默认值
4. 为主构造函数参数设置默认值
另一种更灵活的方式是给主构造函数的参数设置默认值:
data class ArticleWithDefault(
var title: String = "default title",
var author: String = "default author",
var abstract: String = "",
var words: Long = 0L
)
这样我们可以直接使用无参构造函数创建实例:
val myInstance = ArticleWithDefault()
assertThat(myInstance).isInstanceOf(ArticleWithDefault::class.java)
.extracting("title", "author", "abstract", "words")
.containsExactly("default title", "default author", "", 0L)
我们也可以只传部分参数,其余使用默认值:
val myArticle = ArticleWithDefault(title="A Great Article", words=42L)
assertThat(myArticle).isInstanceOf(ArticleWithDefault::class.java)
.extracting("title", "author", "abstract", "words")
.containsExactly("A Great Article", "default author", "", 42L)
✅ 优点:灵活、符合 Kotlin 语法风格
❌ 缺点:不能完全避免构造参数的传递(只是简化)
5. 使用 no-arg 编译器插件
Kotlin 提供了一个名为 no-arg 的编译器插件,可以在编译时为特定类生成一个空构造函数。这在某些框架中非常有用,比如 JPA、Spring 等依赖无参构造函数的场景。
5.1 配置 no-arg 插件
以 Maven 为例,配置如下插件:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
<compilerPlugins>
<plugin>no-arg</plugin>
</compilerPlugins>
<pluginOptions>
<option>no-arg:annotation=com.baeldung.kotlin.emptyConstructorOfDataCls.NoArg</option>
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
5.2 创建 @NoArg 注解并应用于数据类
创建一个注解类:
package com.baeldung.kotlin.emptyConstructorOfDataCls
annotation class NoArg
然后将其应用于一个数据类:
@NoArg
data class Person(
var firstName: String = "a nice name",
var midName: String?,
var lastName: String,
var age: Int
)
此时,编译器会为该类生成一个空构造函数。
⚠️ 注意:这个生成的构造函数是合成的(synthetic),不能直接从 Kotlin 或 Java 代码中调用。
5.3 尝试直接调用空构造函数(失败)
val person = Person() // ❌ 编译错误
错误信息如下:
No value passed for parameter 'midName'
No value passed for parameter 'lastName'
No value passed for parameter 'age'
说明这个构造函数并不是我们通常理解的构造函数。
5.4 使用 Kotlin 反射调用(也失败)
val person = Person::class.createInstance() // ❌ 运行时报错
报错信息:
Class should have a single no-arg constructor
因为 Kotlin 反射不识别合成构造函数。
5.5 使用 Java 反射调用(成功)
val myInstance = Person::class.java.getConstructor().newInstance()
assertThat(myInstance).isInstanceOf(Person::class.java)
.extracting("firstName", "midName", "lastName", "age")
.containsExactly(null, null, null, 0)
✅ 成功创建实例,但注意:
- 非空属性如
firstName
和lastName
可能为null
- 默认值(如
firstName
的"a nice name"
)不会生效
⚠️ 这是因为这个空构造函数绕过了 Kotlin 的构造逻辑,只适用于框架反射创建对象。
6. 总结
我们介绍了三种方式来实现 Kotlin 数据类的无参构造函数:
方法 | 适用场景 | 是否推荐 |
---|---|---|
添加次构造函数 | 需要默认值初始化 | ✅ 推荐 |
设置主构造函数默认值 | 构造灵活、参数可选 | ✅ 强烈推荐 |
使用 no-arg 插件 | 框架反射创建对象 | ⚠️ 仅在必要时使用 |
⚠️ no-arg 插件生成的构造函数只能通过 Java 反射调用,且不保证 Kotlin 类的构造逻辑(如默认值、非空检查)。
🔗 完整源码可在 GitHub 上查看。