@EqualsAndHashCode(cached=true)
caching the computed hashCode? The generated methods could look like
public int hashCode() {
if ($hashCode <> 0) return $hashCode;
... do the usual computation
return $hashCode = result;
}
// String.hashCode() works this way.
public boolean equals(Object o) {
// from http://projectlombok.org/features/EqualsAndHashCode.html
if (o == this) return true;
if (o == null) return false;
if (!(o instanceof Square)) return false;
Square other = (Square) o;
// the only new code
if (this.$hashCode <> other.$hashCode
&& this.$hashCode <> 0
&& other.$hashCode <> 0) return false;
... do the usual computation
}
// This could speed up equals in many cases.
// For whatever reason, String.equals doesn't work this way.
public void setName(String name) {
$hashCode = 0;
this.name = name;
}
// Lombok generated setters should invalidate the cache,
// except for fields excluded from EAHC.
// Self-made mutators are "Somebody Else's Problem",
// the main use is for immutable classes, anyway.
What do you think about it?
Regards, Maaartin.
--
You received this message because you are subscribed to the Google
Groups group for http://projectlombok.org/
To post to this group, send email to project...@googlegroups.com
To unsubscribe from this group, send email to
project-lombo...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/project-lombok?hl=en
I don't thing that anything beyond something like this in necessary:
Note that the hashCode doesn't get updated automatically when the object
changes (except by setters generated by Lombok for the class itself).
Use it for immutable objects or make sure that $hashCode=0 is assigned
in each mutator.
> It would also be nice to know how much of a performance improvement this
> nets you.
No idea, maybe it's not worth it. When I wrote hashCode() for immutable
objects manually, I often did it, since it's so easy; but I've never
measured it. It may be a useless premature optimization.
> In particular I'm thinking about immutable classes in which I
> typically use Guava's immutable collections which seem to cache their
> hashcodes already.
AFAIK, only the hashCode of the whole collection gets cached
(RegularImmutableSet.hashCode), not the values for the entries.
> While I think about it this whole thing falls apart if you expose
> mutable collections which is another thing to warn people about.
Whenever you use hashCode with mutable classes you may get a problem.
(continued below)
On 11-03-28 17:43, Reinier Zwitserloot wrote:
> I rather doubt this is worth the hassle. Here are the cons:
>
> (A) Invalidating the hash on mutable classes. This is a big deal - what
> if the superclass has some setters? What if you write your own setter or
> anything else that mutates this class? What if some object you refer to
> in a field contains a reference to something else, that something else
> is mutated, and as a result that ref changed hashCode and thus our class
> should change along with it?
Sure. All of this should invalidate the hashCode, however, that's the
reason for cached=false by default. Obviously, in case you refer to
anything mutable without your control (including the superclass part of
the object), you can't use the caching. When you write any mutator
yourself, you can use the caching but must set $hashCode=0.
> (B) Does this actually net you any serious speed? The only significant
> gain here is if you include a field whose hashCode() takes very long to
> calculate and which doesn't itself cache, *AND* your object is 100%
> immutable, including all refs. How many classes even exist for which
> that applies? Isn't the solution to make THOSE classes cache their
> hashcodes?
I can't tell, but maybe somebody else will.
> j.u.collection stuff doesn't cache hashes for the same
> reason lombok doesn't. It doesn't know it can safely do so.
Actually, any HashSet could cache the hashcodes without any risk. In
case they ever change, the HashSet is already completely broken. But for
the other sets and for values in any Map, I agree.
> (C) It's another parameter. We want to avoid implementing everything
> that seems useful to a vanishingly small subset of users, because once
> we go down that path, @Data is going to end up with 55 parameters. I
> think we can all agree @Data with no parameters is a lot better than
> @Data with 55 parameters but applicable in about 5% more situations than
> @Data currently is.
I agree with this. So forget it unless somebody comes up with a lot of
use cases.
Regards, Maaartin.
--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
Using some creative semi double locks (actually lock free; having 2 threads calculating the hashcode simultaneously is suboptimal but does not result in a wrong answer, therefore, it's fine) should make it free enough to calculate this on demand once (or, in case of multiple threads calling hashCode for the first time virtually simultaneously) 2 or 3 times at most.
I guess the implementation should involve a new annotation (@CacheHashcode?).
Should we put in something for writing your own hashCode algorithm? That's a bit complicated, as the @EqAHC code has to take that into account for generating its 'hey, you should either override both or neither implementation' stuff.
What about enabling this feature when there's the JSR-305 @Immutable
annotation together with @EqualsAndHashCode?
--
I think it should be a property of the @EAHC annotation, default false, don't care about multithreading, use an Integer field for easy uninitialized checks. Don't take mutability into account. Just listen to the property of the annotation.
That's easy to explain and easy to implement.
I like the 'replace 0 by 1' approach.
I like the 'replace 0 by 1' approach.
@Immutable is a bad idea in java. It can't work.
It makes no sense, and whatever incomplete definition you can come up for it is useless (in that you can't use it as an indicator of memoizability, it does not guarantee deterministic results based on parameter input remaining unchanged, it does not imply that calling a method has no side-effects and therefore any code ignoring the return value is by definition a bug – so what's left?).What does 'immutable' mean? That all fields are final? Alright, include a List field that isn't immutable and hashCodes can change.You could add the further restriction that all fields also need to themselves be immutable types, but:(A) someone is going to have to delve into the rt.jar and all other popular java libraries and add @Immutable annotations all over the place. Without them, it'll all fall apart.(B) There's a more general problem that for example many classes have Lists in them, which are actually ImmutableList. ImmutableList (the type) is immutable. List isn't. Now what?Even granting for now that the above isn't an actual problem, you're still not in the clear. I can make a fake mutable field by creating a static IdentityHashMap. Sure, I'm intentionally trying to mess with the system, but I'm just showing you can get around whatever rule you like to come up with, as long as you continue to try and frame the issue as 'immutability'. Immutability is a property of fields. It has zip squat to do with methods. Classes have methods. Therefore, classes cannot possibly be called 'immutable' in practice. It makes no sense.Some cases in point:* java.io.File is as immutable as it gets: 1 field, it's final, and its type is itself immutable (String). And yet, almost all of its methods are NOT memoizable. You can call .length() on it, and then call it again later on, and the value can change. This proves immutable classes no matter how stringent the definition, aren't memoizable (immediately throwing out the notion of caching hashCode on immutables).* any non-final class can't be immutable by any definition. Unless javac enforces it on subclasses, which it won't, because my name isn't Mark Reinhold.* Let's turn it around: What does the presence of hashCode() on a non-immutable class imply? You shouldn't use mutable keys in maps and set's behaviour will be non-sensical, so what's left?
If @Immutable is shorthand for 'memoize hashCode please', that's tantamount to saying we should always memoize it, period.
The only concept I'm willing to encode as an annotation is 'side effect free' (A property of methods, never of fields, unlike immutability which is the reverse),
and even that one is iffy. It has nothing to do with hashcode generation, caching, or immutability though. It doesn't imply memoization.
A class that has only private fields, AND has only SEF methods seems like it should be able to memoize everything, but, nope:Let's say one of the methods (let's call it 'foo') calls another method ('bar') as part of calculating its return value. If 'foo' isn't overridden in a subclass, but 'bar' is, then foo may no longer be memoizable. Therefore, 'foo' would start emitting results based entirely on when the first call with that parameter list happened, which is bad.
I'm a little bit confused on your 2 code snippets. Which one appears to be faster in your caliper tests?
I can foresee adding something like @CacheHashCode(strategy=CachingStrategy.PRECOMPUTE / CachingStrategy.LAZY). I guess we could just add this to @EqualsAndHashCode instead, but I can foresee a @Memoize annotation which will add memoization to any method,
and PRECOMPUTE can still be useful there, at least for methods with no argument lists (or possibly methods where there is a finite list of possible argument values).
Also, it seems reasonable to offer the option for lombok to generate the appropriate boilerplate to precompute/lazily cache a hand-written hashCode method.
Okay, so we have some concerns here:
* @Memoize should maybe be a lombok feature; it has a few parameters. One of them is the strategy (LAZY or PRECOMPUTE). PRECOMPUTE is not legal unless the method has no arguments, or arguments have limited values: booleans, enums, bytes, chars, and shorts will be allowed, but that's it, and I guess a hardcoded limit on the combinatorial explosion (no @Memoizing a method with 3 'char' params). Probably 2^20 max. I can't imagine it's useful to precompute anything bigger than that.
Furthermore, @Memoize has an option to set a maximum cache size. LAZY caches will always run as an MRU (probably LinkedHashMap, I guess?).
This option is not legal on PRECOMPUTE. Also, PRECOMPUTE will not use LinkedHashMap; I guess it should use an array + a function to map all inputs onto a single number from 0-maxcombinatorial.
About enums: Do we try and precompute 'null', or skip it?
About short/byte/etc: Do we also allow Byte, Short, and Character?
If so, do we also memoize the results when calling with 'null'?
About exceptions: Do we memoize the exception?
It would have a funky stacktrace. Or do we just throw the type and message of the exception, losing the cause and breaking if the type thrown does not have a constructor with 1 parameter (String), or do we just not care about it, thus causing an exception on class load for PRECOMPUTE,
and never memoizing the exception for LAZY?* @EqualsAndHashCode will gain an option to memoize hashCode. The parameter is LAZY or PRECOMPUTE; something like @EqualsAndHashCode(cacheHashCode=CacheStrategy.PRECOMPUTE).* If hashCode() is handwritten but @Memoized, @EqualsAndHashCode will not generate an equals method anyway, because it either generates both, or neither.
However, if (cacheHashCode=something) is provided, equals() will automatically call .hashCode() on itself and on the argument and if they don't return the same number, it returns false immediately.