【问题标题】:Is it somehow possible to extend Joda-Time classes marked final?是否有可能扩展标记为 final 的 Joda-Time 类?
【发布时间】:2015-03-11 23:11:25
【问题描述】:

我真的很喜欢 Joda-Time,但我遇到了一些我认为是问题的事情。我想扩展一些类,特别是 DateTime、LocalDate 和 LocalDateTime。但它们被标记为“最终”。

我发现了一个非常古老的线程,它被解释为确保类保持不可变的一种方法。 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 的现有程序一起使用对象是不可变的。所以在某种程度上将这些类标记为“最终”是由于缺乏允许类被标记为“不可变”的Java语言机制,因此它们可以被扩展,但前提是扩展的类也被标记为“不可变” ”。

所以鉴于 Java 中缺少“不可变”关键字,我可以理解 Joda-Time 的作者想要避免这种情况。

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

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

这可以与尝试教育想要扩展这些类的程序员相结合,警告他们如果他们不小心创建了一个可变版本并且仍然将其视为不可变的可能出现的问题。并指出这些扩展类将无法与其他需要常规(“最终”)类的模块一起使用。

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

【问题讨论】:

  • Use of final class in Java 的可能副本
  • 您可以使用composite 设计模式来扩展功能,但类将是不同的类型。
  • 如果您想扩展 DateTime 之类的类,我怀疑存在设计问题...
  • 真正想解决什么问题?这闻起来像是 XY 问题。
  • @Mr.Polywhirl:我看不出我的问题与询问“final”关键字对 Java 类意味着什么的问题重复。我的问题意味着我确实对“最终”的含义有所了解。我只是想知道如何扩展或实际扩展某些恰好被标记为 final 的 Joda-Time 类。

标签: java jodatime


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    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 的不同实例(但我希望您不要这样做,不可变类很棒)。

    正如 Nathan Hughes 所说,您无法将此类“继承”类传递给其他库或任何需要原始 Joda 时间类的代码。

    【讨论】:

      【解决方案3】:

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

      fork 它们,将它们标记为非 final 并扩展它们。

      否则你不能扩展最终类。

      【讨论】:

        【解决方案4】:

        以防万一其他人感兴趣,这就是我最终做的事情。但我已将接受的答案复选标记授予 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));
           }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-04-04
          • 1970-01-01
          • 2013-08-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-06
          相关资源
          最近更新 更多