为什么java.time.YearMonth做了最后一堂课



我计划写一个extends java.time.YearMonth的类,目的是用一种方法扩展YearMonth,使我能够流式传输该YearMonth:的LocalDate

public class ExtendedYearMonth extends YearMonth {
public Stream<LocalDate> days() {
LocalDate firstOfMonth = this.atDay(1);
LocalDate lastOfMonth = firstOfMonth.with(TemporalAdjusters.lastDayOfMonth());
return firstOfMonth.datesUntil(lastOfMonth);
}
}

嗯,当我发现YearMonthfinal class时,这个计划立即失败了⇒final classes不可扩展

我当然可以写这样的课——

public class ExtendedYearMonth {
private YearMonth yearMonth;
// parameterized constructor, getters and setters omitted for brevity
public Stream<LocalDate> days() {
LocalDate firstOfMonth = this.yearMonth.atDay(1);
LocalDate lastOfMonth = firstOfMonth.with(TemporalAdjusters.lastDayOfMonth());
return firstOfMonth.datesUntil(lastOfMonth);
}
}

但这不是我想要的,因为它要求我将YearMonthExtendedYearMonth实例化为stream(和filter,主要目的),即特定年份中特定月份的LocalDates。

YearMonth的JavaDocs只是声明它是final,而不是为什么final:

public final class YearMonth

YearMonth是一个不可变的日期-时间对象,表示年和月的组合
。。。

为什么YearMonth变成final

或者更准确地说:final class YearMonthclass YearMonth有什么好处
我无法想象有什么原因。。。

我知道,要回答这个问题,就需要深入了解可能在互联网上公开的设计决策,但不幸的是,我没有这种洞察力,到目前为止我还没有找到来源。

在Kotlin中,这并不重要,因为可以编写一个扩展函数,而不必继承class。这是Kotlin的一个很好的特性,但Java(目前)没有这个特性,我拒绝为此编写包装器类。

我也可以问,为什么YearMonth中没有这样的方法,或者当LocalDate在Java 9中获得datesUntil时没有添加这样的方法。但这将是一篇文章中的第二个问题,人们通常对此表示不满(并投了反对票或接近票),所以我可能会在稍后的另一篇帖子中问这个问题。

我目前的解决方案是public static Stream<LocalDate> daysOf(YearMonth yearMonth),它完成了上面代码的功能,但我必须将一个实例传递给static方法,而不是直接使用它的方法。这符合我的要求,但我仍然认为这不是一个近乎完美的解决方案。

YearMonth的文档确实这样说,但间接地:

这是一个基于值的类;在YearMonth的实例上使用对身份敏感的操作(包括引用相等(==)、身份哈希码或同步)可能会产生不可预测的结果,应该避免。

而基于价值的表示:

基于值的类

一些类,如java.util.Optionaljava.time.LocalDateTime,是基于值的。基于值的类的实例:

  • 是最终的和不可变的(尽管可能包含对可变对象的引用)
  • 具有equalshashCodetoString的实现,这些实现仅根据实例的状态而不是根据实例的标识或任何其他对象或变量的状态来计算
  • 不使用对身份敏感的操作,如实例之间的引用相等(==)、实例的身份哈希代码或对实例的内部锁进行同步
  • 仅基于equals()而不是基于参考等式(==)被认为是相等的
  • 没有可访问的构造函数,而是通过工厂方法进行实例化,这些方法不提交返回实例的标识
  • 当相等时,是可自由替换的,这意味着在任何计算或方法调用中,根据equals()交换相等的任何两个实例xy都不会产生明显的行为变化

如果程序试图区分对基于值的类的相等值的两个引用,无论是直接通过引用相等还是间接通过调用同步、身份哈希、序列化或任何其他身份敏感机制,都可能产生不可预测的结果。在基于值的类的实例上使用这种对身份敏感的操作可能会产生不可预测的影响,应该避免。

这里没有明确说明,但子类化会与这些点相矛盾,因为这会导致实例可能代表相同的值(根据基类的状态),但当它们不具有相同的类型时,不能自由替换。此外,即使类不是final,只提供返回未指定标识实例的工厂方法的概念也不允许子类,因为子类需要一个可访问的构造函数。

您可能会将基于值的类视为基元值的等价物;不能对int进行子类化,所以不能对YearMonth进行子类,因为它只表示一个特定的值(只是强类型的),并且表示相同值的所有YearMonth实例都应该是相同的,无论是由不同的对象实例还是单个实例表示。这为Java中的真实值类型的未来开辟了道路。

因为类的实例保证是不可变的。

如果允许使用子类,就无法做出这种保证。

来自年度月级文档:

* {@code YearMonth} is an immutable date-time object that represents the combination
* of a year and month. 

最新更新