Why does java.util.Properties implement Map<Obj

2019-01-11 15:41发布

问题:

The java.util.Properties class is meant to represent a map where the keys and values are both Strings. This is because Properties objects are used to read .properties files, which are text files.

So, why in Java 5 did they retrofit this class to implement Map<Object,Object> and not Map<String,String>?

The javadoc states:

Because Properties inherits from Hashtable, the put and putAll methods can be applied to a Properties object. Their use is strongly discouraged as they allow the caller to insert entries whose keys or values are not Strings. The setProperty method should be used instead. If the store or save method is called on a "compromised" Properties object that contains a non-String key or value, the call will fail.

Since the keys and values are both supposed to be Strings then why not enforce that statically by using the proper generic type?

I guess making Properties implement Map<String,String> would not be fully backward compatible with code written for pre-Java 5. If you have older code that sticks non-strings into a Properties object then that code would no longer compile with Java 5. But... isn't that a good thing? Isn't the whole point of generics to catch such type errors at compile time?

回答1:

Because they did it in a hurry in the early days of Java, and didn't realise what the implications would be four versions later.

Generics were supposed to be part of the design of Java from the beginning, but the feature was dropped as being too complicated and, at the time, unnecessary. As a result, lots of code in the standard libraries is written with the assumption of non-generic collections. It took the prototype language "Pizza" from Martin Odersky to show how they could be done fairly well while maintaining near perfect backwards compatibility, with both Java code and bytecode. The prototype led to Java 5, in which the collections classes were retrofitted with generics in a way that allowed old code to keep working.

Unfortunately, if they were to retroactively make Properties inherit from Map<String, String>, then the following previously valid code would stop working:

Map<Object, Object> x = new Properties()
x.put("flag", true)

Why anybody would do that is beyond me, but Sun's commitment to backwards compatibility in Java has gone beyond heroic into the pointless.

What's now appreciated by most educated observers is that Properties should never have inherited from Map at all. It should instead wrap around Map, exposing only those features of Map that make sense.

Since reinventing Java, Martin Odersky has gone on to create the new Scala language, which is cleaner, inherits fewer mistakes, and breaks new ground in a number of areas. If you're finding Java's niggles annoying, take a look at it.



回答2:

It was originally intended that Properties would indeed extends Hashtable<String,String>. Unfortunately the implementation of bridge methods caused a problem. Properties defined in such a way causes javac to generate synthetic methods. Properties should define, say, a get method that returns a String but needs to override a method that returns Object. So a synthetic bridge method is added.

Suppose you had a class written in the bad old 1.4 days. You've overridden some methods in Properties. But what you haven't done is overridden the new methods. This leads to unintended behaviour. To avoid these bridge methods, Properties extends Hashtable<Object,Object>. Similarly Iterable does not return a (read-only) SimpleIterable, because that would have added methods to Collection implementations.



回答3:

A one-liner (two-liner for no warnings) for creating Map from Properties:

@SuppressWarnings({ "unchecked", "rawtypes" })
Map<String, String> sysProps = new HashMap(System.getProperties());


回答4:

Backwards compatibility.



回答5:

The reason: Liskov substitution principle and backwards compatibility. Properties extends Hashtable and thus must accept all messages that Hashtable would accept - and that means accepting put(Object, Object). And it has to extend plain Hashtable instead of Hashtable<String, String> because Generics were implemented in the downwards-compatibe way via type erasure, so once the compiler has done its thing, there are no generics.