...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
Yes, there is. It will be fully run-time solution (no compile-time type checking), but it's worth it (you will get an early crash on first attempt to insert an object with wrong type).
Usage:
You can do the same for LinkedList and any other list type.