提问人:Andrew Kolubov 提问时间:11/9/2023 最后编辑:David SorokoAndrew Kolubov 更新时间:11/10/2023 访问量:77
没有默认构造函数的数据类
Data class without default constructor
问:
我的数据类中有这些字段:
val year: Int,
val month: Int? = null,
val day: Int? = null,
我如何通过构造函数确保If in not-null也不是null?
理想情况下,我只想有 3 个构造函数,而根本没有默认的可为 null 字段。day
month
(year: Int) (year: Int, month: Int) and (year: Int, month: Int, day: Int)
答:
1赞
David Soroko
11/9/2023
#1
您可以使用某种工厂来强制执行类型和其他验证(负值等)。一种方法是使用伴随对象。
正如@gidds所评论的,虽然构造函数可以是私有的,但仍然可以使用 copy() 创建“无效”实例
data class MyDate
private constructor(val year: Int, val month: Int? = null, val day: Int? = null) {
companion object {
fun from(year: Int) = MyDate(year, null, null)
fun from(year: Int, month: Int) = MyDate(year, month, null)
fun from(year: Int, month: Int, day: Int) = MyDate(year, month, day)
}
}
fun main() {
MyDate.from(2023)
.also { println(it) } // MyDate(year=2023, month=null, day=null)
MyDate.from(2023, 11)
.also { println(it) } // MyDate(year=2023, month=11, day=null)
MyDate.from(2023, 11, 9)
.also { println(it) } // MyDate(year=2023, month=11, day=9)
// Invalid instances can still be created using copy()
MyDate.from(2023, 11, 9).copy(month = null)
.also { println(it) } // MyDate(year=2023, month=null, day=9)
}
有一些选项可以开箱即用地创建各种日期对象,我认为它们不适合您的想法。
评论
0赞
David Soroko
11/10/2023
好点子,修改了答案。
4赞
Klitos Kyriacou
11/10/2023
#2
您可以使用私有构造函数创建一个普通类,并添加重载构造函数:
class X private constructor(val year: Int, val month: Int?, val day: Int?) {
constructor(year: Int) : this(year, null, null)
constructor(year: Int, month: Int) : this(year, month, null)
constructor(year: Int, month: Int, day: Int) : this(year, month, day as Int?)
// Optionally create equals, hashCode, toString and componentN methods...
}
如果您需要任何自动生成的额外方法,只需手动添加它们,或者让 IDE 为您生成它们。data class
理由
我们为什么要创建一个普通的班级?好吧,您可以使用私有构造函数创建一个数据类,但这不会阻止类的用户调用该函数并向其传递无效参数,例如具有 null 月份的一天。copy
与 和 不同,您不能以任何方式覆盖该函数或禁用该函数。如果你将你的类声明为数据类,你会得到一个你无法摆脱的不需要的函数。使用普通类,您可以在函数中定义自己的逻辑,尽管您可能根本不想定义它,因为它有问题。toString
hashCode
equals
copy
copy
copy
1赞
k314159
11/10/2023
#3
您可以创建一个在用法上类似于数据类的接口:
sealed interface FlexibleDate {
val year: Int
val month: Int?
val day: Int?
operator fun component1() = year
operator fun component2() = month
operator fun component3() = day
companion object {
operator fun invoke(year: Int): FlexibleDate = Impl(year, null, null)
operator fun invoke(year: Int, month: Int): FlexibleDate = Impl(year, month, null)
operator fun invoke(year: Int, month: Int, day: Int): FlexibleDate = Impl(year, month, day)
}
private data class Impl(override val year: Int, override val month: Int?, override val day: Int?) : FlexibleDate
}
私有实现类提供正确生成的 实现,并且所有数据类都具有。Impl
toString
equals
hashCode
该接口提供了一些函数,以便您可以创建实例,就好像它具有构造函数一样。超载不会接受没有一个月的一天。invoke
invoke
评论
0赞
David Soroko
11/10/2023
不错,为什么?sealed
1赞
k314159
11/10/2023
@DavidSoroko 它不必被密封,但我想让它尽可能接近数据类。无法扩展数据类。同样,密封接口也无法实现(也就是说,一旦你把你的库放在它自己的模块中)。
评论