是否有可能延长标记为最终的Joda-Time类?

时间:2015-03-06 16:13:51

标签: java jodatime

我真的很喜欢Joda-Time,但我遇到了一些我认为有问题的事情。我想扩展一些类,特别是DateTime,LocalDate和LocalDateTime。但是他们被标记为" final"。

我发现了一个非常旧的线程,这被解释为确保类保持不可变的一种方法。 http://osdir.com/ml/java-joda-time-user/2006-06/msg00001.html

我还在SO上找到了一个线程,其中需要将Java类标记为final以确保不变性。 Why would one declare an immutable class final in Java?

无论如何,我发现这是一个主要的限制,我无法扩展这些类。有没有什么可以做的,没有下载源文件和修改它们,以创建这些类的扩展版本?

编辑 - 讨论:

扩展类的能力是面向对象编程中最强大和最有用的概念之一。这可以始终有用。一个班级的作者不可能100%确定他/她的超级课程对于一些程序员在扩展到涵盖无人能预见的用例时不会更有用。

Joda-Time课程被标记为" final"是为了确保某人不可能创建一个可变的扩展类,并将其用于依赖于Joda-Time对象不可变的现有程序。所以在某种程度上这些类的标记为" final"是因为缺少Java语言机制,允许类被标记为" immutable",所以它们可以被扩展,但前提是扩展类也被标记为" immutable"。

因此缺乏一个"不可变的"在Java中的关键字,我可以理解Joda-Time的作者希望避免这种情况。

以下解决方案是否可行?我们可以有一个结构,例如,LocalDate派生自LocalDateNonFinal吗? LocalDate是一个空类,标记为" final"。所有功能都在LocalDateNonFinal中。

因此,如果您真的想要扩展LocalDate类,并且只打算在您自己的程序中使用扩展类,那么您可以改为扩展LocalDateNonFinal,并将其命名为MyLocalDate。这不会让其他模块暴露于您可能的错误,因为它们仍然需要LocalDate,并且不会接受LocalDateNonFinal或MyLocalDate。

这可以与尝试教育想要扩展这些类的程序员相结合,如果他们意外地创建了一个可变版本并且仍然将其视为不可变,则警告他们可能存在的问题。并指出这些扩展类不适用于期望常规(" final")类的其他模块。

PS。当我完全确定时,我会在几天内发布我的解决方案。到目前为止,我已经投了两个答案 - 感谢您的意见和建议。我现在倾向于采用Dmitry Zaitsev建议的类似包装的解决方案。

4 个答案:

答案 0 :(得分:4)

不可能扩展任何标记为final的内容,并且分叉这些类并不实用。除了你自己的代码中的对象将期望看到Joda类并将验证它们是什么,你将无法传递自己的版本。因此,分叉的最佳情况是您将拥有一组供您自己使用的对象,并且您必须将它们转换为Joda或Java 8以将其与其他代码一起使用。此外,您的分叉版本将无法获得将来对原始类进行的任何修复的好处,除非您继续将修复复制到您自己的版本中。另一个问题可能是Joda类和您自己的版本之间的比较可能不具有传递性,结果可能取决于它们被调用的对象。

您可以创建一个包含所有静态方法的实用程序类,这些方法将获取Joda对象,执行您想要的任何额外功能并返回Joda对象。这类似于在apache-commons或java.lang.Math中找到的StringUtil类。这样你就可以避免分叉的维护,并且你可以直接使用库和框架代码。

答案 1 :(得分:2)

您可以将最终课程包装到您自己的课程中,提供您想要的任何操作,并提供" view"将返回原始Joda-time对象的方法。像那样:

public class MyJodaExtension {

    private final DateTime dateTime;

    public MyJodaExtension(DateTime dateTime) {
        this.dateTime = dateTime;
    }

    public boolean myOperation() {
        return false;  // or whatever you need
    }

    public DateTime asDateTime() {
        return dateTime;
    }

}

使用这种方法,您甚至可以使MyJodaExtension变为可变,并根据需要提供DateTime的不同实例(但我希望您不要,不可变类很棒)。

正如内森休斯所说,你无法通过这样的继承"类到其他库或任何需要原始Joda时间类的代码。

答案 2 :(得分:0)

如果你真的需要扩展类,可以从这里获取源代码: github link

分叉它们,将它们标记为非最终并扩展它们。

否则你无法扩展最终的课程。

答案 3 :(得分:0)

以防其他人感兴趣,这就是我最终做的事情。但我已经将接受的答案复选标记给Dmitry Zaitsev,因为他的回答对我来说最有用。

这是另外两个类使用的基类:

package com.Merlinia.MCopier_Main;

import org.joda.time.DateTime;


/**
 * This common base class is used to provide a (mutable) wrapper for the (immutable) Joda-Time
 * DateTime and LocalDateTime classes. It is used as the base class (super class, in Java
 * terminology) for the DateTimeLocal and DateTimeUtc classes. This provides a somewhat kludgy way
 * of extending the DateTime and LocalDateTime classes, since they can't be directly extended
 * because they are marked "final".
 *
 * The only service provided by this class is that it contains a field which can contain the .Net
 * DateTime "ticks" value that was used to create this object via MCopier deserialization. This is
 * then used by MCopier serialization to provide an identical result if the object is round-tripped
 * from .Net to Java and back again, although only if the associated DateTime or LocalDateTime
 * object has not been updated. (If the DateTime or LocalDateTime object is updated then this field
 * is set to Long.MIN_VALUE, and is no longer considered valid.) Sending an identical result back to
 * .Net simplifies the testing program, as well as avoiding unnecessary loss of precision. (Joda-
 * Time is only precise to the nearest millisecond. .Net DateTime ticks can, in theory, be precise
 * to the nearest 100 nanoseconds.)
 */
public abstract class DateTimeCommon {

   // See here: http://stackoverflow.com/questions/3706306/c-sharp-datetime-ticks-equivalent-in-java
   private static final long CTicksAtEpoch = 621355968000000000L;
   private static final long CTicksPerMillisecond = 10000;


   private long _dotNetDateTimeTicks;  // Long.MIN_VALUE means not valid


   // Constructor for new object, not due to MCopier deserialization
   public DateTimeCommon() {
      _dotNetDateTimeTicks = Long.MIN_VALUE;
   }

   // Copy constructor
   public DateTimeCommon(DateTimeCommon copyFrom) {
      _dotNetDateTimeTicks = copyFrom._dotNetDateTimeTicks;
   }

   // Constructor used by MCopier deserialization
   public DateTimeCommon(long dotNetDateTimeTicks) {
      _dotNetDateTimeTicks = dotNetDateTimeTicks;
   }


   protected void indicateDotNetTicksNotValid() {
      _dotNetDateTimeTicks = Long.MIN_VALUE;
   }


   // Method used by MCopier deserialization to compute the number of milliseconds in Java notation
   // that corresponds to a long int containing a .Net DateTime value in "ticks". But note that
   // although Java millis are normally always based on the UTC time zone, that the millis returned
   // by this method in the case of a .Net DateTimeLocal value are not UTC-based; they are
   // independent of time zone and represent a different instant in time for different time zones.
   // See also here:
   // http://stackoverflow.com/questions/3706306/c-sharp-datetime-ticks-equivalent-in-java
   protected static long convertTicksToMillis(long dotNetDateTimeTicks) {

      return
         (dotNetDateTimeTicks - CTicksAtEpoch + CTicksPerMillisecond / 2) / CTicksPerMillisecond;
   }


   // Method used by MCopier serialization
   protected long getAsDotNetTicks(DateTime jodaDateTime) {

      if (_dotNetDateTimeTicks != Long.MIN_VALUE) {
         return _dotNetDateTimeTicks;
      }
      return (jodaDateTime.getMillis() * CTicksPerMillisecond) + CTicksAtEpoch;
   }
}

这是从基类派生的类之一(更复杂的类):

package com.Merlinia.MCopier_Main;

import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;


/**
 * This class provides a (mutable) wrapper for the (immutable) Joda-Time LocalDateTime class. See
 * comments on the DateTimeCommon base class.
 *
 * All manipulation of this object should consist of a call to getJodaLocalDateTime() followed by
 * manipulation of the LocalDateTime object, producing a new (immutable) LocalDateTime object,
 * followed by a call to setJodaLocalDateTime().
 *
 * When doing MCopier serialization and deserialization from/to .Net DateTime "ticks" we do
 * something a bit sneaky: We pretend that the ticks represent a UTC time, and we pretend that the
 * associated Joda-Time LocalDateTime object also represents UTC time. Both of these pretences are
 * (normally) false, but the end result is that it works. See also here:
 * http://stackoverflow.com/questions/11665404/simplest-way-to-get-local-milliseconds-in-a-time-zone-with-joda-time
 */
public class DateTimeLocal extends DateTimeCommon {

   private LocalDateTime _jodaLocalDateTime;


   // Constructor for new object, not due to MCopier deserialization
   public DateTimeLocal(LocalDateTime jodaLocalDateTime) {
      super();
      _jodaLocalDateTime = jodaLocalDateTime;
   }

   // Copy constructor
   public DateTimeLocal(DateTimeLocal copyFrom) {
      super(copyFrom);
      _jodaLocalDateTime = copyFrom._jodaLocalDateTime;
   }

   // Constructor used by MCopier deserialization
   public DateTimeLocal(long dotNetDateTimeTicks) {
      super(dotNetDateTimeTicks);
      _jodaLocalDateTime = new LocalDateTime(
                        DateTimeCommon.convertTicksToMillis(dotNetDateTimeTicks), DateTimeZone.UTC);
   }


   public LocalDateTime getJodaLocalDateTime() {
      return _jodaLocalDateTime;
   }

   public void setJodaLocalDateTime(LocalDateTime jodaLocalDateTime) {
      _jodaLocalDateTime = jodaLocalDateTime;
      super.indicateDotNetTicksNotValid();
   }


   // Method used by MCopier serialization
   public long getAsDotNetTicks() {
      return super.getAsDotNetTicks(_jodaLocalDateTime.toDateTime(DateTimeZone.UTC));
   }
}