开发者

Casting to an inner class with generics

Consider the following code:

public class Outer<T>  {

    public class Inner{
    }

    public static <T> Outer<T>.Inner get(){
        Object o = new Object();
        return (Outer<T>.Inner)o;
    }

    public static void main(String[] args) throws Exception {
        Outer.<String>get();
    }
}

This code compiles successfully in Eclipse, but fails to compile in javac:

Outer.java:10: ')' expected
        return (Outer<T>.Inner)o;
                        ^
Outer.java:10: ';' expected
        return (Outer<T>.Inner)o;
                         ^
Outer.java:10: illegal start开发者_Go百科 of expression
        return (Outer<T>.Inner)o;
                              ^
3 errors

Is this a bug in javac or Eclipse?

If I change the cast to (Outer.Inner)o it compiles, although there is a warning:

Eclipse:

Outer.Inner is a raw type. References to generic type Outer<T>.Inner should be parameterized

javac:

Outer.java:10: warning: [unchecked] unchecked conversion
found   : Outer.Inner
required: Outer<T>.Inner
        return (Outer.Inner)o;
               ^
1 warning

Javac version: 1.6.0_21


I have found that this is a bug in the javac compiler that has since been fixed. JDK 7b100 compiles this fine.

See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6665356


The most amusing thing is that, unless there is something that I miss about Java generics, both

return (Outer<T>.Inner) o;

And

return (Outer.Inner) o;

Both compile to the same bytecode.

The problem for the first line happens at parsing - meaning that javac and Eclipse do not use the same parser for Java source code. You should ask a question about what differences there are between Eclipse JDT's java parser and javac's. (Or post a bug at Eclipse).

If you insist on keeping that behaviour (I would suggest refactoring Inner to a static inner class), you could use @SuppressWarning with a field assignation (in order to restrict the @SuppressWarning to the smallest scope possible).

@SuppressWarnings({"rawtypes","unchecked"})
Outer<T>.Inner casted = (Outer.Inner)o;
return casted;

EDIT: OK, I believe I got it - Eclipse's JDT parses the Java code before passing it to the compiler - and their parser can make sense of such a cast, while (at least your and my version of) javac's cannot. (And after that, Eclipse directly passes the parsed code to compilation). Before filing a bug, look at how the last version of Java 6 behaves.


Sadly, if you're casting from an Object, then you can't dodge the unchecked cast warning. That's because Object doesn't have enough type information by itself to have the T.

(Java generics are erasure-based. Therefore there is no way to know whether an object has type argument T at runtime---type arguments are used at compile-time only.)


You cannot cast an Object to something that it's not.


There are two problems in you code:

First is at compile time: you cannot safely cast an object to a generic type as generics are not reified in Java. Casting to a non-instantiated generic is clearly to dangerous for your javac. I fear the compiler response to this depends on the compiler vendor and version.

Second, casting an Object, of type Object, to any other type will throw a ClassCastException at runtime as in Java the type information is contained in the object and won't change after creation.


The following "fix" worked when I compiled with javac. It also successfully compiled in Eclipse. The problem I perceive is that you can't create a new Object from a variable (like what you did in your case). I don't know how to explain it or validate my theory.

/**
 * 
 */
package testcases;

/**
 * @author The Elite Gentleman
 *
 */
public class Outer<T> {

    public class Inner{
    }

    public static <T> Outer<T>.Inner get(){
        //Object o = new Object();
        //return (Outer<T>.Inner)o;
        return new Outer<T>().new Inner();
    }

    public static void main(String[] args) throws Exception {
        Outer.<String>get();
    }
}

Basically, since Inner is not a static nested class, in order to instantiate it, this is how you would do it:

new Outer<T>().new Inner();

Also, Object o = new Object(); doesn't guarantee that the object o is actually an instance of type Inner class.


Update My solution helped with Object instantiation and not for object casting of existing instantiated object. For that, I don't have answers to (but we're developers, we'll figure something out :-) ).

What I can think of is why not make Inner class static nested class?


if you must use a cast, you can suppress the warning with @SuppressWarnings

   Object o = new Object();

   @SuppressWarnings("unchecked")
   Outer<T>.Inner returnVal = (Outer.Inner) o;

   return returnVal;

But realize the warning exists because you're doing something unsafe. Object can't be cast to String. This will result in an exception at runtime.

And as The Elite Gentleman noticed, you may want to mark Inner as static:

public static class Inner {


(Giving another answer because my previous one is too noisy.)

There are two big steps to do in order to compile Java source code to bytecode:

  1. The source code must be parsed into a syntax tree.
  2. The syntax tree is used to generate bytecode.

When javac compiles, it uses its own parser and bytecode generator (whose implementation details will depend on the JDK you are using).

When Eclipse's JDT compiles, it uses its own code parser and after that… I don't know. The point I want to make is that, one way or another, they "bypass" some of javac's parser. (For example, they could pass to javac modified java files that replace all casts of generic classes with raw classes).

My point is - in the end, it is a bug in the part of javac that parses java source code. (There is no reason why such a construct would not be permitted in language specification.)

In order to counteract that bug, you could: * Modify the design of your application to avoid it completely. * Every time you have to make this cast, everyone puts a raw type and an @SuppressWarnings annotation instead of the natural cast.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜