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)

✅ 成功创建实例,但注意:

  • 非空属性如 firstNamelastName 可能为 null
  • 默认值(如 firstName"a nice name")不会生效

⚠️ 这是因为这个空构造函数绕过了 Kotlin 的构造逻辑,只适用于框架反射创建对象。


6. 总结

我们介绍了三种方式来实现 Kotlin 数据类的无参构造函数:

方法 适用场景 是否推荐
添加次构造函数 需要默认值初始化 ✅ 推荐
设置主构造函数默认值 构造灵活、参数可选 ✅ 强烈推荐
使用 no-arg 插件 框架反射创建对象 ⚠️ 仅在必要时使用

⚠️ no-arg 插件生成的构造函数只能通过 Java 反射调用,且不保证 Kotlin 类的构造逻辑(如默认值、非空检查)。


🔗 完整源码可在 GitHub 上查看。


原始标题:Instantiate a Kotlin Data Class Using an Empty Constructor