11

Why does code alternative(1) compile without warnings, and code alternative(2) produce an "unchecked cast" warning?

Common for both:

class Foo<T> {
    Foo( T [] arg ) {
    }
}

Alternative (1):

class Bar<T> extends Foo<T> {
    protected static final Object [] EMPTY_ARRAY = {};

    @SuppressWarnings("unchecked")
    Bar() {
         super( (T []) EMPTY_ARRAY );
    }
}

Alternative (2):

class Bar<T> extends Foo<T> {
    @SuppressWarnings("unchecked")
    Bar() {
         super( (T []) EMPTY_ARRAY );
    }

    protected static final Object [] EMPTY_ARRAY = {};
}

Alternative (2) produces:

javac -Xlint:unchecked Foo.java Bar.java 
Bar.java:4: warning: [unchecked] unchecked cast
             super( (T []) EMPTY_ARRAY );
                           ^
  required: T[]
  found:    Object[]
  where T is a type-variable:
    T extends Object declared in class Bar
1 warning

This is:

java version "1.7.0_07"
Java(TM) SE Runtime Environment (build 1.7.0_07-b10)
Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode)
Perception
  • 75,573
  • 19
  • 170
  • 185
Johannes Ernst
  • 2,672
  • 2
  • 32
  • 50
  • 2
    I might be blind, but where's the difference? Both the code examples seem the same to me, only ordered differently. – Natix Dec 11 '12 at 22:47
  • 1
    I think he wonders why one alternative produces a warning while the other doesn't? In fact they seem to be equivalent. – Pyranja Dec 11 '12 at 22:48
  • Just to be clear: you're saying that in (2) the warning happens *despite* the `@SuppressWarnings("unchecked")`? – Paul Bellora Dec 11 '12 at 22:57
  • Paul: yes, the only difference seems to be where the static array is declared. – Johannes Ernst Dec 11 '12 at 23:06
  • 2
    Pro-SO tip 1: when talking to people in the comments, use the `@` symbol. This will put a notification in that person's inbox so they see your comment. Pro-SO tip 2: you can type the first few letters after their name in the `@` and press Tab to auto-complete their name. – Brian Dec 11 '12 at 23:09
  • @JohannesErnst Well, its most surprising, but I am able to emulate this behaviour. Very strange indeed. This doesn't occur in Eclipse, because it uses its own compiler, but I'll try this code in NetBeans. – Natix Dec 11 '12 at 23:12
  • 1
    @Natix: Happy I'm not the only one who is mystified. It seems to have been added in JDK 7, all of a sudden warnings show up that didn't in 6. – Johannes Ernst Dec 11 '12 at 23:17
  • +1, btw, this was a great question. – Brian Dec 11 '12 at 23:33
  • 1
    I did take the suggestion and filed a bug with Oracle. I'm afraid there are some other places where JDK 1.7 ignores @SuppressWarnings("unchecked"), too, but I haven't isolated them. That all used to work in 1.6... – Johannes Ernst Dec 11 '12 at 23:41
  • 1
    @JohannesErnst Could you add the report link to the end of your question, that way future users stumbling across this (and all of us) can follow it? – Brian Dec 11 '12 at 23:44
  • 1
    I did file a bug report with Oracle, but it has not shown up on the public website. No idea what to do now. – Johannes Ernst Dec 29 '12 at 23:43
  • 1
    @JohannesErnst: did that bug ever make it to the surface? – Rich May 22 '13 at 20:17
  • Nope, I filed it multiple times with Sun/Oracle, but it never showed up on the bug tracker. Lame. Not sure what else to do. – Johannes Ernst May 22 '13 at 23:31

4 Answers4

4

I'm unable to find anything in the JLS, both the @SuppressWarnings (JLS 9.6.3.5) and unchecked warnings (JLS 5.1.9) sections don't seem to have any issues that could lead to this problem. My guess (without testing your SSCE myself) is that you've found a bug in the compiler. I'd recommend filing a bug report with Oracle and adding the report link to your question.

In short, the order of members in the class should be completely independent about how warnings are processed. It may be an edge case in just the unchecked warning code, or it may be a larger problem.

In the meantime, you can eliminate all of your problems by doing what you should have done in the first place, and dynamically generate the empty array instead of casting an existing one, as outlined in this question.

Edit

I don't see how the linked proposal would work in case of my EMPTY_ARRAY that is a static final.

Don't make it static final anymore, and provide a Class<T> in your constructor:

@SuppressWarnings("unchecked") // Still need this
public Bar(Class<T> clazz) {
    super((T[]) Array.newInstance(clazz, 0));
}

Java pretty much never uses the value of a final variable for warnings except in cases of dead code. Otherwise, you'd get edge cases like this:

class Bar<T> extends Foo<T> {
    // Is it really empty?
    protected static final Object [] EMPTY_ARRAY = SomeOtherClass.getEmptyArray();

    @SuppressWarnings("unchecked")
    Bar() {
         super( (T []) EMPTY_ARRAY );
    }
}

They'd have to write that logic into the compiler. It's unnecessary complication for edge cases like "empty arrays", and besides, this casting like this is all code smell in the end.

Another option you might have besides that answer is to use var args. Foo:

class Foo<T> {
    Foo( T ... arg ) {
    }
}

And Bar:

class Bar<T> extends Foo<T> {

    Bar() {
         super();
    }
}

That should work, and it eliminates all casting, empty arrays, warnings, etc. See more about var args and their possible invocations here.

Community
  • 1
  • 1
Brian
  • 16,069
  • 5
  • 38
  • 64
  • Note the @SuppressWarnings, and compare (1) and (2) at Natix and Pyranja point out in the comments. – Johannes Ernst Dec 11 '12 at 22:49
  • @JohannesErnst Yeah, I saw that after I posted. I'm updating now. – Brian Dec 11 '12 at 22:51
  • Brian: I don't like complicated workarounds that make code less readable. But in any case, I don't see how the linked proposal would work in case of my EMPTY_ARRAY that is a static final. (The particularly infuriating part of Java here is that EMPTY_ARRAY is indeed and always will be empty, so there isn't any type safety issue.) – Johannes Ernst Dec 11 '12 at 23:10
  • @JohannesErnst Beware that even with empty arrays, casting is not necessarily safe. `String[] string = (String[]) new Object[0];` This throws: `java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String` – Natix Dec 11 '12 at 23:26
  • @Natix Exactly right. The only reason it works with generics is because of type erasure. – Brian Dec 11 '12 at 23:27
3

I'm able to emulate this strange behaviour on my Windows 7 64b machine with:

  • Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
  • OpenJDK Runtime Environment (build 1.8.0-ea-lambda-nightly-h1669-20121030-b63-b00)

Which means both the OpenJDK and the Oracle JDK are affected, both JDK7 and JDK8 (yes, you can already download it).

Eclipse, since it uses its own JDT compiler, doesn't have this problem.

So it seems that this is indeed a javac bug. If you report it, please keep me updated.

EDIT:

I've also located a JDK6 installation on my computer, so I tried that one and actually, it works without a warning in both cases, which is the correct behaviour:

  • Java(TM) SE Runtime Environment (build 1.6.0_23-b05)

Although my Windows are 64b, all the said JDKs are only 32b.

Natix
  • 13,037
  • 7
  • 49
  • 67
  • 1
    Worth pointing out that this means the problem is in both the OpenJDK and the Hotspot JDK. – Brian Dec 11 '12 at 23:41
1

I was able to reproduce the behavior with this simplified setup:

class Bar<T> {
   @SuppressWarnings("unchecked")
   Bar() {
      T[]dummy = (T[]) EMPTY_ARRAY;
   }

   private static final Object [] EMPTY_ARRAY = {};
}

As Brian suggested, it seems to be a bug in the compiler. Additionally, this behavior is restricted to Arrays - replacing the EMPTY_ARRAY with an Object and casting it to a T does not issue a warning as expected.

java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)
Pyranja
  • 3,349
  • 20
  • 24
1

It's a bug in both Oracle and OpenJDK 7 and 8.

You can't (shouldn't) get compile warnings/errors from reordering declarations in a class.

Runtime errors, yes; compiler errors, no.

This is Bug 8016636 (thanks for filing it), but there's been no activity in over a year.

Unfortunately, that is not uncommon in Oracle's bug tracker.


Additional weirdness:

  • The warning is suppressed if EMPTY_ARRAY is not final.

  • Ironically, the warning is suppressed with if you initialize it with a non-array, e.g. Object EMPTY_ARRAY = new Object(). (But don't do that...)

Paul Draper
  • 64,883
  • 37
  • 172
  • 246