在 Kotlin 中处理字段的正确方法是什么?

What is the correct way to deal with fields in Kotlin?

我应该如何正确处理从 Java class 字段到 Kotlin 的迁移?

阅读 Kotlin 的文档后发现,他们的 classes 不能在其中定义字段。作为我的反叛者,实际上试图将我现有的 Java 代码转换为它的 Kotlin 对应代码(使用 Android Studio 的 Java 到 Kotlin 转换器功能)也将 "fields" 标记为.

这是我的 Java class:

  public final class PaperDay implements Day {
    private Date date;
    private Weather weather;

    PaperDay() {
      // Obligatory empty ctor for Paper.
    }

    PaperDay(Date date) {
      this.date = truncateTimeFromDate(date);
      this.weather = Weather.SUNNY; // Default to SUNNY, 'cos sunny is good!
    }

    PaperDay(Date date, Weather weather) {
      this.date = truncateTimeFromDate(date);
      this.weather = weather;
    }

    private Date truncateTimeFromDate(Date date) {
      Calendar calendar = Calendar.getInstance();

      calendar.setTime(date);

      calendar.set(Calendar.HOUR_OF_DAY, 0);
      calendar.set(Calendar.MINUTE, 0);
      ...

..这就是转换成 .kt 的样子:

我怎样才能用 Kotlin 的方式做到这一点?

我假设你想要这样的东西:

class PaperDay (private var date: Date, 
                private var weather: Weather = Weather.SUNNY) : Day {
    init {
        this.date = truncate(date)
    }

    private fun truncateTimeFromDate(date1: Date): Date {
        return date1
    }
}

您应该尽可能使用可选参数 (var weather: Weather = Weather.SUNNY),因为它会大大提高可读性。

为了避免(有点难看)init 块,您还可以仅将两个属性之一作为构造函数参数传递(感谢 Jayson Minard),并在其中计算另一个 属性使用 "normal" 参数声明:

class PaperDay (dateWithTime: Date, 
                private val weather: Weather = Weather.SUNNY): Day {

    private val date: Date = truncateTimeFromDate(dateWithTime)

    ...
}

请注意,这也使用 val 而不是 var,因为不变性通常是更好的选择。

如果你也需要有空的构造函数,你可以添加这个(虽然我不认为这是一个好的做法并且它是 "bad" 的一个指标 设计:

 constructor() : this(Date())

或者,在构造函数中为 所有 参数添加默认值也会隐式创建一个空构造函数。

如果您真的想要空性,请使用 ? 定义您的属性。例如:

class PaperDay (private var date: Date?, private var weather: Weather? = Weather.SUNNY)

然后您还可以将另一个构造函数更改为 constructor() : this(null)


此外,恕我直言,将 truncate... 方法添加为日期 class 的 extension function 而不是将其放在此 class 中也是一个好主意:

fun Date.truncateTimeFromDate() {
  Calendar calendar = Calendar.getInstance()

  calendar.setTime(this)

  calendar.set(Calendar.HOUR_OF_DAY, 0)
  calendar.set(Calendar.MINUTE, 0)
  ...
}

类似于@Lovis 但避免使用 var 应该是 "last resort":

class PaperDay (dateWithTime: Date = Date(), private val weather: Weather = Weather.SUNNY): Day {
    private val date: Date = truncateTimeFromDate(dateWithTime)

    private fun truncateTimeFromDate(dateWithTime : Date) : Date {
        ...
    }
}

在此版本中,构造函数有一个 参数 不是 class 属性,另一个是 属性。稍后在 class 正文中,声明 属性 并将其设置为截断日期。

如果每个构造函数参数都有一个默认值,Kotlin 将自动创建一个使用这些默认值的默认构造函数。无需单独添加。

如果您的框架必须使用默认构造函数实例化,然后在事后设置值,您将需要执行类似以下操作并返回使用 var:

class PaperDay (dateWithTime: Date = Date(), private var weather: Weather = Weather.SUNNY): Day {
    private var date: Date = truncateTimeFromDate(dateWithTime)
       set(value) { field = truncateTimeFromDate(value) }

    private fun truncateTimeFromDate(dateWithTime : Date) : Date {
        return dateWithTime
    }
}

现在我们通过自定义 setter.

确保我们永远不会将无效日期存储在 属性 中

如果您希望 Java 提供的构造函数的所有变体具有互操作性,您可以在构造函数上使用 @JvmOverloads annotation 来生成使用默认参数值的其他排列。

正如@Lovis 指出的那样,将 truncateTimeFromDate 移动到 Date class 上的扩展函数。它可以在文件、模块、class 的本地,在所有这些情况下,它的读起来会更好。