0

I really like Joda-Time, but I've run into something I consider to be a problem. I'd like to extend some of the classes, specifically DateTime, LocalDate and LocalDateTime. But they are marked "final".

I found a very old thread where this is explained as being a way to ensure the classes remain immutable. http://osdir.com/ml/java-joda-time-user/2006-06/msg00001.html

I've also found a thread here on SO where the need to mark a Java class as final to ensure immutability is debated. Why would one declare an immutable class final in Java?

Anyway, I find it to be a major restriction that I can't extend these classes. Is there anything one can do, short of downloading the source files and modifying them, to create extended versions of these classes?

Edit - Discussion:

The ability to extend a class is one of the most powerful and useful concepts in object-oriented programming. This can always be useful. The author of a class can not possibly be 100% sure that his/her super-duper class will not be even more useful for some programmers when extended to cover use cases that nobody could foresee.

The apparent reason that the Joda-Time classes are marked "final" is to ensure that it is not possible for someone to create an extended class that is mutable, and use that with existing programs that are dependent on the Joda-Time objects being immutable. So to some extent the marking of these classes as "final" is due to the lack of a Java language mechanism that permits classes to be marked "immutable", so they could be extended, but only if the extended class is also marked "immutable".

So given the lack of an "immutable" keyword in Java, I can understand that the author of Joda-Time wants to avoid this situation.

Would the following solution be viable? Could we have a structure where, for example, LocalDate is derived from LocalDateNonFinal? LocalDate is an empty class that is marked "final". All functionality is in LocalDateNonFinal.

So if you really want to extend the LocalDate class, and only intend to use the extended class in your own programs, then you can instead extend LocalDateNonFinal, and call it MyLocalDate. This will not expose other modules to possible mistakes on your part because they will still require LocalDate, and won't accept LocalDateNonFinal or your MyLocalDate.

This could be combined with an attempt to educate programmers who want to extend these classes, warning them of the possible problems if they accidentally create a mutable version and still treat it as if it were immutable. And pointing out that these extended classes will not be usable with other modules that expect the regular ("final") classes.

PS. I'll post my work-around solution in a couple of days, when I'm completely sure. So far I've up-voted two of the answers - thanks for your comments and suggestions. I'm currently leaning towards a wrapper-like solution along the lines suggested by Dmitry Zaitsev.

Community
  • 1
  • 1
RenniePet
  • 11,420
  • 7
  • 80
  • 106
  • possible duplicate of [Use of final class in Java](http://stackoverflow.com/questions/5181578/use-of-final-class-in-java) – Mr. Polywhirl Mar 06 '15 at 16:15
  • 6
    You could use [`composite`](http://en.wikipedia.org/wiki/Composite_pattern) design pattern to extend the functionality, but the class would be a different type. – Eng.Fouad Mar 06 '15 at 16:16
  • 1
    I suspect a design problem if you want to extend classes like DateTime... – keuleJ Mar 06 '15 at 16:19
  • 4
    What problem are you *really* trying to solve? This smells like an XY problem. – Platinum Azure Mar 06 '15 at 16:21
  • @Mr.Polywhirl: I don't see how my question is a duplicate of a question that asks what the "final" keyword means for a Java class. My question implies that I do have some understanding of what "final" means. I just want to know how to extend, or extend in practical terms, certain Joda-Time classes that happen to be marked final. – RenniePet Mar 07 '15 at 12:09
  • @Eng.Fouad: Thanks for your comment. I'm not familiar with the composite design pattern, and I must admit that the example shown in the Wikipedia article seems to be quite different to what I'm trying to do. Do you perhaps have a link to an example that is more along the lines of a wrapper-like class, or have I misunderstood something? – RenniePet Mar 07 '15 at 12:13

4 Answers4

4

It's not possible to extend anything marked final, and forking these classes is not going to be practical. Objects in code other than your own will expect to see the Joda classes and will verify that that is what they get, you will not be able to pass in your own versions. So best case with forking is you'll have one set of objects for your own use and you'll have to convert them to Joda or Java 8 to use them with other code. Also your forked versions won't get the benefit of whatever fixes are made to the original classes in the future, unless you keep copying the fixes over into your own versions. Another problem may be that comparisons between Joda classes and your own versions may not be transitive, the results may depend on which object they're called on.

You can create a utility class with all static methods that will take a Joda object, perform whatever extra functionality you want and return a Joda object. This would be similar to StringUtil classes like those found in apache-commons or to java.lang.Math. That way you avoid the maintenance of forking and you have something you can directly use along with library or framework code.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
2

You can wrap a final class into your own class, provide whatever operations you want, and provide "view" method which will return the original Joda-time object. Like that:

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

}

With this approach you can even make your MyJodaExtension mutable and provide different instances of DateTime if you want (but I hope you don't, immutable classes are great).

As Nathan Hughes said, there is no way you can pass such "inherited" class to other libraries or any code that expects original Joda time class.

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
0

If you really really need to extend the classes you can get the source from here: github link

fork them, mark them as non final and extend them.

Otherwise you cannot extend final classes.

0

Just in case it's of interest for others, here's what I ended up doing. But I've awarded the accepted answer check mark to Dmitry Zaitsev, since his answer was the most useful for me.

This is a base class used by two other classes:

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

And this is one of the classes (the more complicated one) that is derived from the base class:

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));
   }
}
RenniePet
  • 11,420
  • 7
  • 80
  • 106