3

I'm reading Effective Java 2nd edition from Joshua Bloch, item 25 (page 122). As you read further into the chapter, you get to a point where the author writes the following code :

// Naive generic version of reduction - won't compile!
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
    E[] snapshot = list.toArray(); // Locks list
    E result = initVal;
    for (E e : snapshot)
    {
        result = f.apply(result, e);
    }
    return result;
}

Then the author states that the compiler won´t compile this because you need to add an explicit cast to the line where is the assignment E[] snapshot = list.toArray();, resulting on this E[] snapshot = (E[]) list.toArray();, and after this you well get a warning saying that there is [unchecked] unchecked cast.


Q1: I know that the book was taking into account changes up to Java 6 (and we are at Java almost 8 right now). However, I wrote the same method, a get the same error from the compile. This is because I am required to add the explicit cast. There is however no warning. So what is that warning about?


Q2: The author states the following method will work but it turns out that it isn't type safe.

With minor modifications, you could get it to throw a ClassCastException on a line that doesn't contain an explicit cast.

Okay, I understand that... but how can I get it to throw a ClassCastException?


I leave this post with a ready to run example, if you want to check the things for yourselves:

import java.util.Arrays;
import java.util.List;

public class Main
{

    public static void main(String[] args)
    {
        List<Integer> ints = Arrays.asList(10,20,30);
        Integer result = reduce (ints,new Function<Integer>() {

            @Override
            public Integer apply(Integer arg1, Integer arg2)
            {
                return arg1 + arg2;
            }
        },0);
        System.out.println(result);
    }

    static <E> E reduce(List<E> list, Function<E> f, E initVal)
    {
        E[] snapshot = (E[]) list.toArray(); // Locks list
        E result = initVal;
        for (E e : snapshot)
        {
            result = f.apply(result, e);
        }
        return result;
    }

    interface Function<T>
    {
        public T apply(T arg1, T arg2);
    }
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Victor
  • 3,841
  • 2
  • 37
  • 63
  • 1
    I get a warning. Might be that you have that particular warning turned off in your IDE? – awksp May 17 '14 at 15:58
  • 1
    @LuiggiMendoza I did read the question. OP said there was no warning. I'm saying that I get one. Question 1 is why that warning was there and/or what it means, and I know I didn't answer that. – awksp May 17 '14 at 16:01
  • 1
    @user3580294 yes, I misread that part of the question. Not sure which compiler OP uses and/or what settings he/she may have. – Luiggi Mendoza May 17 '14 at 16:10
  • Yes i am compiling within my IDE, and it ignores silently that warning. A compilation from command line, reveals the same that the author of the book states. – Victor May 17 '14 at 18:17

4 Answers4

1
E[] snapshot = list.toArray();

list.toArray() returns Object[] instead of E[]. So you need an explicit cast there. But the presence of the explicit (or dynamic) cast like that means that compiler could not guarantee the type-safety there. Instead your are telling the compiler that everything is fine and it will actually be an E[] at runtime. So, it gives you a warning about it saying - unchecked cast. Note, that if the compiler was able to guarantee the type-safety there, then it would be a static/checked cast (performed implicitly by the compiler itself) there, instead.

Bhesh Gurung
  • 50,430
  • 22
  • 93
  • 142
  • 1
    What happened with the answer for question 2? – Luiggi Mendoza May 17 '14 at 16:12
  • I am not quite sure what exactly the *minor modification* stands there for. But you can get the effect by replacing the line with something like `E[] snapshot = (E[]) new Double[]{0d, 1d};`. – Bhesh Gurung May 17 '14 at 16:32
  • 1
    @BheshGurung I believe the *minor modification* is what OP's second question was about. And "With minor modifications, you could get it to throw a `ClassCastException` on a line that **doesn't contain an explicit cast**." – awksp May 17 '14 at 16:40
  • @user3580294: So, you don't think that counts as a *minor modification*? – Bhesh Gurung May 17 '14 at 16:43
  • 1
    @BheshGurung That is a minor modification, but not one that fits Bloch's statement. He was talking about a modification that would result in a `ClassCastException` on a line **without an explicit cast**. Pretty sure what you got there is an explicit cast. – awksp May 17 '14 at 16:45
  • @user3580294: May be you are misinterpreting it. It says - *... on a line that doesn't contain an explicit cast.* Also, if could make it to throw an CCE without any explicit cast like that than don't you think that it would defeat the whole purpose of generics (i.e. **type-safety**)? – Bhesh Gurung May 17 '14 at 16:47
  • @BheshGurung That's exactly what I said. My point was that your modification would throw a `ClassCastException` on the line with an explicit cast, no? And I believe that that was Bloch's point; the very next page he has a better solution that uses `List` – awksp May 17 '14 at 16:49
  • @user3580294: Nope, it will throw it when it needs an `Integer`, which is at a different place. – Bhesh Gurung May 17 '14 at 16:50
  • @BheshGurung Oh, that's interesting. I thought that `Double[]` couldn't be cast to `Integer[]`. Evidently that is the case though. – awksp May 17 '14 at 16:51
  • May be you thought that `E[]` would turn into `Integer[]` there. That's not true. – Bhesh Gurung May 17 '14 at 16:52
  • @BheshGurung Indeed I did. How does that conversion take place then? – awksp May 17 '14 at 16:55
  • 1
    Generics is compile type only feature and all the generic info are removed by the type-erasure. When it happens without any bound like that, that `E` will turn into `Object`. – Bhesh Gurung May 17 '14 at 16:57
  • @BheshGurung Right, I knew that... How stupid of me to forget. I thought that the information would be substituted at runtime... – awksp May 17 '14 at 16:59
  • Thanks for the energy in the answers. I will try to point out what i start to think. First, if you try to write ``Integer[] a = (Integer[]) new Double[]{0d, 1d};`` the compiles won't let you go because the is incompatible types conversion. And as Bhesh states, E is transformed to Object, and E[] becomes Object[], so the fact is that those lines, in execution times becomes, ``Object[] a = (Object[]) new Double[]{0d, 1d};``. And a classCastException will arises when i try to cast an element of the array to Integer explicity. But... – Victor May 17 '14 at 19:17
  • STill don't see why this ``E[] array = (E[]) list.toArray();``, that becomes in runtime ``Object[] array = (Object[]) list.toArray()`` (worth to mention that), generates warning. And still doesn't see a coherent way to make it fail at runtime... please try one more time! – Victor May 17 '14 at 19:18
1

Question one: Why is E[] snapshot = (E[]) list.toArray(); warning about unsafe check?

The reason is that, in java, arrays keep their type information, while generics get erased. At runtime all List<String>, List<Dog> and List<NuclearBomb> are effectively List<Object>.

Therefore:

List<E> list becomes List<Object>

And

E[] array = (E[]) list.toArray();

Is unsafe.

Question two: How can that fail at runtime?

If java lets you do

E[] array = (E[]) list.toArray();

with a list of Objects, it will also let you do:

String[] array = (String[]) list.toArray();

Since strings are objects after all. That line would compile (with a warning) and on runtime you'd get a cast exception.

Pablo Fernandez
  • 103,170
  • 56
  • 192
  • 232
  • ummmmm, sorry my good friend but i didn't catch up. About the first, i understand the type erasure process that happens to remove the type information in order to leave code in java that could operate with legacy code. But why is unsafe? – Victor May 17 '14 at 18:53
  • Also List after type erasure becomes a raw List i think, or at least Joshua states that. The list doesn't becomes List, instead the internal parametrized symbols of the List becomes Object, but the List, as a whole, becomes a List. Please readers, correct me if i am wrong. And please, help me to understand why is unsafe? Thanks! – Victor May 17 '14 at 19:01
  • i Understand the second answer, but i see no logic on doing that on a code that in first place is about the be generic.... i don't understand Joshua intentions perhaps... also, i would remind that the he states the problem can be raised on a line that "doesn't contain an explicit cast". – Victor May 17 '14 at 19:04
  • 1
    I just has understand the first and second example. From the first is unsafe, because at compile time the compiler don´t know what type will be E, so the compiler see something like this ``E = (E) (Object[])``, and knowing that ``AnyType`` perphaps will not be at runtime exactly ``Object`` and therebefore no will be ready for storing values that are not compatible with the actual type parameter applied at runtime, say, al kind of Objects that stays between Object and AnyType in the class hierarchy. – Victor May 18 '14 at 00:48
  • And for the second, the first leads to the answer suposing that the actual type parameter applied at runtime stands, in the class hierarchy, between the concrete type used to the array named ``array`` and do the cast, and the class Object, that, in fact is the type of array that the expression ``list.toArray()`` returns. – Victor May 18 '14 at 00:52
  • But knowing the truth behinds the walls of the compilations, we know that the expression ´´[] = ([]) (Object[])´´ becomes ´´Object[] = (Object[]) (Object[])´´, why the compiler still states about?, if at runtime will be safe after all. Or i really don't get nothing jeje – Victor May 18 '14 at 01:14
1

I think the compiler when it sees an explicit cast raises a warning where generics and arrays are combined . if you remember the earlier example in Bloch effective java page 117

return (T[]) Arrays.copyOf(elements, size, a.getClass());

In the above scenario also the compiler generates a warning : [unchecked] unchecked cast. Although we are pretty much sure at the runtime that array returned will always be of type T. But it is suppressed by using @SuppressWarnings annotation.

kunal
  • 93
  • 1
  • 2
  • 7
0

For Question2, I try to understand the author by codes below.

caller:

List<String> list = Arrays.asList("string");
reduce(list, new Function<String>() {
    @Override
    public String apply(String arg1, String arg2) {
        return "";
    }
}, "");

the "reduce" mothod:

static <E> E reduce(List<E> list, Function<E> f, E initVal) {
    // /*
    List<E> snapshot;
    synchronized (list) {
        snapshot = new ArrayList<>(list);
    }
    snapshot.set(0, 123);    // compile error here
    // */

    /*Object[] objs = list.toArray();
    objs[0] = 123;  // will cause ClassCastException on runtime
    E[] snapshot = (E[]) objs;*/
    E result = initVal;
    for (E e : snapshot) {
        result = f.apply(result, e);
    }
    return result;
}

Maybe it's not such "With minor modifications", but it is an appropriate expaination.

HuoYan
  • 1
  • 1