One of the interesting challenges software developer experiences in his daily work life is – Time. Whenever it is related to project deadlines, benchmarking your application or Time in the application itself. In this post I will share with you few tips working with Date class in Java applications, specifically it’s limitations.
Let’s look at the class below, I was trying to create an immutable class which represents Reservation(for example in the restaurant):
import java.util.Date; public final class Reservation { private final Date start; private final Date end; public Reservation(Date start, Date end) { if (start.after(end)) throw new IllegalArgumentException(start + " after " + end); this.start = start; this.end = end; } public Date getStart() { return start; } public Date getEnd() { return end; } @Override public String toString() { return "Reservation{" + "start=" + start + ", end=" + end + '}'; } }
Now I want to use my class and let’s see what happen if I run this code:
import java.util.Date; public class Main { public static void main(String[] args) { Date start = new Date(); Date end = new Date(); Reservation reservation = new Reservation(start, end); System.out.println(reservation); start.setTime(start.getTime() + 99999999l); System.out.println(reservation); } }
Reservation{start=Mon Sep 10 19:00:20 PDT 2018, end=Mon Sep 10 19:00:20 PDT 2018} Reservation{start=Tue Sep 11 22:47:00 PDT 2018, end=Mon Sep 10 19:00:20 PDT 2018}
Oops, my “immutable” class didn’t really work, I was able to update start date with no trouble, which in it’s turn modified my “immutable” object.
How can we prevent this? We can try to do “defensive” copies of parameters.
import java.util.Date; public final class Reservation { private final Date start; private final Date end; public Reservation(Date start, Date end) { if (start.after(end)) throw new IllegalArgumentException(start + " after " + end); this.start = new Date(start.getTime());//defensive copy this.end = new Date(end.getTime());//defensive copy } public Date getStart() { return start; } public Date getEnd() { return end; } @Override public String toString() { return "Reservation{" + "start=" + start + ", end=" + end + '}'; } }
And after we run code from the main class, it will fix our issue(almost):
import java.util.Date; public class Main { public static void main(String[] args) { Date start = new Date(); Date end = new Date(); Reservation reservation = new Reservation(start, end); System.out.println(reservation); start.setTime(start.getTime() + 99999999l); System.out.println(reservation); } }
Reservation{start=Mon Sep 10 19:05:42 PDT 2018, end=Mon Sep 10 19:05:42 PDT 2018} Reservation{start=Mon Sep 10 19:05:42 PDT 2018, end=Mon Sep 10 19:05:42 PDT 2018}
But we didn’t finish yet, I still can modify “immutable” class, like so:
import java.util.Date; public class Main { public static void main(String[] args) { Date start = new Date(); Date end = new Date(); Reservation reservation = new Reservation(start, end); System.out.println(reservation); reservation.getStart().setTime(start.getTime() + 99999999l); System.out.println(reservation); } }
Reservation{start=Mon Sep 10 19:12:23 PDT 2018, end=Mon Sep 10 19:12:23 PDT 2018} Reservation{start=Tue Sep 11 22:59:03 PDT 2018, end=Mon Sep 10 19:12:23 PDT 2018}
So we are back where we started, but don’t despair, we just forget to do defensive copies on getters, which will finally fix our issue:
import java.util.Date; public final class Reservation { private final Date start; private final Date end; public Reservation(Date start, Date end) { if (start.after(end)) throw new IllegalArgumentException(start + " after " + end); this.start = new Date(start.getTime());//defensive copy this.end = new Date(end.getTime());//defensive copy } public Date getStart() { return new Date(start.getTime());//defensive copy } public Date getEnd() { return new Date(end.getTime());//defensive copy } @Override public String toString() { return "Reservation{" + "start=" + start + ", end=" + end + '}'; } }
import java.util.Date; public class Main { public static void main(String[] args) { Date start = new Date(); Date end = new Date(); Reservation reservation = new Reservation(start, end); System.out.println(reservation); reservation.getStart().setTime(start.getTime() + 99999999l); System.out.println(reservation); } }
Reservation{start=Mon Sep 10 19:20:30 PDT 2018, end=Mon Sep 10 19:20:30 PDT 2018} Reservation{start=Mon Sep 10 19:20:30 PDT 2018, end=Mon Sep 10 19:20:30 PDT 2018}
The real lesson in all of this is that you should, whenever possible, use immutable objects as components of your object so that you don’t have to worry about your defensive copying. Since Java 8 you have Instant class from java.time package, which will fit nicely in our class.
import java.time.Instant; public final class Reservation { private final Instant start; private final Instant end; public Reservation(Instant start, Instant end) { if (start.isAfter(end)) throw new IllegalArgumentException(start + " after " + end); this.start = start; this.end = end; } public Instant getStart() { return start; } public Instant getEnd() { return end; } @Override public String toString() { return "Reservation{" + "start=" + start + ", end=" + end + '}'; } }