๐Ÿ–• welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.


background

Yesterday, in the forum encountered such a problem, on the code:

public class GenericTest {
    / / method
    public static <T extends Comparable<T>> List<T> sort(List<T> list) {
        return Arrays.asList(list.toArray((T[]) new Comparable[list.size()]));
    }

    / / method 2
    public static <T extends Comparable<T>> T[] sort2(List<T> list) {
        // There is no error here
        return list.toArray((T[]) new Comparable[list.size()]);
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        // Method 1 calls fine
        System.out.println(sort(list).getClass());
        // Method two call error, error hereSystem.out.println(sort2(list).getClass()); }}Copy the code

There are four signs of this problem:

(1) Method 1 is completely normal;

(2) Method 2 call error;

System.out.println(sort2(list).getClass())); ToArray ((T[]) new Comparable[list.size()]); This line;

[Ljava.lang.Comparable] cannot be cast to [ljava.lang. Integer;

How’s that๏ผŸ Do you have an answer in mind? Type erasure? How to wipe? Friction friction?

To solve

ToArray ((T[]) new Comparable[list.size()]); That’s right, and if you want to report an error you should report an error in both methods.

With the mentality of never giving up and never giving up, Tong Brother did a lot of experiments and finally came to the essence of generics. Listen to me.

episode

First, we need to understand that arrays in Java do not support downward casting, but they can be rolled over if they are of that type. See the following example:

public static void main(String[] args) {
    Object[] objs = new Object[]{1};
    // Type conversion error
// Integer[] ins = (Integer[]) objs;

    Object[] objs2 = new Integer[]{1};
    / / is not an error
    Integer[] ins2 = (Integer[]) objs2;

}

Copy the code

Type erasure

Generics in Java are fake generics, which are only valid at compile time. There is no concept of generics at runtime. Here’s a simple example:

public static void main(String[] args) {
        List<String> strList = Arrays.asList("1");
        List<Integer> intList = Arrays.asList(1);

        // Print: true
        System.out.println(strList.getClass() == intList.getClass());
    }
Copy the code

It can be seen that the types of the two lists are the same. If you think this example is not convincing enough, I will give you an excessive example:

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    List<String> strList = new ArrayList<>();

    Method addMethod = strList.getClass().getMethod("add", Object.class);
    addMethod.invoke(strList, 1);
    addMethod.invoke(strList, true);
    addMethod.invoke(strList, new Long(1));
    addMethod.invoke(strList, new Byte[]{1});

    // Print: [1, true, 1, 1]
    System.out.println(strList);
}
Copy the code

Look, I can throw anything I want into a List of strings, okay? !

So generics in Java are fake, they don’t exist at runtime.

Return to the chase

I get it, I get it, I get it, I get it, I still can’t get through life, no, I still can’t solve this problem.

Well, it looks like

Let’s look at another simple example:

Generictest2.java (source code)
public class GenericTest2 {
    public static void main(String[] args) {
        System.out.println(raw("1"));
    }

    public static <T> T raw(T t) {
        returnt; }}// genericTest2.class (decompress)
public class GenericTest2 {
    public GenericTest2(a) {}public static void main(String[] args) {
        System.out.println((String)raw("1"));
    }

    public static <T> T raw(T t) {
        returnt; }}Copy the code

Well, there seems to be something going on, there is a constructor after decompilation.

Uh, yeah. What else?

System.out.println((String)raw(“1”)); This sentence added a String strong.

That’s the point. There’s no such thing as generics at runtime, so raw() actually returns Object, but the caller knows that I want String, so I know how to force it.

Let’s take another extreme example:

Generictest2.java (source code)
public class GenericTest2 {
    public static void main(String[] args) {
        System.out.println(raw("1"));
    }

    public static <T> T raw(T t) {
        return (T)new Integer(1); }}// genericTest2.class (decompress)
public class GenericTest2 {
    public GenericTest2(a) {}public static void main(String[] args) {
        System.out.println((String)raw("1"));
    }

    public static <T> T raw(T t) {
        return new Integer(1); }}Copy the code

If you look closely, you can see that the strong (T)new Integer(1) in the raw() method becomes new Integer(1), and the strong is erased. In fact, at runtime, the T here becomes Object. All types are subclasses of Object, so there is no need for strong casts.

If (String)raw(“1”) returns, the caller knows that the type is String.

Java.lang. Integer cannot be cast to java.lang.String, because raw() returns an Integer and the cast fails.

Okay, so that’s the basic idea.

What about generic classes?

All of our examples above are generic methods. What about generic classes?

Again, let’s look at an example:

Generictest3.java (source code)
public class GenericTest3 {
    public static void main(String[] args) {
        System.out.println(new Raw<String>().raw("1")); }}class Raw<T> {
    public T raw(T t) {
        return (T)new Integer(1); }}// genericTest3. class
public class GenericTest3 {
    public GenericTest3(a) {}public static void main(String[] args) {
        System.out.println((String)(new Raw()).raw("1")); }}class Raw<T> {
    Raw() {
    }

    public T raw(T t) {
        return new Integer(1); }}Copy the code

As you can see, it behaves exactly the same as the generic method. Java.lang.Integer cannot be cast to java.lang.String.

conclusion

Generics in Java are only valid at compile time. At run time, only the caller knows what type is needed, and the caller does the cast himself after calling the generic method.

So, the problem is not to ask the caller, but to ask the caller, your ya is how to call? !

To solve the opening

Let’s take the opening question for convenience.

// generictest.java
public class GenericTest {
    / / method
    public static <T extends Comparable<T>> List<T> sort(List<T> list) {
        return Arrays.asList(list.toArray((T[]) new Comparable[list.size()]));
    }

    / / method 2
    public static <T extends Comparable<T>> T[] sort2(List<T> list) {
        // There is no error here
        return list.toArray((T[]) new Comparable[list.size()]);
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        // Method 1 calls fine
        System.out.println(sort(list).getClass());
        // Method two call error, error hereSystem.out.println(sort2(list).getClass()); }}Copy the code


> is the same as

.

So, let’s extend that the called is completely insensitive, it can only try to get the type it knows, like in this case, it can only try to get Comparable, and if it’s

it gets Object.

Therefore, method two returns the true Comparable[] type, which is fine as the caller.

However, the caller knows that I want an Integer[], because the list is an Integer, so it should return an Integer[], so I force it, and then I get an error.

Is that true or not? Let’s look at the decompiled code:

// generictest.class (decompile)
public class GenericTest {
    public GenericTest(a) {}public static <T extends Comparable<T>> List<T> sort(List<T> list) {
        return Arrays.asList(list.toArray((Comparable[])(new Comparable[list.size()])));
    }

    public static <T extends Comparable<T>> T[] sort2(List<T> list) {
        Comparable[] is used, so the Comparable[] type is returned
        return (Comparable[])list.toArray((Comparable[])(new Comparable[list.size()]));
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList();
        list.add(1);
        list.add(2);
        System.out.println(sort(list).getClass());
        // Array downcast failedSystem.out.println(((Integer[])sort2(list)).getClass()); }}Copy the code

As you can see, it’s exactly the same as our analysis.

In a word, for a lifetime

Generics in Java are only valid at compile time. At run time, only the caller knows what type it needs, and the caller does the cast after calling the generic method. The caller is completely unaware and can only try to get the type it knows.

At this point, my mind does not sound the familiar melody, “in a word, a lifetime…” Did you remember today’s sentence?


Welcome to pay attention to my public number “Tong Elder brother read source code”, view more source code series articles, with Tong elder brother tour the ocean of source code.