Generic Methods and Method Overloading

Generic methods and method overloading

Are the two methods overloaded?

Yes.

Shouldn't statement A<int>.MyMethod(myInt); throw an error, since constructed type A<int> has two methods with the same signature?

The question doesn't make sense; A is not a generic type as you have declared it. Perhaps you meant to ask:

Should the statement A.MyMethod(myInt); cause the compiler to report an error, since there are two ambiguous candidate methods?

No. As others have said, overload resolution prefers the non-generic version in this case. See below for more details.

Or perhaps you meant to ask:

Should the declaration of type A be illegal in the first place, since in some sense it has two methods with the same signature, MyMethod and MyMethod<int>?

No. The type A is perfectly legal. The generic arity is part of the signature. So there are not two methods with the same signature because the first has generic arity zero, the second has generic arity one.

Or perhaps you meant to ask:

class G<T> 
{
public static void M(T t) {}
public static void M(int t) {}
}

Generic type G<T> can be constructed such that it has two methods with the same signature. Is it legal to declare such a type?

Yes, it is legal to declare such a type. It is usually a bad idea, but it is legal.

You might then retort:

But my copy of the C# 2.0 specification as published by Addison-Wesley states on page 479 "Two function members declared with the same names ... must have have parameter types such that no closed constructed type could have two members with the same name and signature." What's up with that?

When C# 2.0 was originally designed that was the plan. However, then the designers realized that this desirable pattern would be made illegal:

class C<T> 
{
public C(T t) { ... } // Create a C<T> from a given T
public C(Stream s) { ... } // Deserialize a C<T> from disk
}

And now we say sorry buddy, because you could say C<Stream>, causing two constructors to unify, the whole class is illegal. That would be unfortunate. Obviously it is unlikely that anyone will ever construct this thing with Stream as the type parameter!

Unfortunately, the spec went to press before the text was updated to the final version. The rule on page 479 is not what we implemented.

Continuing to pose some more questions on your behalf:

So what happens if you call G<int>.M(123) or, in the original example, if you call A.MyMethod(123)?

When overload resolution is faced with two methods that have identical signatures due to generic construction then the one that is generic construction is considered to be "less specific" than the one that is "natural". A less specific method loses to a more specific method.

So why is it a bad idea, if overload resolution works?

The situation with A.MyMethod isn't too bad; it is usually pretty easy to unambiguously work out which method is intended. But the situation with G<int>.M(123) is far worse. The CLR rules make this sort of situation "implementation defined behaviour" and therefore any old thing can happen. Technically, the CLR could refuse to verify a program that constructs type G<int>. Or it could crash. In point of fact it does neither; it does the best it can with the bad situation.

Are there any examples of this sort of type construction causing truly implementation-defined behaviour?

Yes. See these articles for details:

https://ericlippert.com/2006/04/05/odious-ambiguous-overloads-part-one/

https://ericlippert.com/2006/04/06/odious-ambiguous-overloads-part-two/

Overloading Methods in Java with generics

You cannot have two methods with the same erasure in the same type. That said, you may be able to manipulate the erasure retaining the same type. For instance, if Foo was an interface:

interface Foo<T> { /* ... */ }

class Bar {
public void do_(Foo<Integer> obj) { }
public <A extends Object & Foo<Double>> void do_(A obj) { }
}

But, avoid overloading methods even where generics are not involved.

Java overloading with generic type parameter

Overloading is resolved at compile time, so you can't rely on the runtime types.

What you can do is check the runtime types with instanceof and cast to the relevant type explicitly:

void delete(List<? extends Object> list){
for (Object o: list) {
if (o instanceof ClassA) {
delete((ClassA) o);
} else if (o instanceof ClassB) {
delete((ClassB) o);
}
}
}

Using a generic parameter on overloaded methods

Overload resolution is only done at compile-time.

Since GenericFunc<T> doesn't know whether T is a string or something else at compile-time, it can only ever use the Print<T>(T value) "overload".

Using dynamic, you can change this to a dynamic dispatch, and get the behaviour you expect:

Print((dynamic)value);

This makes the overload resolution happen at runtime, with the actual runtime type of value.

Overloading method with different parameterized type parameter

Java has this thing called "type erasure" which causes problems like this when compiling generic code like this. When compiled, List<Number> and List<Integer> are both just plain List.

Java method overloading - Generic parameter & parameters within same inheritance tree

The rules of determining which method signature to call at compile-time are explained in the language specification. Specifically important is the section on choosing the most specific method. Here are the parts related to your questions:

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

...

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

  • m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek by §18.5.4.

  • m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ in, n = k).

...

A type S is more specific than a type T for any expression if S <: T (§4.10).

In this case, Integer is more specific than Number because Integer extends Number, so whenever the compiler detects a call to foo that takes a variable declared of type Integer, it will add an invocation for foo(Integer).

More about the first condition related to the second method being generic is explained in this section. It's a little verbose but I think the important part is:

When testing that one applicable method is more specific than another (§15.12.2.5), where the second method is generic, it is necessary to test whether some instantiation of the second method's type parameters can be inferred to make the first method more specific than the second.

...

Let m1 be the first method and m2 be the second method. Where m2 has type parameters P1, ..., Pp, let α1, ..., αp be inference variables, and let θ be the substitution [P1:=α1, ..., Pp:=αp].

...

The process to determine if m1 is more specific than m2 is as follows:

...

If Ti is a proper type, the result is true if Si is more specific than Ti for ei (§15.12.2.5), and false otherwise. (Note that Si is always a proper type.)

Which basically means that foo(Number) and foo(Integer) are both more specific than foo(T) because the compiler can infer at least one type for the generic method (e.g. Number itself) that makes foo(Number) and foo(Integer) more specific (this is because Integer <: Number and Number <: Number) .

This also means that in your code foo(T) is only applicable (and inherently the most specific method since it's only the one applicable) for the invocation that passes a String.

Overload method with generic method and parameter

You don't need to overload the method because Java does this implicitly for you with raw types.

<E> E[] toArray(List<E> data) {

If you call

List objs = ...
Object[] array = toArray(objs);

and if you use generics you get generics

List<String> strs =
String[] array = toArray(objs);

The real problem you have is there is no way to implement toArray. This is because the generic type is not known at at runtime. i.e. you can't create an array of E nor can you cast an Object[] to a String[] for example.

What you can do is pass the type of the array to use.

public static Object[] toArray(List list) {
return toArray(list, Object.class);
}
public static <E> E[] toArray(List<? extends E> list, Class<E> eClass) {
@SuppressWarnings("unchecked")
E[] array = (E[]) Array.newInstance(eClass, list.size());
for (int i = 0; i < array.length;i++)
array[i] = list.get(i);
return array;
}


Related Topics



Leave a reply



Submit