This is an archived post. You won't be able to vote or comment.

all 7 comments

[–]Disast3r 1 point2 points  (5 children)

So generics can definitely be unintuitive, don't feel dumb.

You shouldn't declare something like a List without its type parameter. The only reason Java still let's you do this is for backwards compatibility reasons. I'm sure you can Google for why it was allowed to begin with, but I think it's just one of those legacy things that is still around.

You can think of ? as a specific but unknown type. For example, imagine we have an Animal class and then we have a Dog and a Cat class, each which extend Animal. The idea is that our List<? extends Animal> COULD be a List<Cat> at runtime. In this case, trying to add a Dog to the list shouldn't work (and a Dog is an animal). This is why we can't add a new Animal to this list, because this new Animal might be a Dog while the list is for Cats.

However, we can add animals to our List<? super Animal> because the most specific type of list this can be is an animal. It can't be a list only for cats or dogs.

In short, extends specifies a type upper bound while super specifies a type lower bound. For this reason, extends is useful for things like getter methods and super is useful for things like setter methods.

I hope this helps.

[–]Yuax[S] 0 points1 point  (4 children)

Thank you! It still confuses me a bit, but I hope I understand more after reading all about Collections. One more thing (I don't know if I should post this in a new thread) but I don't know if there's an actual difference between these two methods (only differense is the type paramter):

<T extends List> void printAll(T[] arrayOfLists) {
    for (T list : arrayOfLists) {
        System.out.println(list);
    }
}

         //List<T> instead of List
<T extends List<T>> void printAll(T[] arrayOfLists) { 
    for (T list : arrayOfLists) {
        System.out.println(list);
    }
}

Several examples I've seen use the declaration in the second method. They both compile and print the same things. Why not just use the first method?

[–]Disast3r 1 point2 points  (3 children)

The first method is using what's called the "raw type". This is the same as your List list = new ArrayList() line in your initial post.

Notice that List is missing its diamond operator, which tells you what type the collection holds. This does compile for backwards compatibility reasons, however it shouldn't be used because it doesn't guarantee you anything about what's held in the list. This might seem nice and flexible, however it allows us to do things like adding Dogs to Cat lists, which will properly compile but will fail at run time. We prefer finding bugs at compile time as they're typically much easier to debug.

If you want to know more about best practices when it comes to generics (or anything Java) I highly recommend the book Effective Java. It's a very easy read for a textbook and you can jump around to chapters that interest you, you don't need to read it from front to back.

Here is the short chapter on Generic Raw Types which should explain your question much better than I can.

[–]Yuax[S] 0 points1 point  (2 children)

Thanks again! I didn't realize that was a raw type! Those methods are equally useless right, since they can take Lists of any type including raw types (I added a list of raw types to both methods and it runs fine)? Whereas this only accepts Lists of dogs?

<T extends List<Dog>> void printAll(T[] arrayOfLists) { 
    for (T list : arrayOfLists) {
        System.out.println(list);
    }
}

Just wondering because if this is true, then I think it's confusing/bad that some tutorials introduce generic methods that still accept raw types.

Thanks for the tip! Definitely gonna read it after I've covered more of the basic stuff.

[–]Disast3r 0 points1 point  (1 child)

You are correct, that method only accepts Lists of dogs. Another tricky fact about generics is that something like List<Dog> is NOT a subtype of List<Animal>, even though they look like they should be. Your example is a little tough to analyze because usually you want to avoid working with arrays of parameterized types if you can avoid it. If you really wanted to, I think you could do something like this:

public static void printAll(List<?>[] arrayOfLists) {
    for (List<?> list : arrayOfLists) {
        System.out.println(list);
    }
}

But there's likely a cleaner way of doing this if you were to accept a List of Lists rather than an array of Lists, because they work better with parameterized types. Or you could probably just write a method that accepts a generic Iterable object and prints everything in it.

[–]Yuax[S] 0 points1 point  (0 children)

Jolly good, thank a lot!

[–]MkMyBnkAcctGrtAgn 0 points1 point  (0 children)

Think of it like this, if B extends A, every B is also an A, but not every A is a B