I am stuck in generics downcasting. Because I learn type erasure. When code compiles all parameterized types are converted to the upper bound if the bound is not defined then it changes to object.
Gen Class
public class Gen<T>
{
T ob;
Gen(T o)
{
ob = o;
}
T getob()
{
return ob;
}
void showType()
{
System.out.println("Type of T is: " + ob.getClass().getName());
}
}
GenDemo Class
public class GenDemo
{
public static void main(String [] args)
{
Gen<String> strob = new Gen<String>("I am Ahmad");
String str = strob.getob();
System.out.println("Value of str is: " + str);
}
}
String str = strob.getob(); is converted to String implictly. how JVM converted strob.getob() to String. From where JVM found the strob.getob() is downcast to string. Because type erasure changes the return type to object. So in byte code the return type of getob() is object.But when I call getob() it automatically downcast to string.So I am very confusing the downcasting in generic please explain me in detail.
Yes, at runtime the return type of strob.getob(); is basically equivalent to just Object.
However, during compilation the compiler understands that strob is a Gen<String> and ensures that all the necessary low-level operations are added. So it basically treats that line as if you had written
String str = (String) strob.getob();
In other words: it will generate the bytecode for the type cast, even though that typecast is nowhere to be found in the source code.
This also explains when for some reason the generic type system is broken (by using unchecked casts, basically): if for some reason getob() actually returns something that can't be cast to String then you'll get a boring old ClassCastException just as if you had tried to cast an Integer to String.
Related
To start with an example,
public static <T> T method(T str){
return (T)str;
}
// T is deduced to be a String
// This fails at compile time
Integer integer = method("Trial");
//Object obj = method("Trial"); // Old example
and,
public static <T> T method(String str){
return (T)str;
}
// What type does T gets deduced to in this case?
// This compiles but gives an error at run-time.
Integer integer = method("Trial");
//Object obj = method("Trial"); // Old example
Both code snippets compile fine. Which type does T get deduced to in the second example?
Non specified Generic Type is considered as Object so returned type is correct in both cases.
So which method is used ?
In fact, the most precise matching type will be accepted first. In your case method(String str) as "Trial" is a String.
// Will rename for understanding
// method(T str) => method1
// method(String str) => method2
public static <T> T method1(T str){
return (T)str;
}
public static <T> T method2(String str){
return (T)str;
}
// This is your implementation
Object obj = <Object> //as undefined
method2("Trial"); // as mentionned above
// BUT
Object obj = <Object> // as undefined
method1(1); // as 1 is an int upperbounded to Object
// AND
Object obj = <String>
method2("Trial");
Object obj = <String>
method(1); // will not compile
You should check this post for more details about overloading priorities
I am having a bit of a hard time following the question but anyways.
In your first example the return type is determined (inferred) by the passed in type of the method parameter (i.e String in that case). Consider the following example:
public static <T> T something(T something) {
return something;
}
And two calling examples:
String something = something("string");
Integer someOther = something("something");
The first one works OK but the second produces an error as the dynamically inferred return type is String whereas we attempt to store it an Integer variable.
Now the reason why this works fine when you assign both values to an Object variable is -- as mentioned above -- the fact that this is the upper bound of both String and Integer. In fact in you take a look a the disassembled class file using javap you'll see that your method's signature looks like this:
public static <T extends java.lang.Object> T something(T)
Note that all the above will effectively end up being Object types after compilation due to Java's type erasure.
On another sidenote, your example also casts the passed in parameter to T which is
redundant as this is handled by compiler's ability to infer types.
I suggest you take a look at the following:
https://www.baeldung.com/java-type-erasure
https://www.tutorialspoint.com/java_generics/java_generics_type_inference.htm
Now let's take into consideration your second example:
In that one, T will effectively and up to an Object type -- as I mentioned above. This is still the upper bound for the method but there is a caveat here. This method is wrong as it accepts the String object and attempts to cast it (wrong again) to the inferred type.
So for example I can do this (which compiles OK) but produces a ClassCastException on runtime:
public static <T> T something2(String something) {
return (T) something;
}
and this call:
Integer somethingOther = something2("sas");
Now as mentioned above the reason why this works when assigning to Object is that it is the parent classes of String and everything else.
The first function in your case has both data type of parameter and return type of function as T, which means both of them can be of any type. They can be string, integer, float, character, array, or anything else. The scope of this T type is restricted to this particular function only.
The second function has a data type of parameter as String, while the return type is T, meaning that only String type of values can be passed to this function.
For example,
public static <T> T func1 (T var)
{
return var;
}
The above function can have a = func1("String") or b = func1(2) or anything else. a will be of type String, and b will be of type int. On the other hand,
public static <T> T func2 (String var)
{
return var;
}
will return an error on func2(2).
Refer to Oracle Docs to get a detailed insight into Generic Methods.
Edit to Comment : When a function is being called, the Java compiler automatically infers the type of the parameter from the method arguments. When I invoke func1() in my example, I write func1("ABC"). The Java compiler is intelligent enough to now understand the T type of the argument is String. Similarly, I return a String type from func1, so the compiler will now know that the return type has to be String.
You can also go through https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html to study more.
The following code is from an Android library called ButterKnife. I'm figuring out how it works.
#SuppressWarnings("unchecked") // That's the point.
public <T> T castParam(Object value, String from, int fromPosition, String to, int toPosition) {
try {
return (T) value;
} catch (ClassCastException e) {
throw new IllegalStateException("Parameter #"
+ (fromPosition + 1)
+ " of method '"
+ from
+ "' was of the wrong type for parameter #"
+ (toPosition + 1)
+ " of method '"
+ to
+ "'. See cause for more info.", e);
}
}
I tried to recreate the behaviour of this function:
#SuppressWarnings("unchecked")
public static <T> T cast(Object o){
try {
return (T) o;
} catch (ClassCastException e){
throw new AssertionError("Error");
}
}
And usage:
Object o = new String("test");
Double d = cast(o);
But the exception is not never caught, it gets thrown at the line when the method is called. Why is that?
Also, how does this work exactly? How does the method know what to cast to?
Generics types are checked at compile time only, due to type erasure. This is done because there was no way to introduce generics in the runtime in Java 5 without breaking backwards compatibility and forcing to recompile all the already existing libraries.
Long history short, when you define a "generic" class or method, the actual code is compiled as Object instead of the type you are binding the method. All the checks of types are done at compile time.
So, your method code is not actually doing a cast in the return statement, since it is assigning something (a String) to an Object return value. The actual ClassCastException is returned by the calling line because it is the place when the reference variable is actually typed.
As SJuan67 explained, you cannot really use casts with generic types as Java compiler will
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
More info on all generics restrictions here.
So ButterKnife code will look like this:
public Object castParam(Object paramObject, String paramString1, int paramInt1, String paramString2, int paramInt2)
{
return paramObject;
}
So to your questions:
Q: But the exception is not never caught, it gets thrown at the line when the method is called. Why is that?
A: Well its not even in the bytecode.
Q: Also, how does this work exactly? How does the method know what to cast to?
A: It doesn't. At least not like you think it will. In practice it will throw ClassCastException not IllegalStateException or AssertionError as you observed.
You can even try it with ButterKnife sample app and Bind a known TextView to CheckBox:
#Bind(R.id.title) CheckBox title;
Q: How does the library work then?
A: Well IllegalStateException is just never called and you have ClassCastException. Why it is like that I an not really sure. However as ButterKnife generates code this could be intended to prevent from compile errors.
for example:
public interface Some {
}
public static void weWantSome(Some d) {
}
public static void test() {
String o = "test";
weWantSome((Some)o); //<-- compile error
weWantSome(Main.<Some>cast(o)); //<-- runtime error
}
Which is why in the previous example code compiles but does not run.
I have the following generic test class:
public class BrokenGenerics<T> {
private T[] genericTypeArray;
public BrokenGenerics(T... initArray) {
genericTypeArray = initArray;
}
public void setArray(T[] newArray) {
genericTypeArray = newArray;
}
public T get(int idx) {
return genericTypeArray[idx];
}
public Class getType() {
return genericTypeArray.getClass().getComponentType();
}
public static boolean breakThis(BrokenGenerics any) {
any.setArray(new B[]{new B(2)});
return false;
}
public static void main(String[] args) {
BrokenGenerics<A> aBreaker = new BrokenGenerics<A>(new A("1"));
System.out.println(aBreaker.get(0));
System.out.println(aBreaker.getType());
breakThis(aBreaker);
System.out.println(aBreaker.get(0));
System.out.println(aBreaker.getType());
}
private static class A {
public String val;
public A(String init) {
val = init;
}
#Override
public String toString() {
return "A value: " + val;
}
}
private static class B {
public int val;
public B(int init) {
val = init;
}
#Override
public String toString() {
return "B value: " + val;
}
}
}
When I run it, I get this output, and no errors:
A value: 1
class BrokenGenerics$A
B value: 2
class BrokenGenerics$B
Now, I understand why this compiles; it can't know at compile-time that breakThis is being passed a generic of a bad type. However, once it runs the line any.setArray(new B[]{new B(2)});, shouldn't it throw a ClassCastException (NOTE THAT IT DOES NOT! Try it yourself!) because I'm trying to pass a B[] to a method that expects an A[]? And after that, why does it allow me to get() back the B?
After Type Erasure, T will be turned into Object since you didn't specify a bound on T. So, there is no problem at runtime assigning any type of array to genericTypeArray, which is now of type Object[] or calling the function setArray(...), which now also accepts an argument of type Object[]. Also, your get(...) method will simply return an Object.
Trouble starts when you access elements in the array with a wrong type expectation, since this might lead to (implicit or explicit) illegal type casts, for example by assigning the value returned by get(...) to a variable of type A.
You can also get a run-time ClassCastException if you try to type-cast the array itself, but, in my experience, that is a case that tends to come up less often, although it can be very obscure to find or even understand if it does happen. You can find some examples below.
All generics-checking happens only at compile-time. And if you use raw types, these checks can not be performed rigorously, and thus the best the compiler can do is to issue a warning to let you know that you are giving up an opportunity for more meaningful checks by omitting the type argument.
Eclipse with its standard settings (and probably the java compiler with the correct flags) shows these warnings for your code:
"Class is a raw type" where you define getType() (somewhat unrelated to your question)
"BrokenGenerics is a raw type" where you define breakThis(...)
"Type safety: The method setArray(Object[]) belongs to the raw type
BrokenGenerics" where you call setArray(...) inside breakThis(...).
Examples for causing ClassCastException due to illegal type-cast of the array:
You can get ClassCastExceptions at runtime if you expose the array to the outside world (which can often be a dangerous thing to do, so I try to avoid it) by adding the following to BrokenGenerics<T>:
public T[] getArray() {
return genericTypeArray;
}
If you then change your main method to:
BrokenGenerics<A> aBreaker = new BrokenGenerics<A>(new A("1"));
A[] array = aBreaker.getArray();
System.out.println(array[0]);
System.out.println(aBreaker.getType());
breakThis(aBreaker);
array = aBreaker.getArray(); // ClassCastException here!
System.out.println(array[0]);
System.out.println(aBreaker.getType());
You get the ClassCastException at runtime at the indicated position due to a cast of the array itself rather than one of its elements.
The same thing can also happen if you set the variable genericTypeArray to protected and use it from code that subclasses your generic class with a fixed type argument:
private static class C extends BrokenGenerics<A> {
public C(A... initArray) {
super(initArray);
}
public void printFirst() {
A[] result = genericTypeArray; // ClassCastException here!
System.out.println(result[0]);
}
}
To trigger the exception, add the following to you main method:
C cBreaker = new C(new A("1"));
cBreaker.printFirst();
breakThis(cBreaker);
cBreaker.printFirst();
Imagine this case coming up in a bigger project... How on earth would you even begin to understand how that line of code could possible fail?!? :) Especially since the stack trace might be of very little help trying to find the breakThis(...) call that is actually responsible for the error.
For more in-depth example cases, you can take a look at some tests I did a little while back.
shouldn't it throw a ClassCastException because I'm trying to pass a B[] to a method that expects an A[]?
No. As this post explains, your invocation of setArray in
public static boolean breakThis(BrokenGenerics any) {
any.setArray(new B[]{new B(2)});
return false;
}
is done on a reference expression of the raw type BrokenGenerics. When interacting with raw types, all corresponding generic parameters are erased. So setArray is actually expecting a Object[]. A B[] is a Object[].
why does it allow me to get() back the B?
Assuming you're asking about this
System.out.println(aBreaker.get(0));
PrintStream#println(Object) expects an Object, not an A. As such, there is no reason for the compiler to insert a cast here. Since there is no cast, there is no ClassCastException.
If you had instead done
A a = aBreaker.get(0);
or had a method like
void println(A a) {}
...
println(aBreaker.get(0));
then these would cause ClassCastException. In other words, the compiler will insert a cast (checkcast) anywhere a type needs to be converted from a generic type parameter. That was not the case with PrintStream#println.
Similarly,
System.out.println(aBreaker.getType());
doesn't even involve the generic parameter declared in BrokenGenerics
public Class getType() {...}
and also returns a value of the raw type Class. The compiler has no reason to add a checkcast to A.
this is the code I have so far. Which converts map to object and runs as expected with no error.
But I have a question about how to generic works. Any replay will be appreciated.
1. Why no error occurs although no generic 'T' is defined in the method or in the middle of the code?
2. <T> in the method is like the indicator which informs the compile that this method uses character T as generic? My understanding is right?
public static <T> T convertMapToObject(Map<String, Object> map, Object obj){
try {
Iterator<String> keyIter = map.keySet().iterator();
Method[] methods = obj.getClass().getMethods();
while(keyIter.hasNext()){
String key = keyIter.next();
String methodName = "set" + StringUtils.capitalize(key);
for(int i=0; i<methods.length; i++){
if(methodName.equals(methods[i].getName())){
Class<?> clazz = methods[i].getParameterTypes()[0]; //메서드 인수 타입
if (String.class == clazz){
methods[i].invoke(obj, String.valueOf(map.get(key)));
}
else {
methods[i].invoke(obj, map.get(key));
}
break;
}
}
}
return (T)obj;
} catch (Exception e) {
e.printStackTrace();
log.error(e);
}
return null;
}
---------------------------
FileEx f= convertMapToObject(map, fileEx);
Why no error occurs although no generic 'T' is defined in the method or in the middle of the code?
It >>is<< defined, in your example.
public static <T> T convertMapToObject
^^^ the generic type T is defined here!
It is not necessarily used in the method body, but that's OK. Java doesn't insist that you use things that you define. (Redundant variables, parameters, fields, methods, classes and so on call all legal Java. Pointless ... but legal.)
<T> in the method is like the indicator which informs the compiler that this method uses character T as generic?
Yes ... sort of. T is an identifier (not a character). But yes, it does represent a generic type parameter, and <T> is saying that (i.e. it is declaring the type parameter).
In your example, T >>is<< used in the body; here:
return (T) obj;
However, that usage will be flagged by the compiler as an "unchecked type cast" warning / error. What it says is that the compiler won't be able to emit code that actually checks the type of the result at runtime. That can lead to unexpected typecast exceptions somewhere else in the code; e.g. when the result of a convertMapToObject call is assigned to a variable with a concrete type.
I got this one from a google I/O puzzler talk given by Joshua Bloch. Here's the code
public class Glommer<T> {
String glom(Collection<?> obj){
String result = "";
for(Object o : obj){
result += o;
}
return result;
}
int glom(List<Integer> ints){
int result = 0;
for(int i : ints){
result += i;
}
return result;
}
public static void main(String args[]){
List<String> strings = Arrays.asList("1", "2", "3");
System.out.println(new Glommer().glom(strings));
}
this main method throws an exception because new Glommer is a raw type and hence all the generics in Glommer is erased, so it ends up calling int glom(List<Integer> ints) rather than String glom(Collection<?> obj).
My question is, even if I called glom() as new Glommer<Integer>().glom(strings) shouldn't it call the int glom(List<Integer> ints) method since due to type erasure, this method is effectively int glom(List ints) and strings is of type List not Collection?
The called method is defined at compilation time, not at runtime.
If you add a parameter to your constructor call, the compiler will have enough information to know that it has to call the first method. Otherwise, it's just as if generics didn't exist. In both case, the called method will always stay the same at runtime.
EDIT Some people seem to doubt, so here's another example:
public class Test {
private static void test(Object object) {
System.out.println("Object method");
}
private static void test(Integer integer) {
System.out.println("Integer method");
}
public static void main(String[] args) {
Object object = Integer.valueOf(0);
test(object);
}
}
The result is:
Object method
You pass an Integer to your method, but all that the compiler knows at compile time is that it's an object. The jvm doesn't automagically change the method call even though the Object is actually an Integer.
You can read more about Raw Types to understand it fully
Basically, raw types are for using legacy code, almost anything in a raw class will become raw itself, in this case those 2 methods.
So when it is raw there is a method that gets a List and one for Collection so its called the List one, if its not raw, the methods are not raw also and it will call the Collection one because it has the extra information
This is because when new Glommer() is called without generics the Generic<Type>(), all of type matches are removed from the class.
As strings variable is a List, if it does not have any generic <Type> then it will match the glom(List ints). The type checking doesn't get done till later.
When we create new Glommer<AnyType> the types are all left in place, so when we pass our strings variable it does type checking. The compiler can now check if it is a List<Integer>, which is not so it gets passed to the glom(Collection<?> obj) method.
Hope this helps, please ask for any clarification if you need!