首页 > 解决方案 > 为什么 java.time.YearMonth 做了最后一堂课?

问题描述

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

public class ExtendedYearMonth extends YearMonth {

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

好吧,当我发现⇒ es is not extensibleYearMonth时,该计划立即失败。final classfinal class

我当然可以写一堂像这样的课

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);
    }
}

但这不是我想要的,因为它需要我实例化 aYearMonth和 anExtendedYearMonthstream(和filter主要目的)LocalDate特定年份中特定月份的 s 。

JavaDocsYearMonth只是声明是,final但不是为什么它是final

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

为什么被YearMonth制作final

final class YearMonth或更准确地说: over a 有什么好处class YearMonth
我想不出任何理由...

我知道,要回答这个问题,需要对可能在 www 某处公开的设计决策有洞察力,但是,不幸的是,我没有这种洞察力,而且到目前为止我还没有找到来源。

在 Kotlin 中,这无关紧要,因为可以编写扩展函数而无需从该函数继承class。这是 Kotlin 的一个很好的特性,但是 Java 没有这个(现在),我拒绝为此编写一个包装类。

我还可以问为什么这样的方法在 Java 9 中不可用YearMonth或没有添加,但这将是单个帖子中的第二个问题,通常不赞成(并且反对或接近投票) ,所以我稍后可能会在不同的帖子中问这个问题。LocalDatedatesUntil

我当前的解决方案是public static Stream<LocalDate> daysOf(YearMonth yearMonth)执行上述代码的操作,但我必须将实例传递给static方法,而不是直接使用它的方法。这符合我的要求,但它仍然不是我认为接近完美的解决方案。

标签: javaclassjava-streamfinaljava-time

解决方案


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

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

基于价值的说:

基于值的类

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

  • 是最终的和不可变的(尽管可能包含对可变对象的引用);
  • 具有、 和的实现equals,这些实现仅根据实例的状态计算,而不是从其标识或任何其他对象或变量的状态计算;hashCodetoString
  • 不使用身份敏感操作,例如==实例之间的引用相等 ()、实例的身份哈希码或实例的内在锁上的同步;
  • 仅基于 被认为相等equals(),而不是基于引用相等 ( ==);
  • 没有可访问的构造函数,而是通过工厂方法实例化,这些方法不承诺返回实例的身份;
  • 在相等时可以自由替换,这意味着在任何计算或方法调用中交换任何两个实例x并且y相等的实例equals()不应该产生可见的行为变化。

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

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

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


推荐阅读