3

Why first line in main does not throw ClassCastException while the second does?

import java.util.function.Function;

class Scratch {

    static <T> T getSomething(Function<Integer, T> fun) {
        return (T) fun;
    }

    public static void main(String[] args) {
        Scratch.<String>getSomething(x -> "hello");
        String something = Scratch.<String>getSomething(x -> "hello");
    }
}
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
Julian Rubin
  • 1,175
  • 1
  • 11
  • 23
  • Can't answer the question, but you're apparently trying to cast a Function to String, which is unlikely to work. There needs to be a call to fun.apply in there somewhere. –  May 31 '19 at 12:39
  • Because you use the result in the second case, but not in the first. – Andy Turner May 31 '19 at 12:40

2 Answers2

2

The difference is because you don't use the result of the method in the first case, but you do in the second.

A cast is an expression, but it's not a StatementExpression. This means that you can't write this:

(String) somethingReturningAString();

but you can write:

String aString = (String) somethingReturningAString();

At compile time, the compiler inserts checkcast instructions where it needs to, and where it can:

  • It can't insert a cast for the first case, so no checking takes place.
  • It can (and has to) insert a cast in the second case, in order to ensure that it's assigning something that's actually a String to a String variable. As such, it checks the cast, and that fails.

It's worth noting that there are some perhaps unexpected cases where a cast isn't strictly necessary, but is inserted. For example:

Scratch.<String>getSomething(x -> "hello").toString();

would fail with a ClassCastException, since it would be transformed to:

((String) Scratch.getSomething(x -> "hello")).toString();

even though Object has a toString() method, and so it could invoke that without a cast.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
1

Generics are only a compile time check (read about type erasure). So at runtime your getSomething() method looks similar to this:

static Object getSomething(Function fun) {
    return fun;
}

Now you see clearly that the first line will never throw an exception

Scratch.getSomething(x -> "hello");

because Function is an Object and thus can be returned without a problem.

The second line however will throw one, because it will look similar to this:

String something = (String) Scratch.getSomething(x -> "hello");

A Function is still an Object so it can be returned from the method, but it is not a String and thus, you get your ClassCastException.

The code compiles fine, because you instruct the compiler that you know what you're doing. You will get an Unchecked cast warning though on this line:

 return (T) fun;

This warning should be an indicator from the compiler to you, the programmer, that it (the compiler) can't be sure that that cast will succeed.

Lino
  • 19,604
  • 6
  • 47
  • 65