Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forcing Java generic parameters to be of the same type

Tags:

java

generics

How can a similar functionality be achieved without errors?

class A<K> {
   void f(K x) {}
}

void foo(A<? extends X> a, X x) {
    a.f(x); // AN error: The method f(capture#1-of ? extends X) in the 
            // type A<capture#1-of ? extends X> is not applicable for the 
            // arguments (X)
}

I know that it happens because 'a' can be an instance of A<"non-X">, so its 'f' mustn't accept an instance of X as a parameter, but how can I force the parameters to be of the same type?

Here is more code:

Test class:

class Test {
   <T> void foo(A<T> a, T x) {
   a.f(x); // now it works!
 }
}

In some class:

Container<X> container;
public void test() {
    X x = new X();
    new Test().foo(container.get(), x);
}

Here's the container class:

public class Container<K> {
    A<? extends K> get() {
    return new A<K>();
    }
}
like image 394
Evgeny Avatar asked Jun 30 '13 12:06

Evgeny


People also ask

How do I restrict a generic type in Java?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.

Can generic methods be static?

Static and non-static generic methods are allowed, as well as generic class constructors. The syntax for a generic method includes a list of type parameters, inside angle brackets, which appears before the method's return type.

Can a generic class definition can only have one type parameter?

A generic method can have one or more type parameters, such as the "T" in countOccurrences.

Can generics take multiple type parameters?

Multiple parametersYou can also use more than one type parameter in generics in Java, you just need to pass specify another type parameter in the angle brackets separated by comma.


2 Answers

You can force the parameters to be of the same type by doing the following:

// the first class, A<K>:
class A<K> {
  void f(K x) {}
}

// the second class, defining the method with generic type parameters
class Test {
  <T> void foo(A<T> a, T x) {
    a.f(x); // now it works!
  }
}

// a third class, that uses the above two:
class Main {
  public static void main(final String... args) {
    final Test test = new Test();
    final A<String> a = new A<>();
    test.foo(a, "bar");
  }
}

What this does is: the method foo defines a generic type parameter T and uses it to enforce that the K type parameter of the class A must match the type of x, the second parameter of foo.

You could even impose restrictions on <T> if you wish and if it makes sense for your problem, such as <T extends Bar> void foo(A<T> a, T x) {...}, or with super. You would want this if, as Joni asked on a comment in the question, X is actually a type and not a type parameter: you'd use <T extends X> void foo(...).


After you've shown more code, the problem becomes clear.

The method .get() of the container returns an instance of A<? extends K>. Therefore, the type parameter of the instance you obtain from .get() is not fully specified. Usually, it is not very good design to return such an unspecified type. For a video presentation with Joshua Bloch, the author of Effective Java and many APIs and features in Java, showing how to improve such an API, check this: http://www.youtube.com/watch?v=V1vQf4qyMXg&feature=youtu.be&t=22m. At exactly 25'36", Joshua Bloch says "don't try to use them [wildcard types] on return values", and he explains it later. Basically, you don't get any more flexibility by using them, and just makes it painfully hard for users of the API to deal with it (you just felt the effects of doing it...).

To fix, you could simply try to change the signature of .get() to A<K> get(), so the container class would be:

public class Container<K> {
  A<K> get() {
    return new A<K>();
  }
}

Since you do know that get() is returning an instance of A<K>, there's no reason to use the older signature: it simply makes you lose information you already know!

And if this still doesn't work, your problem might be somewhere else, and you'd need to show even more code... or better still, ask other questions! :)

like image 69
Bruno Reis Avatar answered Sep 19 '22 14:09

Bruno Reis


Keeping in mind the PECS rule, and given the way you are using X, you should be specifying as a lower instead of upper bound:

void foo(A<? super X> a, X x)

This way no compiler errors are produced, and you have the most general signature applicable.

like image 21
Marko Topolnik Avatar answered Sep 18 '22 14:09

Marko Topolnik