Java泛型传递参数

7
希望有人能帮我解决这个困惑。
我写了这个方法:
public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) {
}

使用参数T是为了确保用作键的类与在MyInterface中用作参数的类相同。

现在我想传递一个以不同类为键的映射,当然还有对应的MyInterface实现。

但它无法工作,因为类型参数导致语法错误。以下是代码,希望它能自说明。

    import java.util.HashMap;
    import java.util.Map;

    public class Test {

    public static void main(String[] args) {
        Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>();

    //      Map<Class<Object>, MyInterface<Object>> map = new HashMap<Class<Object>, MyInterface<Object>>();

        map.put(Object.class, new MyObjectImpl());

        //if I use Map<Class<Object>, MyInterface<Object>> I get a compiler error here
        //because map<String> is not map<Object> basically
        map.put(String.class, new MyStringImpl());

        //this would be possible using <?>, which is exactly what I don't want
    //      map.put(String.class, new MyIntegerImpl());

        //<?> generates anyways a compiler error
        myMethod(map);
    }

    //use T to make sure the class used as key is the same as the class of the parameter "object" in doSomething  
    public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) {

    }

    interface MyInterface<T> {
        void doSomething(T object);
    }

    static class MyObjectImpl implements MyInterface<Object> {
        @Override
        public void doSomething(Object object) {
            System.out.println("MyObjectImpl doSomething");
        }
    }

    static class MyStringImpl implements MyInterface<String> {
        @Override
        public void doSomething(String object) {
            System.out.println("MyStringImpl doSomething");
        }
    }

    static class MyIntegerImpl implements MyInterface<Integer> {
        @Override
        public void doSomething(Integer object) {
            System.out.println("MyIntegerImpl doSomething");
        }
    }
}

1
算了吧,这行不通。泛型只能定义同类映射。你不能对每个条目强制执行任何操作,只能对所有映射条目执行相同的操作。 - Marko Topolnik
你只有一个,真的 - 他们的代码几乎完全相同,只是名称不同。你在实践中所做的就是别名。MyStringImpl == MyGenericImpl<String> - Romain
但是doSomething的实现对于每个人都不同。我正在使用它进行测试。在String的情况下,我可以有一个实现来检查它是否为空。因此,在doSomething中,我期望一个String,并检查它是否不为空。在其他String检查的实现中,例如检查非大写字符等。以此类推。而且在地图中,我实际上想要将列表作为值与我想要为某个类执行的所有检查一起列出... - User
1
@Romain,你所说的实际上是不正确的。通常,人们会添加接口以允许有多个实现。开发人员往往倾向于为默认实现添加“Impl”。在这种情况下,尽管代码可以被重构,但存在三个非常不同的实现,具有不同的具体类定义。 - Guillaume Polet
@GuillaumePolet 我们不应该在这里争论 - 这不是评论的重点。使用“Impl”作为默认实现是有缺陷的,因为它的语义(听起来像“...的实现”)会产生歧义。无论如何,这是一种口味问题 - 类似于vimemacs之间的选择。 - Romain
显示剩余2条评论
4个回答

8

你不能这样做,因为Mapput()方法中没有定义keyvalue之间的约束。如果你想确保你的map被正确地填充(即创建这样的约束),请将map隐藏在一些API后面,该API将检查正确性,例如:

public <T> void registerInterface(Class<T> clazz, MyInterface<T> intf) {
    map.put(clazz, intf);
}

然后,只需调用registerInterface而不是手动填充映射。

map.put(clazz, intf) 我想是这样的 :) - Aif

3
据我所知,在Java中无法像你描述的那样声明一个Map。你能做的只有执行类型检查和/或添加约束条件。
Guava提供了一种接近你问题的解决方案,即使用ClassToInstanceMap。因此,一种方法是使用MapConstraints.constrainedMap(如下面的示例所示)。
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

import com.google.common.collect.MapConstraint;
import com.google.common.collect.MapConstraints;

public class Main {

    interface MyInterface<T> {
        void doSomething(T object);

        Class<T> getType();
    }

    static class MyObjectImpl implements MyInterface<Object> {
        @Override
        public void doSomething(Object object) {
            System.out.println("MyObjectImpl doSomething");
        }

        @Override
        public Class<Object> getType() {
            return Object.class;
        }
    }

    static class MyStringImpl implements MyInterface<String> {
        @Override
        public void doSomething(String object) {
            System.out.println("MyStringImpl doSomething");
        }

        @Override
        public Class<String> getType() {
            return String.class;
        }
    }

    static class MyIntegerImpl implements MyInterface<Integer> {
        @Override
        public void doSomething(Integer object) {
            System.out.println("MyIntegerImpl doSomething");
        }

        @Override
        public Class<Integer> getType() {
            return Integer.class;
        }
    }

    public static void main(String[] args) throws ParseException {

        Map<Class<?>, MyInterface<?>> map = MapConstraints.constrainedMap(new HashMap<Class<?>, Main.MyInterface<?>>(),
                new MapConstraint<Class<?>, MyInterface<?>>() {
                    @Override
                    public void checkKeyValue(Class<?> key, MyInterface<?> value) {
                        if (value == null) {
                            throw new NullPointerException("value cannot be null");
                        }
                        if (value.getType() != key) {
                            throw new IllegalArgumentException("Value is not of the correct type");
                        }
                    }
                });
        map.put(Integer.class, new MyIntegerImpl());
        map.put(String.class, new MyStringImpl());
        map.put(Object.class, new MyObjectImpl());
        map.put(Float.class, new MyIntegerImpl()); //<-- Here you will get an exception
    }
}

+1 因为非常信息丰富,但在我目前的看法下,另一个答案更为通用。 - User

0
把你的方法签名改为:
public static <T> void myMethod(Map<Class<? extends T>, MyInterface<? extends T>> map) {

}

现在你的声明和调用应该可以正常工作了。

Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>();
    map.put(Integer.class, new MyIntegerImpl());
    map.put(String.class, new MyStringImpl());
    map.put(Object.class, new MyObjectImpl());
    myMethod(map);

但是这个映射仍然允许您执行map.put(Integer.class, MyStringImpl()),这正是问题所不需要的。顺便说一下,楼主已经在他的问题中提出了这个建议。 - Guillaume Polet

0

我认为这是不可能的:

Class<T> 只接受 T.class 作为值。即使 Object 是 String 的超类,Class<Object> 也不会接受 String.class

因此,任何以 Class<T> 为键的映射只能有一个元素,其键值为 T.class,无论 T 的值如何。

编译器只接受具有明确 T 值的映射作为参数。您不能编写 Map<Class<?>, MyInterface<?>>,因为每个 ? 都被认为是不同的:它与需要 T 具有相同值的 Map<Class<T>, MyInterface<T>> 不匹配。

话虽如此,myMethod 只接受单条目映射,这似乎没有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接