views:

290

answers:

4

I was wondering what is the data type of the template variable if the return is set to a template. I have seen this in a code somewhere but I do not know where does it cast the value retrieved from the session.

public class RequestObject {
    public <T> T getFromSessionMap(String sessionKey) {
     return (T)session.getAttribute(sessionKey);
    }
}

The code for this outside is:

MyClassType type = request.getFromSessionMap("abc");

The line encounters ClassCastException when casting to my object. But when I add to watch session.getAttribute("abc"), it shows that the type is MyClassType. Any help would be appreciated.

Apparently this special code for using template makes the return of getFromSessionMap a variable type and hence no need for a cast. This works on all cases but suddenly it failed in one part of the code.

A: 

In the question's example, the return type is T. The erased type will be Object as, implicitly, T extends Object. The actual cast is performed in the bytecode of the calling method (you can use javap -c to see that).

Generally, you should keep the number of "top-level" objects in sessions as small as possible. One of the benefits of doing that, is there is no longer a need for hacky methods like these.

Tom Hawtin - tackline
A: 

Any help would be appreciated because my code encounters ClassCastException.

If you're getting a ClassCastException, that means you're trying to cast something into something it isn't, like in the following code:

Map session = new HashMap();
session.put("date", "2009-11-12");
Date today = (Date) session.get("date"); // tries to convert String to Date

The ClassCastException should have an enlightening detail message, like "java.lang.String cannot be cast to java.util.Date".

gustafc
The thing is when I add to watch session.getAttribute("abc"), it shows that the type is MyClassType. Yes, that would be the initial reaction to the class exception. That was what I thought too.
Nassign
And the `ClassCastException` detail message is... what?
gustafc
A: 

You use type cast within the method body ('(T)session.getAttribute(sessionKey)').

That means that you explicitly say to compiler 'I'm absolutely sure that returned object IS-A T and ready to handle error if it is not'.

Here your assumption about attribute type was incorrect and you got an error. So, everything is correct and runtime already provides you with the real attribute object type that is not IS-A MyClassType.

denis.zhdanov
+3  A: 

Apparently this special code for using template makes the return of getFromSessionMap a variable type and hence no need for a cast.

Fundamentally, there has to be a typecast somewhere between getting the result of session.getAttribute(sessionKey) and the assignment to MyClassType. The Java language (and the JVM) will not allow some object that is not a MyClassType instance (or a subtype thereof) to be assigned to a MyClassType variable.

No matter how you write the code, a typecast has to occur. And since the attribute (apparently) is not a MyClassType (or subtype), you are getting a ClassCastException.

So the real question is why aren't you getting a compilation error? And the answer is the @SuppressWarnings("unchecked")! If you removed that warning, you would get an "unsafe type conversion" error message for this line:

return (T) session.getAttribute(sessionKey);

In truth, Java cannot do a (real) type cast to a generic type. And that is what the warning / error message is there to point out. In fact, once the code has been compiled, this code

public <T> T getFromSessionMap(String sessionKey) {
    return (T)session.getAttribute(sessionKey);
}

is actually no different in meaning from this:

public Object getFromSessionMap(String sessionKey) {
    return session.getAttribute(sessionKey);
}

Technically speaking this is called "type erasure".

So where is the type checking / typecast actually occurring? The answer is in this line:

MyClassType type = request.getFromSessionMap("abc");

Even though you haven't written a typecast here, the code generated by the Java compiler does a typecast before assigning the value to type. It has to. Because as far as it knows, the instance it is assigning could be any object type.

Other posters have suggested adding a Class argument to getFromSessionMap. By itself this does absolutely nothing. If you also replace the body of the method with:

return clazz.cast(session.getAttribute(sessionKey));

you will cause the method to actually do a real type check. But this only cause ClassCastException to be thrown at a different place. And the assignment statement will still do a hidden type cast!!

Stephen C
I think it casted to type T, but what is type T? Object?
Nassign
T is not a type, T is a parameterized type. You cannot query what type T is at runtime (ala, type erasure). the code you have for putting in the attribute "abc" is not putting in a type of MyClassType (or it is being changed by something after being put into the sessionmap).
Chii
@Nassign - read my answer carefully. There is **no real typecast** occurring when you write `(T)`. What you are doing is an unsafe type conversion ... a no-op in this case.
Stephen C
@Stephen C, thanks for the detailed explanation. I am wondering why the Java compiler even allowed making this construct even the class has no template class definition for the class.
Nassign
@Nassign - I think you are confusing C++ and Java. In Java, there is no such thing as a "template class definition for [a] class".
Stephen C
@Stephen C, I think I mean generics for template. I thought they were the same.
Nassign
@Nassign - In Java, you don't need to declare a generic type parameter at the class level to be able to declare one at the method level.
Stephen C
@Stephen C, I did not know that. Thanks.
Nassign