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:
- The source code must be parsed into a syntax tree.
- 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.
精彩评论