3 ms·
I ran the example and I got the same result. The result that is provided, and the one I got, makes sense. Can someone explain to me what is "broken" about this?
by gravypod 10y ago
I ran the example and I got the same result. The result that is provided, and the one I got, makes sense. Can someone explain to me what is "broken" about this?
Broken down you:
Create 3 classes
- Unsound: Unsound contains a static generic method. Two types are requested (T and U). T is the parameter and the U is the return type. From now on T will be called CoerceInputType and U will be called CoerceOutputType
- Constrain: A class that has two generic types it requests. A being the first type, and B who is the second type. A restriction is placed on B saying that B extends A. We will call A ConstraintType and B ExtendedConstraintType
- Bind: A class that takes one generic parameter of type A. Here we will call A BindType
What is happening here?
Your execution stack:
- *Call* Unsound.coerce. Tell it CoerceInputType is an Integer and CoerceOutputType is a String. You pass into coerce a value of '0' with the type CoerceInputType (an Integer class) and accept a result with the return type CoerceOutputType (a String class)
- *Assign* In Unsound.coerce you define a type Constraint<CoerceOutputType, ANYTHING extends CoerceInputType>. This means that these type annotations will be propagated through anything this is passed into.
- *Assign* you create an instance of bind with the type CoerceOutputType.
What this currently looks like
<Output, AnyType that extends Input> Output ourFunction(Input i) {
return i
}
What happens: You get an exception as Integer is not castable to a String. You can look further down to the only link in this page to see where that cast is coming from in the bytecode.
The authors seem to be surprised that this will not execute? Or maybe that this doesnt? This is completely expected behavior. Maybe not to someone who hasen't worked in Java and is coming from another VM but for me this is not surprising. Why? Here's a list:
The ways you can trigger a type change:
1. Auto boxing
2. Casting
For integers and strings, these two type conversions will not work. Here is a much more direct explanation of what's happening here:
class Test {
public static void main(String[] args) {
String a = "Hello World";
Integer b = 10;
a = b;
}
}
Running this you will get the following error:
test.java:5: error: incompatible types: Integer cannot be converted to String
a = b;
^
1 error
This is because Integer cannot auto (un)box to String. This exception happens at compile time. I'll get into why in a bit.
You might also ask, "But gravypod, what about an explicit cast, will that work?" No, it will not!
test.java:5: error: incompatible types: Integer cannot be converted to String
a =(String) b;
^
1 error
This will not work at compile time as well.
Now for the next section: why will this not be picked up at compile time? Well that's a hard one to answer, the basic answer is the method isn't existent yet. Or to be closer to the truth, the method's types don't exist yet. When you use generics you're creating a method that will work on a base set of types. There isn't necessarily hard type checking on generics though. These are mainly there for the programmer, not the runtime. The runtime will operate on any data and is just expecting casting to change between types.
When run though a decompiler this is the output of Unsound.class: http://hastebin.com/sitamomogu.java http://hastebin.com/sitamomogu.java
All that has happened is the compiler couldn't follow the type annotations at compile time and it was left for runtime. This should be improved, but I don't see what is broken. The time of the error just shifted. This is the same thing that happens with null. The error is not compile time when you are going to have an NPE. Instead they are runtime.
Here is example code that compiles perfectly fine:
public class Test {
public static void main(String[] args) {
String a = null;
System.out.println(a.getClass().getSimpleName());
}
}
When run you will get this exception:
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:7)
I don't think that this is broken behavior as this is consistent with the way the JVM is meant to operate. Types are suggestions to the VM. This to me is a side effect of auto (un)boxing.
Maybe I'm wrong. I hope someone could tell me why if so.
Edit: Please read https://news.ycombinator.com/item?id=13051036 https://news.ycombinator.com/item?id=13051036 as this explanation is much more elegant and gets the idea across in a simpler way.
- int_19h 10y agoThere's no lack on clarify on why this happens. The point is that getting a ClassCastException with no explicit casts in the code is bad in and of itself. Java (and other similar languages) is normally assumed to be sound wrt types in the absence of explicit casts.
- Groxx 10y agoThe unsoundness is more straightforward than that: https://news.ycombinator.com/item?id=13051659 https://news.ycombinator.com/item?id=13051659 By passing `null`, the compiler doesn't notice that the type constraints are irrational, leading to a violation - to satisfy them, it'd need an Integer superclass which is a subclass of String. Arguably that's fine, since the null can't be used to do anything - it's perfectly "safe" to insert a `String x = null;` into a `new ArrayList<Integer>().add(x)` for instance, since it's just a null. But writing that code gives you a compiler error since it isn't allowed by the type system - writing the same thing via generics should too, but instead it fails at runtime.