So, to cite the textbook example, say you are implementing a Stack<E>. It will probably have the methods:
public void push(E element);
public E pop();
and, for convenience: public void pushAll(Iterable<? extends E> elements);
public void popAll(Collection<? super E> destination);
The pushAll has "? extends" because the elements Iterable will "produce" elements for the stack. The popAll has super because the destination Collection will "consume" elements from the stack. It is not that hard, is it? Let's note that guard-of-terra is talking about proficiency, not mere familiarity. I believe reading "Effective Java" is a nice way to get closer to the proficient level.Let it be noted that this whole mess exists because generics in Java were implemented with type erasure so their introduction wouldn't break legacy code. I personally think this was a bad idea, but it does show that when a language is evolving, there are a bunch of constraints the designers must be aware of.
[1] http://www.amazon.com/Effective-Java-Edition-Joshua-Bloch/dp...
I think the mistake the Java designers made here was going with use-site variance instead of definition-site variance.
On the other hand, sometimes I think an unsound type system with List<String> being a subtype of List<Object> would be better than what we have today, for pragmatic reasons. Of course, I think this makes me a non-type-theorist, as what I'm saying is considered heresy in some circles [1].
[1] http://lambda-the-ultimate.org/node/4377 (search for unsound).
On the other hand, if your structure is immutable it would probably work.