1

How can I make an immutable list of date (java.util.Date) using guava?

I have this snippet:

    Date date = new GregorianCalendar(2014, 4, 1).getTime();    

    // doesn't work: 
    // List<Date> immutableList = ImmutableList.of(date);
    // doesn't work either:
    List<Date> immutableList = ImmutableList.copyOf(new Date[] { date });

    date.setMonth(3);

    System.out.println("immutableList has: " + immutableList.get(0));

Whereas I want to make it a "real" immutable list, so that 0th element will not be changed, when the date object is changed.

Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
XoXo
  • 1,560
  • 1
  • 16
  • 35
  • FYI, the terribly troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Aug 27 '18 at 20:06

4 Answers4

7

As you have already found out, using an immutable list protects the list from modification, but does nothing to safeguard the elements contained in the list. For that, the elements themselves have to be immutable.

Unfortunately, it's not possible to create immutable java.util.Date objects. There's a couple of approaches you could consider:

  1. If you're using Java 8, use the immutable date/time value classes introduced by JSR-310. You can find an introductory article here.
  2. If you're not using Java 8, your best bet would be to use the immutable classes from the Joda Time library.
  3. Write your own date wrapper class that protects the wrapped date object from modification.
  4. While objects of type java.util.Date are mutable, objects of type java.lang.Long are immutable.
    You could therefore create an immutable list of Long objects to store the long values obtained from calling Date.getTime(), and construct new date objects on the fly using the Date(long date) constructor when retrieving from the list.
Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
1

tl;dr

  • No need for Guava. Built-in features suffice.
  • List.of & List.copyOf in Java 9 & 10 respectively produce unmodifiable List implementations.
  • Use modern java.time classes, never Date nor Calendar.
    • The java.time objects are immutable, their state cannot be altered.

Example:

List.copyOf(                                          // Produce an unmodifiable `List` based on elements drawn from another `Collection` in iteration-order. 
    LocalDate.of( 2014 , Month.MARCH , 1 )            // Modern class to represent a date-only value, without time-of-day and without time zone.
    .datesUntil(                                      // Generates a stream of `LocalDate` objects, incrementing one day at a time. 
        LocalDate.of( 2014 , Month.APRIL , 1 )        // The `LocalDate` class is designed to produce immutable objects, with state that can be read but not altered (not “mutated”). 
    )                                                 // Returns a `Stream<LocalDate>`.
    .collect(                                         // Processes the series of dates coming from the stream.
        Collectors.toCollection( ArrayList :: new )   // Creates a `List`, and adds the series of dates to that list. 
    )                                                 // Returns a `List<LocalDate>`. 
)                                                     // Returns another `List<LocalDate>` whose elements cannot be added, dropped, or replaced from the collection.
.toString()                                           // Generates a `String` with text representing the value of each contained `LocalDate` object.

[2014-03-01, 2014-03-02, 2014-03-03, 2014-03-04, 2014-03-05, 2014-03-06, 2014-03-07, 2014-03-08, 2014-03-09, 2014-03-10, 2014-03-11, 2014-03-12, 2014-03-13, 2014-03-14, 2014-03-15, 2014-03-16, 2014-03-17, 2014-03-18, 2014-03-19, 2014-03-20, 2014-03-21, 2014-03-22, 2014-03-23, 2014-03-24, 2014-03-25, 2014-03-26, 2014-03-27, 2014-03-28, 2014-03-29, 2014-03-30, 2014-03-31]

No need for Guava

The Google Guava library is extremely useful in many ways. However, it is no longer needed to accomplish your goal of a read-only data structure for a series of dates. Java now has built-in features to support this.

java.time

The modern approach uses the java.time classes that supplanted the terrible old date-time classes such as Date & Calendar.

LocalDate

The LocalDate class represents a date-only value without time-of-day and without time zone.

Your Question is not clear. Seems like maybe what you wanted was a list of dates from the previous month.

LocalDate stop = LocalDate.of( 2014 , Month.APRIL , 1 ) ;  // Sane numbering: `2014` is the year 2014, and 1-12 = Jan-Dec.
LocalDate start = stop.minusMonths( 1 ) ;  

You can use the Java Streams facility to generate the series of LocalDate objects. First make a Stream< LocalDate >.

Stream< LocalDate > streamOfLocalDates = start.datesUntil( stop ) ;

Then, collect the series of LocalDate objects emitted from that stream into a List collection.

List< LocalDate > localDates = 
    streamOfLocalDates.collect( 
        Collectors.toCollection( ArrayList::new ) 
    )
;

The List produced above is modifiable, meaning we can add, drop, or replace its elements. To protect against such changes to the List, produce another List, an unmodifiable List, using the new List.copyOf feature in Java 10 and later.

List< LocalDate > localDatesUnmodifiable = 
    List.copyOf( 
        localDates 
    ) 
;

We have two levels of protection in place here to protect against changes to our data.

  • Using List.copyOf protects you from adding, dropping, or replacing elements within the list. It does nothing to stop you from modifying the state within the object stored in each element.
  • The fact that the java.time classes use the immutable objects pattern protects you from changes in your elements’ object’s state.

So a List.copyOf( List< LocalDate > ) cannot be modified in its elements nor in their content, a completely read-only data structure.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

No way. If you really want to use Date, you have to clone each get() result in order to protect the stored value. You also have to clone the dates you put in, unless you're sure they won't change afterwards.

The best thing you can do is to switch to some immutable datatype. Otherwise, you have to wrap all accesses, which is a real pain. You could base your List on long[] instead (this is probably less error-prone than adapting a list, as there's no chance of mistakenly publishing a stored object).

maaartinus
  • 44,714
  • 32
  • 161
  • 320
0

If you don't have a choice, but to use Date object, then you can always return a copy of the dates object. This will make your object Immutable.

public final class ImmutableDates{
    private final List<Date> dates;
    // other final fields 

    // constructor with final dates and other final fields as argument

    // don't expose setter

    // always create and return a new list populated with new dates
    public List<Date> getDates() {
        List<Date> newDates = dates.stream().map(p -> new Date(p.getTime())).collect(Collectors.toList());
        return newDates;
    }
}

Always create and return a new list populated with new dates

thekosmix
  • 1,705
  • 21
  • 35