5

I am reading about generics, and I learned that they don't allow the use of instanceof. However, the following code compiles:

WeakReference<String> ps = new WeakReference<>(new String());
if (ps instanceof WeakReference<String>) {
    System.out.println("Ok");
}

Can anyone clarify what I don't understand?

Abra
  • 19,142
  • 7
  • 29
  • 41
mike
  • 1,670
  • 11
  • 21
  • Enter the following into the "Search..." box at the top of this Web page: _[java] generics instanceof_ Or just go to: https://stackoverflow.com/questions/1570073/java-instanceof-and-generics – Abra Mar 04 '23 at 07:25
  • If I cast ps to Object, then instanceof does generate error. But above code does compile. – mike Mar 04 '23 at 07:31

3 Answers3

6

Checking if a WeakReference<String> is a WeakReference<String> does not require the runtime to check anything regarding generic types. It is trivially true, after all.

More meaningful/useful examples would be:

List<String> x = new LinkedList<>();
if (x instanceof ArrayList<String>) {
    System.out.println("Ok");
}
List<Integer> x = new LinkedList<>();
if (x instanceof ArrayList<? extends Number>) {
    System.out.println("Ok");
}

In both of these cases, the "generics parts" can be checked all at compile time. It is only when you need to determine generics at runtime that is problematic, because the runtime does not know about generics. An example of this would be:

List<?> x = new LinkedList<Integer>();
// this does not compile because the runtime will need to check if the "?" is String, which it can't.
if (x instanceof ArrayList<String>) {
    System.out.println("Ok");
}

According to the spec, the type needs to be "downcast-compatible" with the expression:

The RelationalExpression must be downcast compatible with the ReferenceType (§5.5), or a compile-time error occurs.

And that means:

If an expression can be converted to a reference type by a casting conversion other than a narrowing reference conversion which is unchecked, we say the expression (or its value) is downcast compatible with the reference type.

So a rule of thumb is that if (T)foo gives you an unchecked warning, that means foo instanceof T does not compile.

Note that the spec didn't always say this. The Java 8 spec said:

It is a compile-time error if the ReferenceType mentioned after the instanceof operator does not denote a reference type that is reifiable (§4.7).

Which would have made instanceof WeakReference<String> invalid in all situations.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
3

In Java, Generics1,2,3 uses type parameters. The code in your question uses type arguments and not type parameters. Similar to method parameters vs method arguments as explained in Difference between arguments and parameters in Java.

Even if you replace the String with the ? wildcard, the code still compiles, i.e.

WeakReference<?> ps = new WeakReference<>(new String());
if (ps instanceof WeakReference<?>) {
    System.out.println("Ok");
}

This is because the same generic argument is being used, i.e. ?

If you replace the arguments with parameters, then the code will not compile, for example:

import java.lang.ref.WeakReference;

public class TestClaz<T> {

    public static <T> void main(String[] args) {
        WeakReference<T> ps = new WeakReference<>(new String());
        if (ps instanceof WeakReference<T>) {
            System.out.println("Ok");
        }
    }
}

Here T is a type parameter (and not a type argument) and my IDE4 gives the following compiler error:

Cannot infer type arguments for WeakReference<>

This part of the code does not compile:

WeakReference<T> ps = new WeakReference<>(new String());

If I change that line to:

WeakReference<T> ps = new WeakReference<T>(new String());

Then the compiler error becomes:

The constructor WeakReference<T>(String) is undefined

Now I can add a cast to T, as in:

WeakReference<T> ps = new WeakReference<T>((T) new String());

and then the code compiles but with the following [compiler] warning:

Unchecked cast from String to T

In other words, the compiler cannot know whether String can be cast to T because, at runtime, T could be any class and not necessarily one that can be cast to String, hence the compiler cannot verify that String can be cast to T.

1 Generics lesson in Oracle's Java tutorials.
2 Generics bonus lesson in Oracle's Java tutorials.
3 Generics guide from Oracle's Java documentation.
4 My IDE is Eclipse.

Abra
  • 19,142
  • 7
  • 29
  • 41
1

instanceof will be unable to distinguish between WeakReference<String> and WeakReference<Integer>. Because these types (String and Integer) are erased during compilation (hence the name "type erasure") and at runtime it will only know that it is a WeakReference containing something of type Object.

In Java generics are a way of avoiding the need for casting when you retrieve an object from a generic type (in earlier versions you had to cast a lot). But they don't really exist at runtime.

Here is the Java spec on the topic: https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6

Mat
  • 202,337
  • 40
  • 393
  • 406
AminM
  • 822
  • 4
  • 11