问题很简单:(使用Kotlin 1.3.71(
我有以下与此类似的数据:
data class Location(val lat: Double, val lng: Double)
我想通过这样的调用实现类型安全:
val loc = location {
lat = 2.0
lng = 2.0
}
为了实现这一点,我建立了:
fun location(builder: LocationBuilder.() -> Unit): Location {
val lb = LocationBuilder().apply(builder)
return Location(lb.lat!!, lb.lng!!)
}
data class LocationBuilder(
var lat: Double? = null,
var lng: Double? = null
)
为了避免使用!!
运算符,我想编写一个契约,帮助编译器推断一个智能广播,说明属性lat
和lng
不为空,但我未能成功做到这一点。
我尝试过一些没有成功的事情,我相信这可能是因为我没有完全理解合同的动态。这些是的风格
fun LocationBuilder.buildSafely(dsl: LocationBuilder.()->Unit): LocationBuilder {
contract {
returnsNonNull() implies (this@buildSafely.lat != null && this@buildSafely.lng != null)
}
apply(dsl)
if(lat == null || lng == null) throw IllegalArgumentException("Invalid args")
return this
}
fun location(builder: LocationBuilder.()->Unit): Location {
val configuredBuilder = LocationBuilder().buildSafely(builder)
return Location(configuredBuilder.lat, configuredBuilder.lng)
/* I would expect a smart cast but I am getting a compile error stating that lat and lng may still be null */
}
所以问题是:
这可以用当前的Kotlin版本完成吗?如果是,如何
这目前是不可能的。约定不能基于约定中类的属性,因此当您在约定中选中latitude
或longitude
时,这是不允许的。
您可以将"{}"替换为"(("并使用命名参数:
val loc = Location(
lat = 2.0
lng = 2.0
)
还要注意,这是类的构造函数,不需要构造函数:(这适用于Kotlin中的所有调用。
请参阅https://kotlinlang.org/docs/reference/functions.html#named-自变量
您可以这样做:
import kotlin.properties.Delegates
fun location(builder: LocationBuilder.() -> Unit): Location {
val lb = LocationBuilder().apply(builder)
return Location(lb.lat, lb.lng)
}
class LocationBuilder {
var lat by Delegates.notNull<Double>()
var lng by Delegates.notNull<Double>()
}
data class Location(
val lat: Double,
val lng: Double
)
fun main() {
val l = location {
lat = 2.0
lng = 8.0
}
println(l)
}
但是对于null,您不会有编译时异常。这意味着它不会强迫你同时设置属性(lat和lng(