Now we run into trouble at run-time, because names contains something that isn't an instanceof String.

Presumably, if you want names to contain only String, you could perhaps still use a raw type andmanually check everyadd yourself, and then manually cast to String every item from names. Even better, though is NOT to use a raw type and let the compiler does all the work for you, harnessing the power of Java generics.

See also

How's a raw type different from using <Object> as type parameters

The following is a quote from Effective Java 2nd Edition, Item 23: Don't use raw types in new code:

Just what is the difference between the raw type List and the parameterized type List<Object>? Loosely speaking, the former has opted out generic type checking, while the latter explicitly told the compiler that it is capable of holding objects of any type. While you can pass a List<String> to a parameter of type List, you can't pass it to a parameter of type List<Object>. There are subtyping rules for generics, and List<String> is a subtype of the raw type List, but not of the parameterized type List<Object>. As a consequence, you lose type safety if you use raw type like List, but not if you use a parameterized type like List<Object>.

To illustrate the point, consider the following method which takes a List<Object> and appends a new Object().

void appendNewObject(List<Object> list){
list.add(newObject());}

Generics in Java are invariant. A List<String> is not a List<Object>, so the following would generate a compiler warning:

See also

How's a raw type different from using <?> as a type parameter?

List<Object>, List<String>, etc are all List<?>, so it may be tempting to just say that they're just List instead. However, there is a major difference: since a List<E> defines only add(E), you can't add just any arbitrary object to a List<?>. On the other hand, since the raw type List does not have type safety, you can add just about anything to a List.

The compiler did a wonderful job of protecting you from potentially violating the type invariance of the List<?>! If you had declared the parameter as the raw type List list, then the code would compile, and you'd violate the type invariant of List<String> names.

If it's unsafe, why is it allowed to use a raw type?

Here's another quote from JLS 4.8:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

Effective Java 2nd Edition also has this to add:

Given that you shouldn't use raw types, why did the language designers allow them? To provide compatibility.

The Java platform was about to enter its second decade when generics were introduced, and there was an enormous amount of Java code in existence that did not use generics. It was deemed critical that all this code remains legal and interoperable with new code that does use generics. It had to be legal to pass instances of parameterized types to methods that were designed for use with ordinary types, and vice versa. This requirement, known as migration compatibility, drove the decision to support raw types.

In summary, raw types should NEVER be used in new code. You should always use parameterized types.

Are there no exceptions?

Unfortunately, because Java generics are non-reified, there are two exceptions where raw types must be used in new code:

See also

is that on the left hand side, you are using the generic type List<String> where on the right side you are using the raw type LinkedList. Raw types in Java effectively only exist for compatibility with pre-generics code and should never be used in new code unless you absolutely have to.

Now, if Java had generics from the beginning and didn't have types, such as LinkedList, that were originally created before it had generics, it probably could have made it so that the constructor for a generic type automatically infers its type parameters from the left-hand side of the assignment if possible. But it didn't, and it must treat raw types and generic types differently for backwards compatibility. That leaves them needing to make a slightly different, but equally convenient, way of declaring a new instance of a generic object without having to repeat its type parameters... the diamond operator.

As far as your original example of List<String> list = new LinkedList(), the compiler generates a warning for that assignment because it must. Consider this:

List<String> strings =...// some list that contains some strings// Totally legal since you used the raw type and lost all type checking!List<Integer> integers =newLinkedList(strings);

Generics exist to provide compile-time protection against doing the wrong thing. In the above example, using the raw type means you don't get this protection and will get an error at runtime. This is why you should not use raw types.

// Not legal since the right side is actually generic!List<Integer> integers =newLinkedList<>(strings);

The diamond operator, however, allows the right hand side of the assignment to be defined as a true generic instance with the same type parameters as the left side... without having to type those parameters again. It allows you to keep the safety of generics with almost the same effort as using the raw type.

I think the key thing to understand is that raw types (with no <>) cannot be treated the same as generic types. When you declare a raw type, you get none of the benefits and type checking of generics. You also have to keep in mind that generics are a general purpose part of the Java language... they don't just apply to the no-arg constructors of Collections!