10

Simply, I have to override the way in which the cache chooses the right key because some fields (e.g., timestamp, message id, etc.) shouldn't be considered when retrieving a key. I cannot modify the actual hash function of the key object because it is already used to recognize in my code.
Is it possibile with Guava Caches? And with a workaround?

This is my configuration:

CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).recordStats().
    expireAfterWrite(DEFAULT_AGE, TimeUnit.DAYS).build(
    new CacheLoader<Request, Response>() {
        @Override
        public Response load(Request request) { 
            return request.getResponse();
        }
    });

And this is my hash function (used somewhere else in my code):

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + code;
    result = prime * result + messageID; // <= **this one shouldn't evaluated**
    result = prime * result + Arrays.hashCode(payload);
    result = prime * result + (int) (timestamp ^ timestamp >>> 32); // <= **this one shouldn't evaluated**
    result = prime * result + (type == null ? 0 : type.hashCode());
    result = prime * result + version;
    return result;
}

Btw, is this kind of cache using its own implementation of hash function (e.g., through introspection) or is it using the default one?

** EDIT: **
As pointed out in the responses, the best way to achieve this result is a wrapper class.
My solution:

/**
 * Nested class to store a request as a key in the cache. It is needed to
 * normalize the variable fields of the normal requests.
 */
private static final class CachedRequest extends Request {

    private static CachedRequest fromRequest(Request request) {
        // set only the fields that cannot change between two same requests
        // ...
    }

    @Override
    public int hashCode() {
        HashFunction hashFunction = Hashing.md5();
        HashCode hashCode;
        // ...
        return hashCode.asInt();
    }

    @Override
    public boolean equals(Object obj) {
            // coherent with hashCode()
            // ...
    }
}
Hamal000
  • 516
  • 1
  • 5
  • 25
  • i can't imagine it would be a very useful utility if it used its own hash implementation. – jtahlborn Sep 05 '12 at 15:04
  • also, can you further explain what you mean by "I cannot modify my hash() function because it is already used to recognize "real" equals objects."? – jtahlborn Sep 05 '12 at 15:09
  • In fact I'm not asking to add this feature, but if someone knows how to deal with this situation. – Hamal000 Sep 05 '12 at 15:11
  • you deal with this situation by implementating hashCode() and equals() appropriately on your keys (i.e. your Request). – jtahlborn Sep 05 '12 at 15:12
  • In the title, "Custom equals/hash" means "different from the usual already declared functions". This means that I have already a hash function and I cannot modify it. – Hamal000 Sep 05 '12 at 15:21
  • added an answer. you do realize, though, that you can _remove_ fields from the hashcode without breaking other usage of it, right? a less specific hashcode is still a valid hashcode. also note that equality of hashcode _means nothing_ in terms of equality of the actual class values. – jtahlborn Sep 05 '12 at 15:23
  • 1
    Removing fields from `hashCode` alone while leaving them in `equals` 1. may be bad for performance, 2. is surely correct, 3. *makes no sense at all.* While @Hamal000 said nothing about his equals, I'd suppose there are included there as well. – maaartinus Sep 05 '12 at 15:34
  • A reminder: http://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-java and http://stackoverflow.com/questions/410236/how-to-ensure-hashcode-is-consistent-with-equals – Hamal000 Sep 05 '12 at 15:41

4 Answers4

16

You could simply wrap your Request objects into CachedRequest objects, where CachedRequest would implement hashCode() and equals() based on the desired fields, and provide access to the wrapped Request.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
2

I'm pretty sure, it's not possible with Guava. There are some legitimate cases for using a custom Equivalence, but they say they're too rare to be handled by the CacheBuilder and MapMaker.1 There's even com.google.common.base.Equivalence, but it gets used only internally (see also here and here).

You need to make you own key out of the fields you want to use for the lookup, or wrap your Request in another object defining equals and hashCode the way you want.

1The only case when something different from the default equals/hashCode gets used is with weakKeys (softKeys exists no more), and then it's the ==/System.identityHashCode combo. In no case you can choose the equivalence freely.

Community
  • 1
  • 1
maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • 1
    `Equivalence` is public: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Equivalence.html – fry Sep 06 '12 at 08:01
  • 1
    @fry: Indeed it is, but there's only one public use (in `Maps.difference`). – maaartinus Sep 07 '12 at 01:55
1

If you can't modify the hash function (i still don't understand why), then you need to use a "wrapper" key for you cache, e.g.:

public class RequestKey {
  private final Request _req;

  public int hashCode() {
    // use appropriate Request fields here
  }

  public boolean equals(Object o) {
    return ((this == o) || ((o != null) && (getClass() == o.getClass()) && _req.equals(((RequestKey)o)._req)));
  }
}
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • I cannot modify the hash because it is legacy code and the hash is already used somewhere else. – Hamal000 Sep 05 '12 at 15:24
  • @Hamal000 - i understand that. however, removing fields from the hashcode should not reduce its usefulness elsewhere (unless you are persistently storing these hashcodes in, for example, a database) – jtahlborn Sep 05 '12 at 15:28
  • Well, removing one field means to shrink the space where the hash function is mapping the objects, but this is not the problem... unfortunately the hash, in my case, is used as id in somewhere on the client side (with this function). – Hamal000 Sep 05 '12 at 15:34
0

If you're using Lombok:

There's a Lombok Annotation @EqualsAndHashCode that overrides hashCode and equals to be based on your object's non-static, non-transient properties. Thus for the below, if you compare two Employees created with the same name, age, and salary, they will evaluate to true when compared.

@EqualsAndHashCode
public class Employee {

    private String name;
    private int age;
    private int salary;
}

If you have properties you don't want included you can mark them with @EqualsAndHashCode.Exclude.

@EqualsAndHashCode
public class Employee {

    private String name;
    @EqualsAndHashCode.Exclude
    private int age;
    @EqualsAndHashCode.Exclude
    private int salary;
}

You can also specifically include fields with the following:

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {

    @EqualsAndHashCode.Include
    private String name;
    @EqualsAndHashCode.Include
    private int age;
    private int salary;
}

Lombok Annotation Documentation: https://projectlombok.org/features/EqualsAndHashCode

Examples come from: http://www.javabyexamples.com/delombok-equalsandhashcode

tfishbone
  • 1
  • 1