Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java generics type parameter for method in interface

what I want to achieve here is that It's up to the subclass to decide whether they want to pass java.lang.String or java.util.HashMap as parameter to query() method. Interface just needs to claim that subclasses have to implement query method, but I don't care about what type of parameter subclasses want to pass in.

I have a interface like:

interface A<T>{
    public void query(T type);
}

Two subclasses like:

public class B implements A<String> {
    public void query(String type);
}

public class C implements A<HashMap> {
    public void query(HashMap type);
}

Then I have a factory class to produce B or C:

public class AFactory {
    public static A<?> getType(String type)  {
        if(type.equals("B")){
            return new B();
        } else if(type.equals("C")) {
            return new C();
        } 
   }
}

The idea is that the client can use the interface as below without having dependencies on B,C like:

A instance = AFactory.getType("B");
String a = "test";
instance.query(a); 

The problem I have here is: eclipse gives me error on the line of instance.query(a):

The method query(capture#2-of ?) in the type A is not applicable for the arguments (String).

I think the problem is that the interface contract doesn't know the query should be expecting String or HashMap. The way I can only think of solve this is that, I have to cast the result like:

B instance = (B)AFactory.getType("B");
String a = "test";
instance.query(a); 

But by doing this, I would have the dependency on B instead of just A(the interface) which is something I wanted to avoid at the beginning. Any idea how I can do this without having dependencies on the subclasses(in this case, B and C).

like image 394
Shengjie Avatar asked Apr 17 '12 13:04

Shengjie


2 Answers

As Ernest says, you need to cast the returned value — A<String> is more specific than A<?>.

If you're willing to change the signature of the factory method, you could use generics to avoid having to cast yourself:

public static A<T> getType(Class<T> type) {
    if (type.equals(String.class)) {
        return new B();
    } else if (type.equals(HashMap.class)) {
        return new C();
    }
}

Then your calling code could look like this:

A<String> instance = AFactory.getType(String.class);
String a = "test";
instance.query(a);

If you can't do this then the caller of getType() will have to cast.

like image 98
Sean Reilly Avatar answered Nov 15 '22 05:11

Sean Reilly


You don't have to cast to B -- cast to A<String> . That will give you query(String), but without introducing B into your source:

@SuppressWarnings("unchecked")
A<String> instance = (A<String>) AFactory.getType("B");
String a = "test";
instance.query(a); 

I added the @SuppressWarnings annotation because you'll get an "unsafe" warning without it; you know the warning to be spurious, so the annotation is OK.

like image 33
Ernest Friedman-Hill Avatar answered Nov 15 '22 04:11

Ernest Friedman-Hill