Kotlin空安全容器”是一种可变属性,该属性可能已在此时更改

时间:2018-10-27 03:00:11

标签: kotlin

class ExchangeRatesServiceImpl : ExchangeRatesService {

    private var container: ExchangeRatesContainer? = null

    /**
     * {@inheritDoc}
     */
    override val currentRates: Map<Currency, BigDecimal>
        get() {
            if (container == null || container.date != LocalDate.now()) {
                container = client.getRates(Currency.getBase())
                        log.info("exchange rates has been updated: {}", container)
            }
            return ImmutableMap.of<Currency, BigDecimal>(
                    Currency.EUR, container.rates[Currency.EUR.name],
                    Currency.RUB, container.rates[Currency.RUB.name],
                    Currency.USD, BigDecimal.ONE
            )
        }
}

如果(容器==空|| container.date!= LocalDate.now()) 错误【因为“容器”是可变属性,这一次可能已经更改了

我如何编译它?

2 个答案:

答案 0 :(得分:2)

container在函数范围之外声明,因此理论上,在检查null并使用它之间,另一个线程可以修改它。为防止这种情况,您应该创建一个初始化为container的局部变量,并像这样使用它:

class ExchangeRatesServiceImpl : ExchangeRatesService {

private var container: ExchangeRatesContainer? = null

/**
 * {@inheritDoc}
 */
override val currentRates: Map<Currency, BigDecimal>
    get() {
        val tempContainer = container
        if (tempContainer == null || tempContainer.date != LocalDate.now()) {
            tempContainer = client.getRates(Currency.getBase())
                    log.info("exchange rates has been updated: {}", tempContainer)
        }
        return ImmutableMap.of<Currency, BigDecimal>(
                Currency.EUR, container.rates[Currency.EUR.name],
                Currency.RUB, container.rates[Currency.RUB.name],
                Currency.USD, BigDecimal.ONE
        )
    }
}

答案 1 :(得分:1)

正如apemanzilla已经提到的,这是一个线程问题。即使您只有一个线程,Kotlin也会阻止您这样做,因为有可能另一个线程尝试访问它。这基本上是一场比赛;即使您检查它是否不为null,理论上也有可能在一毫秒后的下一次调用时为null。

但是,对此有多种解决方案。我确实想指出apemanzilla代码的一个缺陷:它没有设置外部容器。这意味着tempContainer == null始终为真。像这样使用temp变量的主要问题是,它总是最终需要两次调用。另外,临时变量必须是变量。否则无法设置。它只会说“无法重新分配val”。

现在,对于实际解决方案:

第一个(我不建议这样做)使用null断言。 ,如果可以保证它没有被其他线程修改,则这样做,否则将导致程序崩溃:

return ImmutableMap.of<Currency, BigDecimal>(
        Currency.EUR, container!!.rates[Currency.EUR.name],
        Currency.RUB, container!!.rates[Currency.RUB.name],
        Currency.USD, BigDecimal.ONE
)

您还必须在此处执行相同操作:

if (container == null || container!!.date != LocalDate.now()) { ... }

尽管使用null安全实际上更好。将null与非null比较可以。就像if(null != "some string")(这始终是对的,但您明白了):

if (container == null || container?.date != LocalDate.now) { ... }

如果您愿意,也可以将null-safe与elvis运算符配合使用:

return ImmutableMap.of<Currency, BigDecimal>(
        Currency.EUR, container?.rates[Currency.EUR.name] ?: TODO(),
        Currency.RUB, container?.rates[Currency.RUB.name] ?: TODO(),
        Currency.USD, BigDecimal.ONE
)

或者,您可以使用let:

container?.let { /* it -> is explicitly declared here */
    return ImmutableMap.of<Currency, BigDecimal>(
        Currency.EUR, it.rates[Currency.EUR.name], // You might still need the elvis operator on these if it.rates[something] can return null
        Currency.RUB, it.rates[Currency.RUB.name],
        Currency.USD, BigDecimal.ONE
    )
}

尽管这会使处理if语句稍微困难一些。

但是 ,这要求您添加第二个退货。如果container为null,它将不会执行该代码,这意味着您需要一个最终的return语句。您可以尝试递归地重新调用代码,直到成功,返回默认值,引发异常,返回null(无论您喜欢什么)。

最后,如apemanzilla所提到的,是临时变量。

这将创建一个局部变量,该变量在本地是不可变的,这意味着您不会遇到可为空的问题。

val currentRates: Map<Currency, BigDecimal>
    get() {
        var localContainer = container // This needs to be a var; you assign it, then re-assign it. You can't do that with a `val`
        if (localContainer == null || localContainer.date != LocalDate.now()) {

            localContainer = client.getRates(Currency.getBase())
            log.info("exchange rates has been updated: {}", tempContainer)
            container = localContainer // This is also necessary to prevent it from always updating. 
        }

        return ImmutableMap.of<Currency, BigDecimal>(
                Currency.EUR, container.rates[Currency.EUR.name],
                Currency.RUB, container.rates[Currency.RUB.name],
                Currency.USD, BigDecimal.ONE
        )
    }

尽管,老实说,我不理解您为什么选择container.date != LocalDate.now()。如果时间不匹配,则总是如此。因此,如果我正确理解了您的代码,则可以将其缩短为:

val currentRates: Map<Currency, BigDecimal>
    get() {
        val localContainer = client.getRates(Currency.getBase())
        // container = localContainer // If you use the container somewhere else. 
        log.info("exchange rates has been updated: {}", tempContainer)

        return ImmutableMap.of<Currency, BigDecimal>(
                Currency.EUR, container.rates[Currency.EUR.name],
                Currency.RUB, container.rates[Currency.RUB.name],
                Currency.USD, BigDecimal.ONE
        )
    }