Java Typesafe映射 - 两种泛型类型的TypeCheck可能吗?

时间:2017-02-08 08:49:25

标签: java generics dto typesafe nested-generics

我正在为从和转换到POJO的Typesafe地图创建一个库,保存数据以保证类型安全。键不是任意的,而是固定的常量,如枚举。我已经检查了thisthis,但没有解决方案来检查下面提到的2个条件。

我有2个课程MapKey。 Map具有get方法,该方法返回给定Key的值。有两个条件:

  • Key应与Map的通用类型相匹配。
  • B get应返回与Key的通用类型匹配的值。

我希望在编译时检查这两个条件。我不想传递冗余的类或类型参数。

class Map<K extends Key<?>{
    <T> T get1(K key) {...} //My first attempt: Solves A(checks key type) but not B(return type T)
    <T> T get2(Key<T> key) {...} //Another attempt: Solves B(checks return type T) but not A(key type)

    // I want to write method get, which checks both conditions something like this:
    <T,K1 extends K & Key<T>> T get(K1 key){...}//Won't compile ofcourse
}
interface/*or abstract class*/ Key<T>{
  //We could use enum instead of subclassing Key, but java enum constants are not generic. See: http://openjdk.java.net/jeps/301
}

这可能看起来很复杂,但在客户端代码下方显示使用它是微不足道的。

//Client Code
class PersonKey<T> extends Key<T>{
  PersonKey<String> name=new PersonKey<>();
  PersonKey<Integer> age=new PersonKey<>();
}
class HouseKey<T> extends Key<T>{
  HouseKey<String> name=new HouseKey<>();
  HouseKey<String> address=new HouseKey<>();
}
//Usage:
Map<PersonKey<?>> person=new Map<>();
String name=person.get(PersonKey.name);//Intended use
Integer age=person.get(PersonKey.age);//Intended use
String name=person.get(HouseKey.name);//A: This should generate compile error: Arg must be PersonKey, not HouseKey
Integer age=person.get(PersonKey.name);//B: This should generate compile error: must return String, not Integer

get1解决 A get2解决 B ,但我找不到检查两者的方法。

理论上有一个不切实际的方法是使用2个相同的参数来实现这一点,key1检查A,key2检查B:

class Map<K extends Key<?>>{
    <T> T strangeGet(K key1,Key<T> key2) {assert key1==key2;  ... }
}
//Client Use as follows:
String name=person.get(HouseKey.name,HouseKey.name);//A achieved
Integer age=person.get(PersonKey.name,PersonKey.name);//B achieved

到目前为止的解决方案:

我找到了一个解决方案,但它很尴尬并在客户端生成原始类型警告:Let Key有2个T类型和ActualKey。

class Map<K extends Key<?,?>>{
    <T> T get(Key<T, K> key) {...}
}
class Key<T,ActualKey extends Key<?,?>> {...}
//Client Code
class PersonKey<T> extends Key<T,PersonKey> {//raw-type warning
    PersonKey<String> name=new PersonKey<>();
    PersonKey<Integer> age=new PersonKey<>();
}
class HouseKey<T> extends Key<T,HouseKey>{//raw-type warning
    HouseKey<String> name=new HouseKey<>();
    HouseKey<String> address=new HouseKey<>();
}
//Usage:
Map<PersonKey> person=new Map<>();//raw-type warning
String name=person.get(PersonKey.name);//Intended use
Integer age=person.get(PersonKey.age);//Intended use
String name=person.get(HouseKey.name);//A: Successfully generates compile error: Arg must be PersonKey, not HouseKey
Integer age=person.get(PersonKey.name);//B: Successfully generates compile error: must return String, not Integer

似乎无法在客户端代码中删除此原始类型警告。

1 个答案:

答案 0 :(得分:0)

修改

我删除了之前的答案内容,因为错了。

我找到了一个可以满足您需求的解决方案,它是完全类型安全的并且避免了原始类型,但产生了更复杂的密钥声明。您可以根据需要使用地图。

首先定义两个不同的Key接口: 第一个声明接受一个泛型类型,它不是键值的类型,而是键本身的类型

public interface Key<T extends Key<T>> {

}

第二个声明定义了一个具有类型的键:

public interface TypedKey<T, K extends Key<K>> extends Key<K> {

}

现在我们可以通过这种方式定义Map:

public class Map<K extends Key<K>> {

    <T, K1 extends TypedKey<T, K>> T get(K1 key) {
        return null; // TODO implementation
    }

}

这需要在实例化时实现Key的类型,并且每次调用get方法时都需要TypedKey,它与第二个泛型类型的Key相同。

使用这个简单的测试类,您可以看到结果:

public class Tester {

    static final class PersonKey implements Key<PersonKey> {
        private PersonKey() {}
    }

    static final class HouseKey implements Key<HouseKey> {
        private HouseKey() {}
    }

    static final class WrongKey implements Key<PersonKey> {
        private WrongKey() {}
    }

    static class ExtendableKey implements Key<ExtendableKey> {

    }

    static class ExtensionKey extends ExtendableKey {

    }

    static class PersonTypedKey<T> implements TypedKey<T, PersonKey> {

    }

    static class HouseTypedKey<T> implements TypedKey<T, HouseKey> {

    }

    /*static class ExtensionTypedKey<T> implements TypedKey<T, ExtensionKey> { // wrong type

    }

    static class WrongTypedKey<T> implements TypedKey<T, WrongKey> { // wrong type

    }*/

    public static void main(String[] args) {
        Map<PersonKey> personMap = new Map<>();
        Map<HouseKey> houseMap = new Map<>();
        //Map<WrongKey> wrongMap = new Map<>(); // wrong type
        //Map<ExtensionKey> extMap = new Map<>(); // wrong type
        PersonTypedKey<String> name = new PersonTypedKey<>();
        PersonTypedKey<Integer> age = new PersonTypedKey<>();
        HouseTypedKey<String> houseName = new HouseTypedKey<>();
        String nameString = personMap.get(name);
        Integer ageInt = personMap.get(age);
        //String houseString = personMap.get(houseName); // wrong type
        //ageInt = personMap.get(name); wrong type
        Map<ExtendableKey> extMap = new Map<>();

        whatMayBeWrongWithThis();
    }

    static class OtherPersonTypedKey<T> implements TypedKey<T, PersonKey> {

    }

    static class ExtendedPersonTypedKey<T> extends PersonTypedKey<T> {

    }

    public static void whatMayBeWrongWithThis() {
        Map<PersonKey> map = new Map<>();
        String val1 = map.get(new OtherPersonTypedKey<String>());
        String val2 = map.get(new ExtendedPersonTypedKey<String>());
        /*
         * TypedKey inheritance can be disallowed be declaring the class final, OtherPersonTypedKey can not be disallowed
         * with those declarations
         */
    }

    // if needed you can allow Key inheritance by declaring key, typedKey and map with extends Key<? super K>
}

我还评论了一些没有编译的示例代码。在上面的代码中,您可以看到我按照您的示例定义了PersonKey和HouseKey,然后我定义了它们的类型版本,它们是您实际使用的实现。我定义了PersonKey和HouseKey final以防止扩展(我添加了一条解释如何添加继承支持的注释)和一个私有构造函数,以防止实例化(如果不需要可以删除)。 还有一个名为whatMayBeWrongWithThis的方法,它解释了为什么这个解决方案可能不是你需要的那个。

但我也找到了解决这些问题的解决方案:

首先,我们必须更改TypedKey的定义和Map中的get方法:

public interface TypedKey<T, K extends TypedKey<T, K>> {

}

public class Map<K extends Key<K>> {

    <T, K1 extends TypedKey<T, ? extends K>> T get(K1 key) {
        return null; // TODO implementation
    }

}

现在TypedKey没有扩展Key,第二个泛型类型必须是TypedKey本身的扩展。所以get方法更具限制性,只允许扩展K的TypedKeys。

我们还必须更改PersonKey和HouseKey的定义,但是为了防止我们的Keys的不必要的扩展,我们必须以这种方式将它们定义为内部类:

public class PersonKeyWrapper {

    public static class PersonKey implements Key<PersonKey> {
        private PersonKey() {}
    }

    public static class PersonTypedKey<T> extends PersonKey implements TypedKey<T, PersonTypedKey<T>> {

    }

}

public class HouseKeyWrapper {

    public static class HouseKey implements Key<HouseKey> {
        private HouseKey() {}
    }

    public static class HouseTypedKey<T> extends HouseKey implements TypedKey<T, HouseTypedKey<T>> {

    }

}

因此可以从外部看到PersonKey和HouseKey,但由于私有构造函数而没有扩展,因此唯一可能的扩展是我们提供的TypedKeys。

这里有一个使用代码的例子:

public class Tester {

    /*static class PersonTypedKey<T> implements TypedKey<T, PersonKey> { // no more allowed

    }

    static class HouseTypedKey<T> implements TypedKey<T, HouseKey> { // no more allowed

    }*/

    public static void main(String[] args) {
        Map<PersonKey> personMap = new Map<>();
        Map<HouseKey> houseMap = new Map<>();
        PersonTypedKey<String> name = new PersonTypedKey<>();
        PersonTypedKey<Integer> age = new PersonTypedKey<>();
        HouseTypedKey<String> houseName = new HouseTypedKey<>();
        String nameString = personMap.get(name);
        Integer ageInt = personMap.get(age);
        //String houseString = personMap.get(houseName); // wrong type
        //ageInt = personMap.get(name); wrong type

        whatMayBeWrongWithThis();
    }

    /*static class OtherPersonTypedKey<T> implements TypedKey<T, PersonKey> { no more allowed

    }*/

    static class ExtendedPersonTypedKey<T> extends PersonTypedKey<T> { // allowed, you can declare PersonTypedKey final if you don't wont't to allow this

    }

    static class OtherPersonTypedKey<T> implements TypedKey<T, PersonTypedKey<T>> {

    }

    public static void whatMayBeWrongWithThis() {
        Map<PersonKey> map = new Map<>();
        String val1 = map.get(new OtherPersonTypedKey<String>());
        String val2 = map.get(new ExtendedPersonTypedKey<String>());
        /*
         * OtherPersonTypedKey can not be disallowed with this declaration of PersonTypedKey
         */
    }
}

从示例中可以看出,现在不再允许以前的一些不受欢迎的行为,但我们仍有问题。

以下是最终解决方案:

您需要的唯一更改是在Wrapper类中,将它们转换为TypedKeys的工厂:

public class PersonKeyWrapper {

    public static class PersonKey implements Key<PersonKey> {
        private PersonKey() {
        }
    }

    private static class PersonTypedKey<T> extends PersonKey implements TypedKey<T, PersonTypedKey<T>> {

    }

    public static <T> TypedKey<T, ? extends PersonKey> get() {
        return new PersonTypedKey<T>();
    }

}

public class HouseKeyWrapper {

    public static class HouseKey implements Key<HouseKey> {
        private HouseKey() {}
    }

    private static class HouseTypedKey<T> extends HouseKey implements TypedKey<T, HouseTypedKey<T>> {

    }

    public static <T> TypedKey<T, ? extends HouseKey> get() {
        return new HouseTypedKey<T>();
    }

}

现在PersonKey和HouseKey的唯一扩展是隐藏的(私有修饰符),获取它们实例的唯一方法是通过工厂方法。

现在是测试类:

public class Tester {

    public static void main(String[] args) {
        Map<PersonKey> personMap = new Map<>();
        Map<HouseKey> houseMap = new Map<>();
        TypedKey<String, ? extends PersonKey> name = PersonKeyWrapper.get();
        TypedKey<Integer, ? extends PersonKey> age = PersonKeyWrapper.get();
        TypedKey<String, ? extends HouseKey> houseName = HouseKeyWrapper.get();
        String nameString = personMap.get(name);
        Integer ageInt = personMap.get(age);
        //String houseString = personMap.get(houseName); // wrong type
        //ageInt = personMap.get(name); wrong type
    }

    /*static class ExtendedPersonTypedKey<T> extends PersonTypedKey<T> { // no more allowed

    }

    static class OtherPersonTypedKey<T> implements TypedKey<T, PersonTypedKey<T>> { // no more allowed

    }*/
}

正如您所看到的,我们已经将Map绑定为一个非常特定的类型,只能通过Wrapper类获得。如果要在兼容的TypedKeys上创建层次结构,唯一的限制是必须在Wrapper中将它们声明为私有内部类,并通过工厂方法公开它们。