This question already has answers here:
Why does this generic java method accept two objects of different type?
(4 answers)
Closed 4 years ago.
Say I have this code:
class Demo
{
static <T> T pick(T a1, T a2)
{
return a2;
}
public static void main(String[] args)
{
pick("d", 123);
}
}
From what I learned, it seems like I have stated that the two parameters a1, a2 and the return type of pick must be under the same generic type T.
So why is the compiler allowing me to pass a String and an Integer to pick?
Both String and Integer are subclasses of Object, along with every other type in Java. When compiling a generic method (or class), Java attempts to find the closest supertype shared between every instance of the generic type. This part never fails, because Object exists. But if a generic type is resolved to Object, it may not be useful anymore.
So what’s the point in using generics here if the compiler will let any types to be used? It’s all to do with the return type. Assuming your definition of pick(), what do you think will happen when you try to compile each of these lines?
Object o = pick("Hello", 123); // 1
String s = pick("Hello", 123); // 2
String s = pick("Hello", "world"); // 3
Integer i = pick("Hello", 123); // 4
Integer i = pick(123, 456); // 5
int i = pick(123, 456); // 6
1 compiles just fine, but then you’ve lost any useful type information. This is what would happen if you didn’t use generics at all, and instead just used Object for everything. It’s you’d have had to do before Java 5, along with plentiful casting and exception catching.
2 and 4 won’t compile:
error: incompatible types: inferred type does not conform to upper bound(s)
Since the two arguments to pick() share only Object as a common supertype, T becomes Object and an Object is returned and you can’t assign an Object to a String or an Integer.
3 works just fine though. Both arguments have the same type, so T is easily determined to be String. 5 works for similar reasons.
6 also works, but not because T becomes int. int is a primitive type, so it can’t be used in a generic. When attempting to resolve the generic type T, the compiler first autoboxes the primitive arguments into a ‘real’ class (Integer in this case). This also happens for 4 and 5, and even when you simply assign a literal like Integer i = 123;. The difference here is that the result (an Integer) is unboxed back to an int so that it can be assigned to i.
In your example implementation of pick(), the return value should have the same type as the second parameter. If your API specifies that the result is always derived primarily from that parameter, you could use two generic types:
static <T, U> T pick(U a1, T a2) {
return a2;
}
With this addition, 2 still fails to compile, but 4 works as you’d expect.
The compiler will walk down the inheritance tree of a1 and a2 to find a common ancestor, in this case Object. Part of the reason you aren't seeing that is that you are discarding the return value. The following two versions won't compile:
String choice = pick("d", 123);
and
Integer choice = pick("d", 123);
The following will, however:
Object choice = pick("d", 123);
Related
I was playing little bit with java generics, I came across this piece of code, which I am confused why it is happening so.
I am passing my second parameter K as Integer and inside generic method I was casting float to my K type, and in main() I am receiving it as Integer,
In my Code inspector I was seeing the Float number completely sitting in my list (not chopped after casting to Integer) which is of Integer type, but when I try to pick element to save it in Integer variable it gives ClassCastException.
Can someone explain what is going wrong with generics so it is not saving us from casting exception.
Note : I reach to this scenario when I removed my second parameter K from signature so there will be nothing defining type of K , in that case I think Java make it Object and then possibly we get cast exception but why in this case when I am passing K type as well.
import java.util.ArrayList;
import java.util.List;
public class IntegerPrinter {
Integer item;
public void print() {
System.out.println(item);
}
public <T,K> List<K> anyPrint(List<T> num,K lo) {
List<K> mylist = new ArrayList<>();
mylist.add( (K) new Float(2.99f));
return mylist;
}
public IntegerPrinter(Integer item) {
this.item = item;
}
}
import java.util.ArrayList;
import java.util.List;
public class GenericsInAction {
public static void main(String[] args) {
IntegerPrinter oldPrinter = new IntegerPrinter(188);
oldPrinter.print();
List<Integer> dates = oldPrinter.anyPrint(new ArrayList<Integer>(),7);
Integer x = dates.get(0);
}
}
I condensed the code down to the essential parts and modified it slightly to highlight the behaviour that is important:
class Ideone {
public static void main(String[] args) {
List<Integer> dates = new IntegerPrinter().anyPrint(7);
System.out.println(dates.get(0)); // succeeds
Integer x = dates.get(0); // Line 8, throws
}
}
class IntegerPrinter {
public <K> List<K> anyPrint(K lo) {
List<K> mylist = new ArrayList<>();
mylist.add((K) Float.valueOf(2.99f));
return mylist;
}
}
When executed, this program will result in the following output:
2.99
Exception in thread "main" java.lang.ClassCastException: class java.lang.Float cannot be cast to class java.lang.Integer (java.lang.Float and java.lang.Integer are in module java.base of loader 'bootstrap')
at Ideone.main(Main.java:8)
Ideone.com demo
Now, let us step through the code and try to understand what is going on.
This line:
mylist.add((K) new Float(2.99f));
basically tells the compiler "do not care for the type, we (as programmers) guarantee that it is a K, tread it as a K".
Then, if we dig deeper, we see that ArrayList uses an Object[] as backing data structure. So there is on problem here, the backing Object[] elementData can store everything.
Things get weird when we start retrieving elements. The JLS is somewhat vague about the type assertions in those cases (I think they are covered under §5.1.5 and §5.1.6.3, but I am not entirely sure). It basically says "the compiler has to assert the types, but only when necessary".
So if we retrieve an element from our List<Integer>, that clearly is not an Integer, but is passed along to a method that can deal with Object, no type-assertion is necessary. This is exactly the case here:
System.out.println(dates.get(0));
The closest signature matching in System.out is the println(Object) method. This is the situation in JLS, §5.1.5: a widening conversion, it will never throw.
On the other hand, if we now try to retrieve an Integer and try to store it in an Integer:
Integer x = dates.get(0);
Now, a type check is in place. And indeed, if we check the output of the program, we see that the System.out.println(...) took place, but the assignment to an int-variable was the statement that triggered the ClassCastException. This is the situation described in JLS, §5.1.6.3: a narrowing conversion at run time (that comes from ArrayList's elementData(int) method).
Footnote
Generics are most certainly one of the most, if not the most, complex and confusing parts in the JLS. I made a best-effort attempt to cite the JLS on its relevant parts, this might be miss-cited. I also know that this question was asked before, but I am unable to find the duplicate. If:
a citation of the JLS is wrong, and another part should be cited instead, please ping me via comments or edit the post
you find the (a) duplicate, please ping me, and I will close the question as duplicate (and delete my answer, if possible)
Since ArrayList is a generic type whose type erasure is java.lang.Object, that is what is stored in the list. You can think of the type erasure as being the run-time type, the "real" type, as opposed to the compile-time type that the compiler knows about. Any type can be stored in the ArrayList when the program runs.
It just so happens the the type erasure of K in anyPrint is also java.lang.Object, because you have no bounds on the type K. The method is compiled once for all usages, and it must be able to accept any type for K. So when the code for anyPrint is compiled, the cast to K in the line mylist.add( (K) new Float(2.99f)); is ignored, since the type erasure of K is java.lang.Object. Casting to java.lang.Object is useless and pointless. It compiles as mylist.add(new Float(2.99f)); and the code inserts an object of type java.lang.Float into a list of type java.lang.Object.
Also, a cast in Java on an object type simply ensures the object has the correct type, it does not change the values of the object, like a cast on a primitive type. So there is no reason for you to believe the value 2.99f could change.
GenericsInAction is compiled separately.
The parametrized type of K is java.lang.Integer in the main method of GenericsInAction, since you pass in a 7 which is converted to java.lang.Integer via auto-boxing, to be compatible with the type erasure of java.lang.Object in anyPrint. So, when that main method is compiled, the compiler inserts a run-time check, a checkcast, right after the call to dates.get, a check that ensures that the call to dates.get(0); dates returns an object of type java.lang.Integer, since the type of K must be java.lang.Integer inside main.
Since you inserted a java.lang.Float into the list, that run-time check fails and throws ClassCastException.
This question already has answers here:
Generic type extending Number, calculations
(3 answers)
Closed 5 years ago.
The following code snippet throw me the error as shown in the header, I didn't figure out why it does not work as T is of type Number, I expected operator '+' to be fine.
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
Would be appreciate if anyone can provide some clues, thx !
There is a fundamental problem with the implementation of this idea of generic arithmetic. The problem is not in your reasoning of how, mathematically speaking, this ought to work, but in the implications of how it should be compiled to bytecodes by the Java compiler.
In your example you have this:
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
Leaving boxing and unboxing aside, the problem is that the compiler does not know how it should compile your + operator. Which of the multiple overloaded versions of + should the compiler use? The JVM has different arithmetic operators (i.e. opcodes) for different primitive types; hence the sum operator for integers is an entirely different opcode than the one for doubles (see, for example, iadd vs dadd) If you think about it, that totally makes sense, because, after all, integer arithmetic and floating-point arithmetic are totally different. Also different types have different sizes, etc (see, for example ladd). Also think about BigInteger and BigDecimal which extend Number as well, but those do not have support for autoboxing and therefore there is no opcode to deal with them directly. There are probably dozens of other Number implementations like those in other libraries out there. How could the compiler know how to deal with them?.
So, when the compiler infers that T is a Number, that is not sufficient to determine which opcodes are valid for the operation (i.e. boxing, unboxing and arithmetic).
Later you suggest to change the code a bit to:
class MathOperationV1<T extends Integer> {
public T add(T a, T b) {
return a + b;
}
}
And now the + operator can be implemented with an integer sum opcode, but the result of the sum would be an Integer, not a T, which would still make this code invalid, since from the compiler standpoint T can be something else other than Integer.
I believe there is no way to make your code generic enough that you can forget about these underlying implementation details.
--Edit--
To answer your question in the comment section consider the following scenario based on the last definition of MathOperationV1<T extends Integer> above.
You're correct when you say that the compiler will do type erasure on the class definition, and it will be compiled as if it was
class MathOperationV1 {
public Integer add(Integer a, Integer b) {
return a + b;
}
}
Given this type erasure it would seem as if using a subclass of Integer ought to work here, but that's not true since it would make the type system unsound. Let me try to demonstrate that.
The compiler cannot only worry for the declaration site, it also has to consider what happens in the multiple call sites, possibly using a different type argument for T.
For example, imagine (for the sake of my argument) that there is a subclass of Integer that we'll call SmallInt. And assume our code above compiled fine (this is actually you question: why it doesn't compile?).
What would happen then if we did the following?
MathOperationV1<SmallInt> op = new MathOperationV1<>();
SmallInt res = op.add(SmallInt.of(1), SmallInt.of(2));
And as you can see the result of the op.add() method is expected to be a SmallInt, not an Integer. However, the result of our a + b above, from our erased class definition, would always return an Integer not a SmallInt (because the + uses the JVM integer arithmetic opcodes), and therefore this result would be unsound, right?.
You may now wonder, but if the type erasure of MathOperationV1 always returns an Integer, how in the world in the call site it might expect something else (like SmallInt) anyways?
Well, the compiler adds some extra magic here by casting the result of add to a SmallInt, but only because it has already ensured that the operation can't return anything else other than the expected type (this is why you see a compiler error).
In other words, your call site would look like this after erasure:
MathOperationV1 op = new MathOperationV1<>(); //with Integer type erasure
SmallInt res = (SmallInt) op.add(SmallInt.of(1), SmallInt.of(2));
But that would only work if you could ensure that add returns always a SmallInt (which we cannot due to the operator problems described in my original answer).
So, as you can see, your type erasure just ensures that, as per the rules of subtyping, you can return anything that extends an Integer, but once your call site declares a type argument for T, you're supposed to always assume that same type wherever T appeared in the original code in order to keep the type system sound.
You can actually prove these points by using the Java decompiler (a tool in your JDK bin directory called javap). I could provide finer examples if you think you need them, but you would do well to try it yourself and see what's happening under the hood :-)
Auto(un)boxing only works for types that can be converted to their primitive equivalents. Addition is only defined for numeric primitive types plus String. i.e: int, long, short, char, double, float, byte . Number does not have a primitive equivalent, so it can't be unboxed, that's why you can't add them.
+ isn't defined for Number. You can see this by writing (with no generics):
Number a = 1;
Number b = 2;
System.out.println(a + b);
This simply won't compile.
You can't do addition generically directly: you need a BiFunction, a BinaryOperator, or similar, which is able to apply the operation to the inputs:
class MathOperationV1<T extends Number> {
private final BinaryOperator<T> combiner;
// Initialize combiner in constructor.
public T add(T a, T b) {
return combiner.apply(a, b);
}
}
But then again, you may as well just use the BinaryOperator<T> directly: MathOperationV1 adds nothing over and above that standard class (actually, it provides less).
This question already has answers here:
When is generic return value of function casted after type erasure?
(3 answers)
Closed 5 years ago.
Consider below code and the output in Eclipse 4.5.0 & javac(1.8) .
I know it is due to the type erasure in runtime, but why the second one still output data even it is declared as List of Integer, I also checked with javap, checkcast bytecode is only inserted in the third output .
My questions are :
is it a bug ?
How the javac determine where to insert the "cast" ?
public static void main(String[] args){
List<String> a = Arrays.asList("abc","def");
List<Integer> b = (List<Integer>)(List<?>)a;
System.out.println(b.size()); --output 2
System.out.println(b.get(1)); ---output "def"
System.out.println(b.get(1).getClass()); --error in type cast
EDIT
Checked the answer below and the When is generic return value of function casted after type erasure? , quite similar with my case.
If we add the answer from this What is meant by "the erasure of the static type of the expression on which it is called" in the getClass() docs?, then it will be much clear about the "cast" rule.
The compiler can determine where to insert the cast and ensure type safety.
My first case is fine since it will return int anyway.
Second case is fine since the println expect Object. so no cast is needed to ensure type safety.
Thrid case is not since the getClass() is expected to return the Class which is the static type of b.get(1) according to JLS. So the cast is inserted and get type cast error.
As #newacct said , "you should not rely on the compiler to decide either way" (when there is alternative choice and also ensure type safety ,the 2nd case here).
In the last example, the cast happens as follows
Class clazz = ((Integer)b.get(1)).getClass();
And hence the exception, where the second line
System.out.println(b.get(1));
Does not assign to an integer and hence the cast wont happen, assign it to an integer to see it failing.
Integer x = b.get(1); //fails
This question already has answers here:
Java Generics: Wildcard capture misunderstanding
(7 answers)
Closed 7 years ago.
Imagine an interface like this
public interface MessageParameter<T> {
public List<T> unmarshal(byte[] array);
public int getLength(List<T> values);
}
and a consumer of that interface
public class GenericUser {
List<MessageParameter<?>> payload = new ArrayList<>();
public void run() {
byte[] byteArray = new byte[] { 1, 2 };
for (MessageParameter<?> element : payload) {
element.getLength(element.unmarshal(byteArray)); //compiler error
}
}
}
The compiler gives an error
The method getLength(List<capture#1-of ?>) in the type MessageParameter<capture#1-of ?> is not applicable for the arguments (List<capture#2-of ?>)
Clearly since I am using element in both method calls, the type of both is the same and it should be allowed. Another way to ask the same question, why is the compiler creating capture#2?? why can't it deduce that they are logically both the same capture?
Am I missing something? is there a counter-example where this code would throw a runtime exception??
My main question is not how to fix the code (although that would be interesting as well, my current solution is to use Object instead of ?), but what is the logical reason for this error? It looks to me like a shortcoming on the implementation of the compiler more than a logical limitation
The answer is that the compiler is not that smart to accept that the runtime type corresponding to ? is the same because it does not care that your one-line expression involves the same element:
element.getLength(element.unmarshal(byteArray));
is semantically similar to:
List<?> unmarshalledList = element.unmarshal(byteArray);
element.getLength(unmarshalledList);
In this case, it is not so obvious that the list unmarshalledList would surely have to have the same "any-type" as the one expected by getLength(). The above are two separate statements (even though they're contiguous). Imagine that they're not contiguous. You may have something like:
MessageParameter<?> otherElement = getOtherElement();
for (MessageParameter<?> element : payload) {
List<?> unmarshalledList = element.unmarshal(byteArray);
// unmarshalledList can be re-assigned from another parameterized type
unmarshalledList = otherElement.unmarshal(byteArray);
element.getLength(unmarshalledList); // error
}
In other words, the compiler cannot assume that the variable unmarshalledList will retain the same ? type from element when the program reaches the statement invoking getLength on the same element. It can be re-assigned to a different parameterized type in between.
I believe you're misinterpreting the meaning of ? in a generic. That symbol is known as the wildcard; and it refers to a truly unknown type. This is different from your current effort, in which it would literally be better to use Object, as your types are not completely unknown—you know that they both implement Object and can reference them as such. (? extends Object might be better in some places).
As to the reason why ? is not synonymous with Object, remember that primitives do not inherit from Object, but may be referenced with the wildcard. Therefore, after type erasure, your program cannot be certain that the two wildcards are referring to compatible entities; unless you explicitly tell it as much.
I am reading up OCaml and from wiki, it says:
*its static type system renders runtime type mismatches impossible*
I understand why, but then I think, why is this so special in OCaml (and FP)? How do you cause a runtime type mismatch in, say, Java? e.g.
boolean a = true;
int b = a + 1;
will return an error in compile time.
EDIT 1:
Haskell
func :: Int -> Bool
func i = if i > 0 then True else False
Java
boolean func (int i) {
if (i > 0) return true; else return false;
}
Isn;t it the case that both will guarantee the argument type when the func is called?
In Java, you can cause a Runtime type mismatch like this:
Object i = Integer.valueOf(6);
String s = (String) i;
System.out.println(s);
This will compile, because the compile-time type of i (Object) is allowed to be cast to String, however at Runtime, the actual value of i (6, as Integer) will be incompatible to String.
Given this, a ClassCastException is thrown.
Consider the following code using arrays:
// create an array of strings
String[] strings = new String[10];
// cast it to an array of objects
Object[] objects = strings;
// insert an object into the array
objects[0] = new Object(); // Run-time error occurs here
Java allows this to compile, despite the fact that casting a array of strings to an array of objects array introduces the possibility of run-time errors. Line 8 demonstrates this, causing a run-time exception of a type created specifically for this situation: java.lang.ArrayStoreException: java.lang.Object.
See Java generics and type erasure
That wiki is discussing static type systems in general, and contrasting them with dynamically-typed languages rather than other statically-typed languages. There's nothing specific to OCaml or Haskell about runtime type mismatches that doesn't apply to all statically-typed languages.
Note that impossible is a little disingenuous. Pretty much all statically-typed languages give you the ability to also do runtime typing in a limited way, because certain tasks are extremely difficult without it. In fact, the very paragraph you're quoting lists a couple of those cases, like serialization. Other answers here have provided some good examples in Java. However, the vast majority of your code should be able to easily avoid runtime type mismatches.
In Java, type mismatches are possible. For example, the following throws a ClassCastException.
Object o = 1;
String s = (String) o;
However, the arguments passed to a method are checked by the compiler.
It is impossible to invoke a method with signature
boolean func (int i)
unless i is an int, and it is impossible to invoke a method with signature
boolean func2 (String s)
unless s is a String or null.
Therefore you will never get a ClassCastException at runtime within the body of func2 because s is not a String.
In Java, it is impossible to have a type mismatch between reifiable types (primitive types, non-generic reference types, raw types, types parameterized by all wildcards, or array types whose element type is reifiable).
If you have a variable of a reifiable type, then the value it holds at any point in time is guaranteed to be a value of that type. For reference types, this means that the reference is either null or points to an object whose runtime class is a subtype of the variable's type. This is guaranteed because Java requires a cast when storing a value whose type is not a subtype of the variable's type, and casts to reifiable types are checked casts, which means they are checked at runtime, and if the type is not compatible it will throw an exception rather than let there be a type mismatch.
On the other hand, for non-reifiable types (e.g. parameterized types), it is possible to have a type mismatch (which is called "heap pollution" in Java terminology). This is because casts to non-reifiable types are unchecked casts.
List<String> foo = new ArrayList<String>();
foo.add("hi");
List<?> bar = foo;
List<Integer> baz = (List<Integer>)bar; // unchecked cast
// now there is a type mismatch