Using Java and C#'s generics to simulate duck typing - java

http://nullprogram.com/blog/2014/04/01/ tries to explain that Java's generics can't simulate duck typing with an example:
class Caller<T> {
final T callee;
Caller(T callee) {
this.callee = callee;
}
public void go() {
callee.call(); // compiler error: cannot find symbol call
}
}
class Foo {
public void call() { System.out.print("Foo"); }
}
class Bar {
public void call() { System.out.print("Bar"); }
}
public class Main {
public static void main(String args[]) {
Caller<Foo> f = new Caller<>(new Foo());
Caller<Bar> b = new Caller<>(new Bar());
f.go();
b.go();
System.out.println();
}
}
The program will fail with a compile-time error. This is the result of
type erasure. Unlike C++’s templates, there will only ever be one
compiled version of Caller, and T will become Object. Since Object has
no call() method, compilation fails.
Does it mean that by Java generics, the methods of a type parameter are limited to the methods of class java.lang.Object?
C#'s generics is implemented in terms of reification instead of type erasure. Does C#'s generics not have the above limitation as Java's generics? So can C#'s generics actually achieve the same thing as duck typing?
Thanks.

can C#'s generics actually achieve the same thing as duck typing?
No. But C#'s generics can include a constraint where the type parameter is restricted to inherit or implement some particular type. When that's done, any expression of the type of that type parameter is resolved as the constrained type and members from that type can be accessed.
This is similar to the extends constraint described in the article you read.
The only duck-typing support in C# is the dynamic keyword, where final compilation of expressions involving dynamic values is deferred until runtime when the actual runtime type is known.
Related reading:
Trivial C# class with a generic parameter wouldn't compile for no apparent reason
Call a method of type parameter

Does it mean that by Java generics, the methods of a type parameter are limited to the methods of class java.lang.Object?
Not exactly. While most generics are erased, it is possible to include a constraint in Java such that the type parameter must be of a certain type. Which effectively makes it not Object.
Untested, but this should be close.
class Caller<T extends CallMe> {
final T callee;
Caller(T callee) {
this.callee = callee;
}
public void go() {
callee.call(); // should work now
}
}
interface CallMe {
void call();
}
class Foo implements CallMe {
public void call() { System.out.print("Foo"); }
}
class Bar implements CallMe {
public void call() { System.out.print("Bar"); }
}
public class Main {
public static void main(String args[]) {
Caller<Foo> f = new Caller<>(new Foo());
Caller<Bar> b = new Caller<>(new Bar());
f.go();
b.go();
System.out.println();
}
}

C# has the same limitations.
Other than dynamic, C# does not have arbitrary ducky-typing anywhere; even with generics, you can only call methods as defined by a type (specifically, the constraint(s) for the generic type parameter, which default to object).

Related

Rust versus Java static methods and invoking on generic type parameters

In rust we can do something like this:
trait MyTrait {
fn my_func();
}
struct MyStruct {}
impl MyTrait for MyStruct {
fn my_func() {
// this gets invoked
}
}
fn my_func<B: MyTrait>() {
// This here
B::my_func();
}
fn main() {
my_func::<MyStruct>();
}
In java we can't do something like this:
interface MyInterface {
public static void myFunc() {
// This gets invoked
}
}
class MyClass implements MyInterface {
public static void myFunc() {
}
}
public class Main {
static <T extends MyInterface> void myFunc() {
// This here
T.myFunc();
}
public static void main(String[] args) {
Main.<MyClass>myFunc();
}
}
Now there are other questions about why java doesn't allow overriding/enforcing of static methods in java, but does this specific example not work because of monomorphization versus type erasure? I'm trying to understand the deeper reason why. My guess is that since there's only one implementation after type erasure, and static invocations need to be compiled in, it doesn't work. Is this the actual reason?
As you pointed out, type erasure takes place and Java generics can be considered syntactic sugar. It is used for type checking, but in the resulting byte code, it will be replaced.
If it is an unbound type, it would be replaced by Object, but in your example, it is a bound type (T extends MyInterface), so it will be replaced by MyInterface instead. That is why MyInterface.myFunc will be called.
Why does the compiler not resolve it like in Rust or C++? I assume it would be possible, so I can only speculate about the design decisions in Java. Java is by nature more dynamic then Rust. New classes can be defined during annotation processing, but they can also be dynamically created on the fly. That property does not play well with compile time resolution, as the compiler cannot know all classes at compile time.
Java is primarily a object-oriented language, while Rust and C++ put more emphasis on the compile phase. Although Java is statically typed, it is more dynamic and more is done at runtime. That makes it more flexible, but it sacrifices performance.
The "Java way" to express the Rust code that you provided, would be to use normal functions instead of static functions. The effect would be similar, but you end up with dynamic-dispatch. Rust like C++ follows the zero-overhead principle, so being forced to use a dynamic-dispatch would violate that principle. In Java, on the other hand, it is not a design goal.
Typically, the JIT should still be able to optimize it (i.e. eliminate the dynamic-dispatch and inline the code). But there is no guarantee like in the equivalent Rust (or C++), where everything is decided at compile time.
I wanted to state clearly that static is not inherited in java.
So the approach would need an instantiation, be it a singleton.
interface MyInterface {
public void myFunc() {
// This gets invoked
}
}
class MyClass implements MyInterface {
#Override // Does not work with static.
public void myFunc() {
}
}
public class Main {
static void myFunc(MyInterface obj) {
// This here
obj.myFunc();
}
public static void main(String[] args) {
Main.myFunc(new MyClass());
}
}
You could hide the instance obj, make it static or whatever.
It might be that lambdas are a better solution:
Main.myFunc(MyClass::myStaticFunc);

Type erasure and overriding generic method

My understanding about generic was that if I have public void saveAll(Collection<? extends Object> stuff) { then compile will determine the "most common type" of the input parameter to be Collection<Object>. Now, with that I wrote below code for overriding the generic methods, but I get following error message in the overriding method - Name clash: The method saveAll(Collection<? extends Object>) of type ChildWithGenericMethod has the same erasure as saveAll(Collection<Object>) of type ParentWithGenericMethod<T> but does not override it
public class ParentWithGenericMethod {
public void saveAll(Collection<Object> stuff) {
}
}
public class ChildWithGenericMethod extends ParentWithGenericMethod{
public void saveAll(Collection<? extends Object> stuff) {
}
}
What I want to know is that after type erasure how these methods would look like, I thought they would look like public void saveAll(Collection stuff) { and hence I should be able to override, because if I don't use generics then I can override like this.
Please note that I know that I can see the byte code after compilation using "javap" but it doesn't tell me how these methods would look like after "type erasure".
Update:
This question doesn't answer my question, if after type erasure both methods become public void saveAll(Collection stuff) { then why subclass is getting overriding error, because with public void saveAll(Collection<Object> stuff) { it passes the overriding rules.
It's worth remembering run time vs compile-time dispatch.
Take away generics for a second.
public class Parent {
public void saveAll(Object x) {
}
}
public class Child extends Parent {
public void saveAll(String x) {
}
}
public String x = "hello";
public Object y = new Integer(334);
public Object z = "goodbye";
public Child c1 = new Child();
c1.saveAll(x); // invokes Child.saveAll
c1.saveAll(y); // invokes Parent.saveAll
c1.saveAll(z); // invokes Parent.saveAll even though z contains a String
Now put generics back into the story.
public class Child<T extends Object> extends Parent {
public void saveAll(T x) {
}
}
Here, saveAll overloads, rather than overrides, the parent's saveAll.
replacing T with an anoymous ? doesn't really change that -- the compiler is trying to create a method that overloads the parent's method, but then realizes that it can't dispatch on compile-time type.
[edit, see Lew Block's comment on the original: the compiler needs to decide whether the child method overrides or overloads the parent's method before type erasure]
if after type erasure both methods become public void saveAll(Collection stuff) { then why subclass is getting overriding error, because with public void saveAll(Collection stuff) { it passes the overriding rules.
The problem (again see Lew Bloch's comment) is that overriding is determined before type erasure. Before type erasure, the parent and child method have different signatures, and so the compiler creates them as overloaded methods rather than overriding methods. After type erasure, the compiler realizes it is in a bind because it now has two methods with the same signature and no way to know how to dispatch.

Why does Java generics not allow type conversion on generic types?

public class Main {
public static <T> void foo(T[] bar) {
double d = (double) bar[0]; // Error : incompatible types
}
public static void main(String[] args) {
int[] int_buf = new int[8];
foo(int_buf);
}
}
The issue is indicated in the code.
Why does Java generics not allow type conversion on generic types?
The problem is deeper than this. Even without the line you highlighted, this program is broken:
public class Main {
public static <T> void foo(T[] bar) {
// do nothing
}
public static void main(String[] args) {
int[] int_buf = new int[8];
foo(int_buf); <-- problem is here
}
}
Java generics only support reference types as type parameters; the generic method foo() could be rewritten as follows:
<T extends Object> void foo(T[] bar) { ... }
And here's your problem: there's no T that extends Object such that an int[] is a T[].
Secondarily, the reason the conversion also fails is that we know nothing about T, so we don't know that there exists a conversion from T to double.
This is because you are not specifiying what the generic type T is. So by default it will think T is an object type, not a number. It's not possible to cast an object to a double, this makes no sense.
If you change to <T extends Number> this should work just fine. Although, you might need to have an Integer array rather than a int array
The Java compiler erases all type parameters in generic code. A direct consequence of this is that you cannot verify which parameterized type for a generic is currently is use. If you test, instanceof will not work either. Since, runtime does not keep track of a type parameters in java, there is no difference between HashSet<Integer> and HashSet<Double>. You can do a ..instanceof HashSet<?> at the most to check if it is an instance of a HashSet.
If something is not clear in what I have written, please refer to the docs.

Java generics puzzler with generic static factory

I recently started writing a generic object mapper for a project and ran into something I don't quite understand. Given the following:
public class G<X> {
public G(Class<X> c) { }
public void m(X x) { }
public static <T> G<T> create(Class<T> c) {
return new G<T>(c);
}
public static void main(String[] args) {
Object o = ""; // irrelevant!
G<?> t = create(o.getClass());
t.m(o);
}
}
I get the following compilation error:
m(capture#402 of ?) in G<capture#402 of ?> cannot be applied to (java.lang.Object)
I can't seem to figure out a way to properly cast t to make this compile. What am I missing? Using JDK 1.6.
EDIT:
This is not an academic question. I'm trying to write a mapper from hibernate objects to their corresponding DTO to be passed around in the REST layer. The assumption is that for each ORM object Foo, there might exist a class FooDTO that has a constructor that takes an instance of Foo as a parameter. The generic class that maps Foo to FooDTO will encapsulate this assumption and throw appropriate exceptions if FooDTO doesn't exist or doesn't have the proper constructor:
class Mapper<Foo,FooDTO> {
private final Constructor<FooDTO> dtoConstructor;
Mapper(Class<Foo> fooClass, Class<FooDTO> fooDTOClass){
// find the constructor of FooDTO or throw ...
}
public FooDTO map(Foo f){
return dtoConstructor.newInstance(f);
}
// this factory is for convenience when we don't know the type of FooDTO:
public static Mapper<X,Object> create(Class<X> fromClass){
Class<Object> dtoClass = // ... find it
return new Mapper<X,Object>(fromClass,dtoClass);
}
}
This seems to break if I pass a generic object class to create.
Note that my actual implementation has all FooDTO classes extends from a generic super class, i.e., the signature of Mapper is actually something like Mapper<Foo,DTO<Foo>>. I don't think that's relevant here.
EDIT 2:
Actually the suggestion of changing the line G<?> t = create(o.getClass()); to G<Object> t = (G<Object>) create(o.getClass()); worked in this context.
Unfortunately I didn't realize that the fact that my class is more complex actually has an impact. Here's a more complete example (I apologize for the piecemeal question):
public class Y<T> {
}
public class G<X, Z extends Y<X>> {
public G(Class<X> c, Class<Z> s) {
}
public void m(X x) {
}
public static <T, S extends Y<T>> G<T, S> create(Class<T> c) {
Class<S> s = null; // find this via some reflection magic
return new G<T, S>(c, s);
}
public static void main(String[] args) {
Object o = ""; // irrelevant!
G<? extends Object, Y<? extends Object>> t = create(o.getClass());
t.m(o);
}
}
In this case the object Class<S> is created using reflection and some conventional location for objects of that type. That part works fine and should be irrelevant to this discussion. The error I am getting now is the following:
inconvertible types
found : G<capture#155 of ? extends java.lang.Object,Y<capture#155 of ? extends java.lang.Object>>
required: G<java.lang.Object,Y<java.lang.Object>>
And if I change the incriminated line to:
G<Object, Y<Object>> t = (G<Object, Y<Object>>) create(o.getClass());
I get a similar error:
java: inconvertible types
required: G<java.lang.Object,Y<java.lang.Object>>
found: G<capture#1 of ? extends java.lang.Object,Y<capture#1 of ? extends java.lang.Object>>
Once again, I apologize for the piecemeal information. I am sorting through this while I am writing.
You have passed the Class object from the getClass() method, which returns a Class<?>, meaning that you had to declare t to be a G<?>.
You cannot call a method with a generic type parameter when the generic type parameter of the variable is a wildcard. The compiler doesn't know which specific class the wildcard really is, so it cannot guarantee type safety when such a method is called. It's the same reason that add can't be called on a List<?>.
To get this to compile, you must use a class literal, to avoid having a Class<?>, and declare t not to have a wildcard.
G<Object> t = create(Object.class);
Then
t.mo(o);
will compile.
What you have here is a consumer. However, the following seems to compile (in Eclipse).
public static class G<X, Z extends Y<X>> {
public G(Class<? extends X> c, Class<Z> s) {}
public void m(X x) {}
public static <T, S extends Y<T>> G<T, S> create(Class<? extends T> c) {
Class<S> s = null; // find this via some reflection magic
return new G<T, S>(c, s);
}
public static void main(String[] args) {
Object o = ""; // irrelevant!
create(o.getClass()).m(o);
}
}
You're creating a G<Object> and then assigning it to a variable of type G<?>. The method invoked takes a variable of the generic type, which won't take anything for <?>. If you change the variable to G<Object> it will work.
Since you are specifying G<?>, javac is expecting to figure out what the generics are (what classes do they represent). Changing the statement to G t = create(o.getClass()); fixes the errors.
capture errors generally mean that the compiler is unable to figure out the classes...
Its not really clear what you are trying to do... Perhaps that information would be useful in helping you more...

If Method calls are dynamically binded then why the Compiler complains The method xyz is undefined for the type xxx

If Method calls are dynamically binded then why the Compiler complains
The method run() is undefined for the type B
Why is compiler checking for the presence of method run in Class b
Here is the code
import java.lang.*;
public class Program
{
public static void main(String [] args)
{
B a = new A();
a.run();//compiler complains at this line.
a.p(10);
a.p(10.0);
}
}
class B {
public void p(int i)
{
System.out.println(i*2);
}
}
class A extends B{
public void p(int i)
{
System.out.println(i);
}
public void run(){
}
}
Java is by design a statically typed language, meaning that the compiler must know and be able to guarantee that an implementation of that method exists in every concrete object. (Maxim Shoustin's answer very nicely demonstrates the reason behind this design decision with an example.)
If the compiler were to assume without any guarantees that an unknown object will happen to have a specific method, it would make Java a duck typed language. This could have its own advantages, but it wasn't in accordance with the design goals of Java.
In practice, in statically typed languages, virtual (meaning non-final) methods (such as your run() method) are resolved dynamically but the strategy used to resolve them is still written at compile time. That strategy may, for example, involve reading the correct offset of the vTable (a table containing the addresses of the actual implementations of the virtual methods of that object), in many implementations of polymorphism - leveraging the type safety of the language to gain some performance during the dynamic dispatch.
The method run() is undefined for the type B
The error is self explanatory. The type B doesn't have a method called .run()
B a = new A() means that your variable a is of type B and that is all the system knows about the variable a.
If you did Object s = new String() and then did s.toLowerCase() it would fail as well, because the variable s is of type Object not of type String.
whatever type your variable is, is the only behaviors that you can call on that type.
Its easy to show:
Let me change your code a bit:
B = Animal
A = Cow
after replace:
public class Program
{
public static void main(String [] args)
{
Animal a = new Cow();
a.sayMooo();//compiler complains at this line. You try to animal to say "moo"?
a.speed(10);
a.speed(10.0);
}
}
class Animal {
public void speed(int i)
{
System.out.println(i*2);
}
}
class Cow extends Animal{
public void p(int i)
{
System.out.println(i);
}
public void sayMooo(){
}
}
Not all animals are Cows
and sure
Not all animals say "mooo"
but
all Cows are animals

Categories