A member variable's hashCode() value is different
There's a piece of code that looks l开发者_如何学Goike this. The problem is that during bootup, 2 initialization takes place. (1) Some method does a reflection on ForumRepository
& performs a newInstance() purely to invoke #setCacheEngine
. (2) Another method following that invokes #start()
. I am noticing that the hashCode of the #cache
member variable is different sometimes in some weird scenarios. Since only 1 piece of code invokes #setCacheEngine
, how can the hashCode change during runtime (I am assuming that a different instance will have a different hashCode). Is there a bug here somewhere ?
public class ForumRepository implements Cacheable
{
private static CacheEngine cache;
private static ForumRepository instance;
public void setCacheEngine(CacheEngine engine) { cache = engine; }
public synchronized static void start()
{
instance = new ForumRepository();
}
public synchronized static void addForum( ... )
{
cache.add( .. );
System.out.println( cache.hashCode() );
// snipped
}
public synchronized static void getForum( ... )
{
... cache.get( .. );
System.out.println( cache.hashCode() );
// snipped
}
}
The whole system is wired up & initialized in the init
method of a servlet.
And the init() method looks like this conceptually:
// create an instance of the DefaultCacheEngine
cache = (CacheEngine)Class.forName( "com..DefaultCacheEngine" ).newInstance();
cache.init();
// init the ForumRepository's static member
Object o = Class.forName( "com.jforum....ForumRepository" ).newInstance();
if( o instanceof Cacheable )
((Cacheable)o).setCacheEngine(cache);
// Now start the ForumRepository
ForumRepository.start();
UPDATE I didn't write this code. It is taken from jforum
UPDATE 2 Solution found. I added a separate comment below describing the cause of the problem. Thanks to everyone.
You're going to have to give WAY more information than this, but CacheEngine
is probably a mutable data type, and worse, it may even be shared by others. Depending on how CacheEngine
defines its hashCode()
, this could very well lead to aForumRepository
seeing various different hash codes from its cache
.
It's perfectly fine for the same object, if it's mutable, to change its hashCode()
over a period of time, as long as it's done in a consistent manner (which is another topic altogether).
See also
Object.hashCode()
-- make sure you understand the implications of the contract
On cache
being static
More information has resurfaced, and we now know that the object in question, while mutable, does not @Override hashCode()
. However, there seems to be a serious issue in design in making cache
a static
field of ForumRepository
class, with a non-static
"setter" setCacheEngine
(which looks to be specified by Cacheable
).
This means that there is only incarnation of cache
, no matter how many ForumRepository
instances are created! In a way, all instances of ForumRepository
are "sharing" the same cache
!
JLS 8.3.1.1
static
FieldsIf a field is declared
static
, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. Astatic
field, sometimes called a class variable, is incarnated when the class is initialized.
I think it's important to step back right now and ask these questions:
- Does
cache
need to bestatic
? Is this intended?- Should instances of
ForumRepository
have their owncache
? - ... or should they all "share" the same
cache
?
- Should instances of
- How many instances of
ForumRepository
will be created?- Putting pros and cons of the design pattern aside, should
ForumRepository
be a singleton?
- Putting pros and cons of the design pattern aside, should
- How many times can
setCacheEngine
be called on aForumRepository
object?- Would it benefit from a defensive mechanism of throwing
IllegalStateException
if it's called more than once?
- Would it benefit from a defensive mechanism of throwing
The best recommendations would depend on the answers to the above questions. The third bullet point is something that is immediately actionable, and would reveal if setCacheEngine
is getting invoked multiple times. Even if they're only invoked once for each ForumRepository
instance, it's still effectively a multiple "set" in the current state of affairss, since there's only one cache
.
A static
field with a non-static
setter is a design decision that needs to be thoroughly reexamined.
are you sure the ForumRepository classes that you are comparing are the same. if you are doing newInstance you might have a weird classloader issue.
Mutable objects with hashCode
implementations based on mutable state are almost always a bad idea. For example, they behave very strangely in hash-based collections -- if you insert such an object into a HashSet
and then mutate it, the contains
method won't be able to find it anymore.
Objects that are naturally distinguished by their identity should not override equals
and hashCode
at all. If you override hashCode
based on state, that state should be immutable. Examples are String
and the boxing types. Those are often referred to as "value classes", because their identity has no significance -- it is meaningless to distinguish between multiple instances of new Integer(42)
.
The thing that puzzles me about this question is this:
Why are you looking at the hashcode of a CacheEngine instance?
It seems that your code is putting Forum
objects into a cache and getting them back. As such, it makes sense to look at the hashcode values for the Forum
instances. But the hashcode of the cache itself would appear to be entirely irrelevant.
Having said that, if the DefaultCacheEngine
class inherits its implementation of hashcode
from java.lang.Object
then the value returned by the method is the "identity" hashcode, and this cannot change over the lifetime of an object. If it does appear to change, this means that you are now looking at a different instance of the DefaultCacheEngine class.
I've solved my problem and I would like to share with you what I've learnt or discovered.
Root cause of the bug
The jforum.war
webapp was being loaded twice by Tomcat 6.x, via two different virtual hosts. So yes, the CacheEngine
was displaying two different hashCodes because they were loaded up in separate webapp classloaders.
Solution
The quick fix for me was to limit the invocation of the servlets in jforum.war
via one specific virtual host address.
精彩评论