That's in fact exactly what lombok does:
equals will check if the incoming object's class is the same as its
own class. This makes it fully polymorphic, and you can in fact chain
equals calls by using the @EqualsAndHashCode(callSuper=true)
mechanism. The documentation states that you should be careful as not
all equals methods are implemented correctly, but it works fine if the
entire hierarchy all have lombok-generated equals methods. (in code,
lombok does this: if ( getClass() != o.getClass() ) return false; -
see the pre/post example on the features pages.
Immutable classes should probably be final, yes. However, it would be
rather overly presumptuous for lombok to toss a 'final' on your class
just because you're using @Data on a class with all-final fields. It
would reduce flexibility, it doesn't really reduce any boilerplate
("final" is 6 characters, and conveys important semantic information,
so it fulfills neither of the requirements to be boilerplate: It is
neither overly long, nor pure noise), and it would make lombok that
much more non-intuitive: If you don't already know that lombok semi-
magically makes your classes final if you @Data them when they contain
all-final fields, you'd never jump to that conclusion.
So, just keep manually writing 'final' in there:
public final @Data class Point { private int x, y; }
At some point an @Immutable annotation may become popular. It's a bit
beyond lombok's current technical abilities to add this anytime soon,
though. Also, there are issues of practicality involved:
- A file is immutable in the technical sense (in that its one 'name'
field is final), but as a practical sense, files aren't immutable at
all: They represent a file, and if I call '.delete()' on it, obviously
the state represented by the file object changes. This is endemic for
any 'pointer'-like object. If we call such objects immutable, then
immutable is relatively useless - the only practical consideration you
can give them, is that making defensive copies is useless. You can't
memoize any of the method calls (memoize = remember the result for a
given input, and cache it, automatically), and you can't streamline
aspects of its execution because the code isn't guaranteed to be side-
effect-free either.
So, let's try this again but this time with @SideEffectFree and
@Memoizable, which means, respectively: Calling this method NEVER
causes any state changes (example: .toLowerCase() on String, or .get()
on a map, mutable or not, as long as it isn't an access-indexed
linkedhashmap), and: If called on the same object with the same input,
the result will never change (again, .toLowerCase() would qualify for
that one). These are more practical:
Not checking the result of a @SideEffectFree method, or creating one
with a void return type, is pointless. Warn - it's neccessarily a bug.
This would catch "bigInteger.plus(otherBigInteger);" being erroneously
used as a statement to change 'bigInteger' (it doesn't change
anything; it returns a new BigInteger with the result).
@SideEffectFree, and especially @Memoized can be aggressively
optimized by the JVM.
On the flip side, these bring their own issues:
- should you be able to opt out of a compile-time SideEffectFree
check? Logging is obviously a side-effect, but you should probably be
able to stick special annotations on the various log method that say:
I know this LOOKS like a side-effect, but treat it as if its side-
effect-free.
- Should memoizable/sideeffectfree be an implementation detail (THIS
particular method so happens to be sef/memoizable, but methods
overriding this one don't have to be - also means sticking memoizable
or sef on an abstract method is pointless, kind of like sticking
'synchronized' on those), or should it strictly inherit: That way you
can create immutable hierarchies, for example. immutability and object
hierarchies are problematic in java today, as you can't enforce
immutability on your children. On the other hand, making it a part of
the interface requires major changes to the JVM and verifier. It also
moves sef/memoizable from the 'compiler, please double-check I didn't
mess up and accidentally introduced something mutable in here' to the
'compiler, you MUST ensure this class doesn't actually change
anything, my code depends on it', which would make implementing it
much, much more difficult.
- Should SEF and Memoized by rolled into one annotation? Memoized
isn't necessarily SEF, but a non-SEF memoizable isn't very practical;
caching the value would stop the side-effect, so the only practical
thing you can do is call the memoizable anyway, but already continue
the main flow in the CPU pipeline as you already know what the result
is going to be. SEF also doesn't mean memoizable. HashMap.get(key) is
SEF, but obviously not memoizable. So, we appear to need: "SEF" and
"SEFAndMemoizable", but clearly those need better names. How many java
programmers are even going to understand these concepts?
- There are lots of borderline classes where it's not at all obvious:
I already mentioned java.io.File, but think about java.util.Random:
grabbing random values technically changes the state of the Random
object, but in practice, especially for thread-safe implementations of
it, Random appears to be stateless. You get a random number when you
call .next() on it, regardless of what other code has been doing to
that object in the mean time. Yet another argument for allowing a
programmer to override what the compiler can infer.
On Aug 5, 10:52 am, Peter Becker <
peter.becker...@gmail.com> wrote:
> v6ak wrote:
> >
http://java.sun.com/javase/6/docs/api/java/lang/Object.html#equals%28...
> >
http://java.sun.com/javase/6/docs/api/java/lang/Object.html#hashCode%...