Java 类型安全映射 - 可以对两种通用类型进行类型检查吗?
Java Typesafe maps - TypeCheck for both generic types possible?
我正在为从 POJO 转换到 POJO 的类型安全映射创建一个库,根据键保存数据,目的是提供类型安全。键不是任意的,而是像枚举这样的固定常量。我检查了 this and this 但没有检查下面提到的 2 个条件的解决方案。
我有 2 个 类 Map
和 Key
。 Map 具有 get
方法,其中 return 给定键的值。有2个条件:
- A:
Key
应该匹配 Map
的通用类型。
- B:
get
应该 return 值匹配 Key
的通用类型。
我希望在编译时检查这两个条件。我不想传递多余的 Class 或类型参数。
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
目前的解决方案:
我找到了一个解决方案,但它很笨拙,并在客户端生成原始类型的警告:让 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
似乎没有办法在客户端代码中删除这种原始类型的警告。
编辑:
之前回答的内容不对,我删掉了。
我找到了一个可以满足您需求的解决方案,它是完全类型安全的并且避免了原始类型,但是会产生更复杂的键声明。地图的使用将如您所愿。
首先定义两个不同的Key接口:
第一个声明接受一个泛型类型,它不是键值的类型,而是键本身的类型
public interface Key<T extends Key<T>> {
}
第二个声明定义了一个 Key 类型:
public interface TypedKey<T, K extends Key<K>> extends Key<K> {
}
现在我们可以这样定义地图了:
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 相同。
通过这个简单的测试class你可以看到结果:
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 的不需要的扩展,我们必须将它们定义为内部 classes,这样:
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
*/
}
}
正如您从示例中看到的那样,现在不再允许以前的一些不良行为,但我们仍然存在问题。
这是最终的解决方案:
您需要的唯一更改是包装器 classes,将它们变成 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 的唯一扩展(私有修饰符),获取它们实例的唯一方法是通过工厂方法。
现在测试class:
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 classes 获得。如果你想在兼容的 TypedKeys 上创建一个层次结构,唯一的限制是你必须在 Wrapper 中将它们声明为私有内部 classes 并通过工厂方法公开它们。
我正在为从 POJO 转换到 POJO 的类型安全映射创建一个库,根据键保存数据,目的是提供类型安全。键不是任意的,而是像枚举这样的固定常量。我检查了 this and this 但没有检查下面提到的 2 个条件的解决方案。
我有 2 个 类 Map
和 Key
。 Map 具有 get
方法,其中 return 给定键的值。有2个条件:
- A:
Key
应该匹配Map
的通用类型。 - B:
get
应该 return 值匹配Key
的通用类型。
我希望在编译时检查这两个条件。我不想传递多余的 Class 或类型参数。
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
目前的解决方案:
我找到了一个解决方案,但它很笨拙,并在客户端生成原始类型的警告:让 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
似乎没有办法在客户端代码中删除这种原始类型的警告。
编辑:
之前回答的内容不对,我删掉了。
我找到了一个可以满足您需求的解决方案,它是完全类型安全的并且避免了原始类型,但是会产生更复杂的键声明。地图的使用将如您所愿。
首先定义两个不同的Key接口: 第一个声明接受一个泛型类型,它不是键值的类型,而是键本身的类型
public interface Key<T extends Key<T>> {
}
第二个声明定义了一个 Key 类型:
public interface TypedKey<T, K extends Key<K>> extends Key<K> {
}
现在我们可以这样定义地图了:
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 相同。
通过这个简单的测试class你可以看到结果:
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 的不需要的扩展,我们必须将它们定义为内部 classes,这样:
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
*/
}
}
正如您从示例中看到的那样,现在不再允许以前的一些不良行为,但我们仍然存在问题。
这是最终的解决方案:
您需要的唯一更改是包装器 classes,将它们变成 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 的唯一扩展(私有修饰符),获取它们实例的唯一方法是通过工厂方法。
现在测试class:
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 classes 获得。如果你想在兼容的 TypedKeys 上创建一个层次结构,唯一的限制是你必须在 Wrapper 中将它们声明为私有内部 classes 并通过工厂方法公开它们。