Java generics with 'semantics', is this possible?
Firstly apologies if I'm using the wrong term by picking the word 'semantics.'
I'm a big fan of generics in Java for all the obvious reasons. It helps me enormously as I work with a huge variety of odd bits of code and I often have to go back to old stuff. Today I found myself with a classic parameter misplacement bug which I probably wouldn't have written in the pre-generic days - they have made me a bit lazy.
I'd like to know if there is a language feature, either in Java or perhaps as a similar concept in other languages, which takes the type safety of generics and extends it to a kind of semantic safety. Specifically I want to help trap the kind of errors which result from putting the right thing in the wrong place.
开发者_Go百科A simple example:
Map<String, String> myMap = new HashMap<String, String>();
String myKey = "key";
String myVal = "value";
myMap.put(myVal, myKey);
Which no compiler will catch. I could subclass make wrappers to give me a String
Key
type and a Value
type, I suppose. I could name my variables to indicate their use (as I've done in this example). What else?
So my questions are:
- out of academic interest, what is this concept called and what languages have this feature?
- In the absence of any such language feature, what are the best practices to avoid this kind of error?
Thanks
I have always used maps that have two different types for the key and value, never the same types. I suppose there are times when you would want to use the same type for the key and value as in your example. I can't think of any languages off the top of my head that have this feature.
Actually I have used Clojure before and for maps it uses a keyword for the key which looks like :key
and then the value is just whatever the value is. It makes it clearer but now i'm dabbling into functional programming which is not really the question you were asking. Check out Lisp-like languages.
I think its just a case of not getting them mixed up, Java certainly cannot enforce this kind of checking.
Best advice would be to use different types for the key and value so the compiler can detect the type error, or name the key and value variables explicitly as you have done.
You can't subclass String. You can create your own classes, though, and use them. So if you are mapping Persons to Roles, for instance, you can define a Person like
class Person {
String id;
String firstName;
String lastName;
...
}
and a Role like
class Role {
int id;
String name;
}
(Note these will need to define an equals and hashcode method)
and have a mapping from person to role like:
Map<Person, Set<Role>> myMap = new HashMap<Person, Set<Role>>();
so that the type system enforces what you can put where (as opposed to having a map of strings to sets of ints). Not sure what this is called but "Domain Objects" and "Abstract Data Types" seem related.
In the absence of type-checking the fallback is to write unit tests.
You could use a more extensive type system, avoiding using the same type in parameter lists.
i.e.
Map<Key, Value> myMap = new HashMap<Key, Value>();
Key myKey = // some key
Value myVal = // some value
myMap.put(myVal, myKey); //compiler error
On some level you need to explain the concepts and how they interact with operations to have the language check anything, interested in whether there are any alternative approaches out there.
I'm not aware of any language concept which would catch this. Basically what you're asking for is a mind-reading compiler. i.e.
- You want to be able to make a map of pairs.
- You want (presumably) to be able use any variable name of your choosing.
- You want the compiler to detect that you passed arguments in the incorrect order.
My only thought is that a language could impose a syntactic constraint to mimic the desired semantic constraint. For example, it could require you to name variables which are to be used as keys "key_somename_", and likewise for values. A little bit of an improvement, but it rather restricts the programmer.
If you're really worried about it, you could, in Java, extend Map to accept only types implementing a dummy "key" interface, and produce subtypes which implement that interface every time you want a key class.
This is very crappy but it works:
public static <K extends String,V extends String> void test() {
K key = (K) "foo";
V val = (V) "bar";
Map<K,V> map = new HashMap<K,V>();
map.put(key,val);
}
Now it is slightly more interesting:
// The whole code is using type parameters like this:
public static <K extends String,V extends String> void test(K key, V value) {
Map<K,V> map = new HashMap<K,V>();
map.put(key,val);
}
// At the end, use the real type (K=String, V=String);
public static void main(String[) args) {
test<String,String>("foo","bar");
};
精彩评论