layout | title | date | tags |
---|---|---|---|
post |
The Flyweight Pattern in Java |
2016-10-06 09:23:13 -0400 |
programming java |
Types in Java fall into two categories, the primitive types (char
,
boolean
, byte
, short
, int
, long
, float
, double
) and the reference
types
(objects and arrays). Each primitive type has a corresponding reference type,
often called a boxed primitive, which allow these types to be used anywhere an
object would be required, as well as adding some useful methods and fields.
Conversion from a primitive to a boxed primitive is called boxing and the
converse
is
called unboxing. Objects are typically created by invoking a constructor, or
using a static factory method. One of the major differences between these two
techniques is that a constructor requires a new object's reference to be
returned[^citation?], while a factory method would merely have to return an
object of that type. This means that the same object can be returned on multiple
invocations when using a factory method.
As a result static factory methods facilitate the implementation of the
flyweight pattern. In the case of an immutable class, if some
values are requested much more frequently than others, it may make sense from a
performance and memory perspective for the class to keep a cache of the most
commonly requested values, instead of creating a new copy wherever one is
requested. The Java Language Specification (JLS) gives us a simple
case-study of this pattern in use. According to the JLS, if p
is an int
in
\([-128, 127]\) it is guaranteed that two boxing operations on p
will return
the same reference. In other words, the Integer
class must cache all of the
values in \([-128, 127]\)1.
On my laptop (openjdk 8.u102-1)2, running
public class Flyweight
{
public static void main(String[] args)
{
System.out.println(Integer.valueOf(127) == Integer.valueOf(127));
System.out.println(Integer.valueOf(128) == Integer.valueOf(128));
System.out.println(new Integer(127) == new Integer(127));
}
}
prints
true
false
false
As discussed above, explicitly invoking the constructor twice will create two
new objects. Since ==
on objects tests equality of reference on objects, this
will fail. The first example shows that Integer.valueOf(127)
will always
return the same reference. Relying on this behavior may result in subtle bugs:
==
may work in the most common cases and fail in rarer cases. If my
application uses the first 127 integers often, I may not notice this at all
until one day I have a perplexing error where ==
does not work properly. As a rule of thumb, when testing object equality ==
will rarely do want you want; use the equals()
method instead.
Some takeaways:
- Don't use
==
comparison on objects. It could potentially lead to a subtle bug. Instead useObject.equals()
. - Consider using
valueOf
or other static factories instead of constructors to take advantage of caching. This can positively impact your applications performance.3 - Don't use the boxed primitive constructors. 4
Footnotes
-
there are similar rules for
Character
andBoolean
↩ -
Note that this behavior is JVM specific, as a JVM is only required to cache the values in \([-128, 127]\), but may cache more. ↩
-
There are other advantages of using static factories over constructors, see Effective Java Item (something) for a discussion of the trade-offs. ↩
-
The constructors of the boxed primitives are deprecated in Java 9 ↩