The story of equals()
combines several unfortunate design decisions that are now impossible to change, and that every Java user just has to live with one way or another: it is deeply woven into the semantics of the standard container types, it is automatically inherited by every user-defined class but with a behaviour that is usually not what you want, and defining it without also defining hashCode()
silently results in undefined behaviour (i.e. the language imposes certain requirements on you but has no way of warning you if they are unsatisfied.)
You can get along by simply never defining the method IF you never use such objects anywhere that would expose the unwanted behaviour. But since software development is a cooperative game, this requires an iron-clad, no-exceptions, disciplined rule of "Absolutely no Foo widgets in Set
s, ever!!" that everyone involved with the code must know and follow 100%.
It can be worthwhile to impose such a rule in order to gain some performance, just as with other very fundamental principles of coding - e.g. "No null
return values, ever!" is a common strategy to save a lot of checking for null
. But this depends on how much you really profit from the small simplification for each class. The downside is that you have added another unspoken assumption that all users of your class must know and follow, or silently suffer from undefined behaviour.
...the narrow answer to your question is, when you upcast an object into Object, then necessarily you have to downcast it to the specific type later -- unless you're going to use the Object methods like toString, or else do an end-run around the compiler's type checking with runtime introspection (e.g. Reflection).
When you upcast an object (e.g. something.setAsObject("a string")
), you lose type information about that object. You can get it back in various ways, but generics is how to carry the type on for the compiler to know what to expect later, and doesn't require the downcast back to what it was before.
...
Java generics open the door for parametric polymorphism, which is a different type of polymorphism than we usually talk about in OO. It allows for creating "container" or "wrapper" types that add behaviors independent of underlying types. As wikipedia puts it, " a function or a data type can be written generically so that it can handle values identically without depending on their type"
For example, for a List<T>
, what the <T>
says is that a list works the same regardless of whether it's a List<String>
or a List<Integer>
. You can perform the same operations, and when you're done you can retrieve objects of whatever type the list contains.
So, in a funny way, generics allow a type to be unaware of the underlying type. A List<T>
doesn't know, doesn't care, what type it contains. An Observer<T>
doesn't know, doesn't care, what type it's observing. It just provides certain semantics on top of that type.
This allows the code to avoid a downcast. Downcasts are viewed as bad in OO, because they raises the possibility of a runtime class cast exception -- you lose the benefit of type-checking by the compiler.
In your example, making your Observer interfaces generic is saying, "I don't care what type I'm observing, I'm going to provide this Observer mechanism on top of it." Seems like an apt application of generics.
(For what it's worth, when you get into the business of combining parametric polymorphism (i.e. container types that add behaviors) with chained or fluent interfaces, you can create some really powerful and elegant programmatic APIs.)
Best Answer
Ignoring the erasure issues mentioned in the comments, there are other issues with
equals
that might make a general "type safe" implementation problematic. The main one is how to test for the compatibility of your type T. Let's say you have a class Foo and a subclass SpecialFoo.You propose this:
public static <T> boolean equals(T a, T b)
The classic question is, does T have to be an exact match? In other words, if a is a Foo, and b is a SpecialFoo, can they be equal?
The answer is, "it depends", and is domain specific. You'll see religious wars online about this.
For example, you'll see two different ways to test type compatibility in the typical
public boolean equals(Object other)
method.this.getClass() == other.getClass()
, exact matchother instanceof Foo
subclasses are o.k.I recommend you check out Effective Java for more extensive discussion.