为什么将java.time.YearMonth设计为最终类?

huangapple go评论71阅读模式
英文:

Why was java.time.YearMonth made a final class?

问题

我计划编写一个类该类`扩展了java.time.YearMonth`,目的是通过扩展`YearMonth`的方法使我能够流式传递该`YearMonth``LocalDate`:

```Java
public class ExtendedYearMonth extends YearMonth {

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

当我发现YearMonth是一个final class时,这个计划立即失败了 ⇒ final classes是不可扩展的

当然,我可以写一个像这样的类

public class ExtendedYearMonth {
    private YearMonth yearMonth;

    // 参数化构造函数、getter和setter已省略以保持简洁性

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

但这不是我想要的,因为它要求我实例化一个YearMonth和一个ExtendedYearMonth,只是为了在特定年份的特定月份中流式传递(和过滤,主要目的是过滤)LocalDate

YearMonth的JavaDocs只是说明了final,但没有说明为什么它是final
>public final class YearMonth
>...
>YearMonth是表示年份和月份组合的不可变日期时间对象。
>...

为什么YearMonth被设置为final

更准确地说:相对于class YearMonthfinal class YearMonth有什么好处?
我无法想象任何理由...

我知道,要回答这个问题需要了解可能在www某处公开的设计决策,但不幸的是,我没有这方面的了解,也没有找到相关来源。

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

我还可以问为什么YearMonth中没有提供这样的方法,或者为什么在Java 9中LocalDate获得datesUntil时没有添加这个方法,但这将是一个单独的问题,通常不被接受(会被投票关闭或降低分数),所以我可以在以后的不同帖子中问这个问题。

我当前的解决方案是一个public static Stream<LocalDate> daysOf(YearMonth yearMonth),它做了与上述代码相同的事情,但我必须将实例传递给一个static方法,而不是直接使用其中的方法。这对于我的要求是可以的,但仍然不是我认为接近完美的解决方案。


<details>
<summary>英文:</summary>

I planned to write a class that `extends java.time.YearMonth` with the purpose to extend the `YearMonth` with a method that enables me to stream the `LocalDate`s of that `YearMonth`:

```Java
public class ExtendedYearMonth extends YearMonth {

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

Well, that plan instantly failed when I found YearMonth to be a final class &rArr; final classes are not extensible.

I could of course write a class like this one

public class ExtendedYearMonth {
    private YearMonth yearMonth;

    // parameterized constructor, getters and setters omitted for brevity

	public Stream&lt;LocalDate&gt; days() {
		LocalDate firstOfMonth = this.yearMonth.atDay(1);
		LocalDate lastOfMonth = firstOfMonth.with(TemporalAdjusters.lastDayOfMonth());
		return firstOfMonth.datesUntil(lastOfMonth);
	}
}

but that's not what I wanted because it requires me to instantiate a YearMonth and an ExtendedYearMonth just to stream (and filter, the main purpose) the LocalDates of a specific month in a specific year.

The JavaDocs of YearMonth just state that it's final, but not why it is final:
>public final class YearMonth
>...
>YearMonth is an immutable date-time object that represents the combination of a year and month.
>...

Why was YearMonth made final?

Or more accurate : What's the benefit of final class YearMonth over a class YearMonth?
I cannot imagine any reason...

I know, to answer this one is required to have insight to the design decisions that may be public somewhere in the www, but, unfortunatley, I don't have this insight and I haven't found a source so far.

In Kotlin, this doesn't matter because one can write an extension function without having to inherit from that class. That's a nice feature of Kotlin, but Java doesn't have this (for now) and I refuse to write a wrapper class for this.

I could also ask why such a method isn't available in YearMonth or wasn't added when LocalDate got datesUntil in Java 9, but that would be a second question in a single post, which is generally frowned (and down- or close-voted) upon, so I may ask that later in a different post.

My current solution is a public static Stream&lt;LocalDate&gt; daysOf(YearMonth yearMonth) which does what the above code does, but I have to pass an instance to a static method instead of directly using a method of it. That's working for my requirement, but it's still not what I consider a near-to-perfection solution.

答案1

得分: 6

YearMonth的文档确实提到了,但是间接地:

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

基于值的说明如下:

> ### 基于值的类
> 一些类,例如java.util.Optionaljava.time.LocalDateTime,是基于值的。基于值的类实例:
> - 是最终的和不可变的(尽管可能包含对可变对象的引用);
> - 其equalshashCodetoString的实现仅基于实例的状态计算,而不基于其身份或任何其他对象或变量的状态;
> - 不使用诸如实例之间的引用相等性(==)、实例的身份哈希码或在实例的内部锁上同步等与身份相关的操作;
> - 仅基于equals()被视为相等,不基于引用相等性(==);
> - 没有可访问的构造函数,而是通过工厂方法实例化,该方法对返回的实例的身份不作承诺;
> - 在相等时可以自由替换,这意味着在任何计算或方法调用中互换任何两个根据equals()相等的实例xy不应产生可见的行为变化。
>
> 如果程序试图区分对基于值的类的两个引用,这些引用表示相等的值,无论是通过直接引用相等性还是间接地通过同步、身份哈希、序列化或任何其他身份敏感的机制,程序可能会产生不可预测的结果。在基于值的类的实例上使用此类身份敏感操作可能会产生不可预测的效果,应避免使用。

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

您可以将基于值的类视为与原始值等效;您无法对int进行子类化,因此也无法对YearMonth进行子类化,因为它仅表示特定值(只是强类型),所有表示相同值的YearMonth实例都应该是相同的,无论是由不同的对象实例还是单个实例表示。这保留了通向Java中实际值类型的未来的可能性。

英文:

The documentation of YearMonth does say it, but indirectly:

> This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of YearMonth may have unpredictable results and should be avoided.

Whereas value-based says:

> ### Value-based Classes
> Some classes, such as java.util.Optional and java.time.LocalDateTime, are value-based. Instances of a value-based class:
> - are final and immutable (though may contain references to mutable objects);
> - have implementations of equals, hashCode, and toString which are computed solely from the instance's state and not from its identity or the state of any other object or variable;
> - make no use of identity-sensitive operations such as reference equality (==) between instances, identity hash code of instances, or synchronization on an instances's intrinsic lock;
> - are considered equal solely based on equals(), not based on reference equality (==);
> - do not have accessible constructors, but are instead instantiated through factory methods which make no committment as to the identity of returned instances;
> - are freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() in any computation or method invocation should produce no visible change in behavior.
>
> A program may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism. Use of such identity-sensitive operations on instances of value-based classes may have unpredictable effects and should be avoided.

It’s not stated explicitly here, but subclassing would contradict these points, as it would lead to the possibility of instances representing the same value (in terms of the baseclass’ state), but not be freely substitutable, when they don’t have the same type. Also, even when the class was not final, the concept of only providing factory methods returning instances of unspecified identity would not allow subclasses, as subclasses require an accessible constructor.

You may see value-based classes as an equivalent to primitive values; you can’t subclass an int, so you can’t subclass a YearMonth, as it only represents a specific value (just strongly typed) and all YearMonth instances representing the same value are supposed to be the same, whether represented by different object instances or a single instance. This keeps the path open to a future with real value types in Java.

答案2

得分: 3

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

如果允许子类存在,这种保证就无法实现。

来自YearMonth类文档

* {@code YearMonth}是一个不可变的日期时间对象,表示年份和月份的组合。
英文:

Because instances of the class are guaranteed to be immutable.

That guarantee could not be made if subclasses were allowed.

From the YearMonth class documentation:

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

huangapple
  • 本文由 发表于 2020年9月25日 22:05:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/64065702.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定