HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Helper class to memoize DateFormats application-wide

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
dateformatsapplicationhelperwideclassmemoize

Problem

Premise

Consider the following method:

static String formatMyDate(Date date) {
    return new SimpleDateFormat("yyyy-MM-dd").format(date);
}


It's often desirable to memoize DateFormat objects so they can be reused rather than repeatedly instantiating new ones. This frequently leads to the following naive refactor:

private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

static String formatMyDate(Date date) {
    return DATE_FORMAT.format(date);
}


But this is wrong for multithreaded applications. From the DateFormat documentation:


Date formats are not synchronized. It is recommended to create
separate format instances for each thread. If multiple threads access
a format concurrently, it must be synchronized externally.

Assuming our application is using thread pooling, this leads us to memoize an object per thread, using ThreadLocal (see article):

private static final ThreadLocal DATE_FORMAT_REF =
        new ThreadLocal() {
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd");
            }
        };

static String formatMyDate(Date date) {
    return DATE_FORMAT_REF.get().format(date);
}


This works fine. But I've noticed it can still leave code duplication across a larger project. For example multiple developers could each write their own code using "yyyy-MM-dd" date formats in various classes. This led me to write the following helper class (using JSR-305 and Guava):

Solution 1

```
public class DateFormats {

private static final ThreadLocal> DATE_FORMAT_MAP_REF =
new ThreadLocal>() {
@Override
protected Map initialValue() {
return Maps.newHashMap();
}
};

private DateFormats() { }

/**
* Retrieves, and if necessary creates and caches, a {@code DateFormat} instance
* corresponding to the specified for

Solution

It looks like the questions are very specific. You should probably formulate a goal. Is your main goal to reduce code duplicity? Memory usage? Speed (however we define it here)? Something else? A combination of previous items? (which makes life harder the more items you combine)
This affects the first two questions.

For the first question, I could imagine, that you have some common date formats. If less than 5 formats make up to 90% of usage, I would supply a specific method like

/** 
 * Formats the date exactly the same as SimpleDateFormat("yyyy-MM-dd").format(date),
 * but is multi threaded safe without any penalties in subsequent calls.
 * Example: 2012-09-30
 */
public static String (DateFormats.)formatDate_yyyy_MM_dd(Date date) { ... }


You could use ThreadLocal then.

This would solve this the easiest way. If you need any more sophisticated solution, you could change the class internals without breaking any external code.

For the last 2 questions, I do not have experience with LoadingCache, so I cannot help there.

I would have used a standard HashMap to store the ThreadLocal Version of DateFormat. As far as I thought about it, we do not need synchronization for the map, because we do not want to replace entries, we do not care if we put something multiple times in rare cases for the initialization part of a new datestring and we do not remove anything.

A bit offtopic, but: One thing I do not get about your description. If they do not care about using yoda (which could be used inside this DateFormats class and no one would notice about it), how could you convince them to use this DateFormats class?

Code Snippets

/** 
 * Formats the date exactly the same as SimpleDateFormat("yyyy-MM-dd").format(date),
 * but is multi threaded safe without any penalties in subsequent calls.<br>
 * Example: 2012-09-30
 */
public static String (DateFormats.)formatDate_yyyy_MM_dd(Date date) { ... }

Context

StackExchange Code Review Q#22867, answer score: 3

Revisions (0)

No revisions yet.