104
Properties properties = new Properties();
Map<String, String> map = new HashMap<String, String>(properties);// why wrong?

java.util.Properties is a implementation of java.util.Map, And java.util.HashMap's constructor receives a Map type param. So, why must it be converted explicitly?

bruno
  • 2,213
  • 1
  • 19
  • 31
Tardis Xu
  • 1,161
  • 2
  • 7
  • 9

14 Answers14

103

This is because Properties extends Hashtable<Object, Object> (which, in turn, implements Map<Object, Object>). You attempt to feed that into a Map<String, String>. It is therefore incompatible.

You need to feed string properties one by one into your map...

For instance:

for (final String name: properties.stringPropertyNames())
    map.put(name, properties.getProperty(name));
fge
  • 119,121
  • 33
  • 254
  • 329
  • 1
    Yes, but that is not the problem here: the generic arguments don't match. You can feed whatever you want in a `Hashtable`, even things which are not strings -- even _keys_ which are not strings. – fge Jun 20 '13 at 08:56
  • @assylias: No, that won't compile either. – Jon Skeet Jun 20 '13 at 08:58
  • 16
    in 1,8 you can do properties.forEach( (k,v) -> map.put((String)k, (String)v)); – ModdyFire Feb 19 '17 at 03:11
  • 2
    Or if you don't have already the map in hand properties.entrySet().stream().collect(Collectors.toMap(e->(String)e.getKey(),e->(String)e.getValue())) – Tonsic May 30 '18 at 18:10
54

The efficient way to do that is just to cast to a generic Map as follows:

Properties props = new Properties();

Map<String, String> map = (Map)props;

This will convert a Map<Object, Object> to a raw Map, which is "ok" for the compiler (only warning). Once we have a raw Map it will cast to Map<String, String> which it also will be "ok" (another warning). You can ignore them with annotation @SuppressWarnings({ "unchecked", "rawtypes" })

This will work because in the JVM the object doesn't really have a generic type. Generic types are just a trick that verifies things at compile time.

If some key or value is not a String it will produce a ClassCastException error. With current Properties implementation this is very unlikely to happen, as long as you don't use the mutable call methods from the super Hashtable<Object,Object> of Properties.

So, if don't do nasty things with your Properties instance this is the way to go.

padilo
  • 996
  • 9
  • 18
39

You could use Google Guava's:

com.google.common.collect.Maps.fromProperties(Properties)

reevesy
  • 3,452
  • 1
  • 26
  • 23
Ashwin Jayaprakash
  • 2,168
  • 24
  • 29
29

The Java 8 way:

properties.entrySet().stream().collect(
    Collectors.toMap(
         e -> e.getKey().toString(),
         e -> e.getValue().toString()
    )
);
Stephan
  • 41,764
  • 65
  • 238
  • 329
Ben McCann
  • 18,548
  • 25
  • 83
  • 101
  • Is there a way to use method reference instead of lambda. Cos of sonar qube issues. – VIJ Jul 12 '19 at 11:05
28

How about this?

   Map properties = new Properties();
   Map<String, String> map = new HashMap<String, String>(properties);

Will cause a warning, but works without iterations.

eis
  • 51,991
  • 13
  • 150
  • 199
Seshadri Sastry
  • 349
  • 2
  • 3
  • 4
    @fge: It's not a `Map`, it's a `Map` argument (raw type). This answer is correct – Lukas Eder Jun 20 '13 at 09:13
  • 2
    Huh, yes I tried with Eclipse. One of those generic differences between Eclipse and javac again? .... nope, works with javac as well – Lukas Eder Jun 20 '13 at 09:17
  • 4
    This works but the iteration still happens. If you look at the source code for HashMap, the constructor basically iterates through the generic map parameter. So the computation time does not change but the code is certainly more concise. – Simeon G Aug 13 '14 at 17:37
  • As the previous answer stated, there's no need to create a new instance and iterate over the properties object. Just use a sequence of casts: `(Map) ((Map) properties)` – Ricardo Veloso Jul 11 '18 at 10:29
18

Properties implements Map<Object, Object> - not Map<String, String>.

You're trying to call this constructor:

public HashMap(Map<? extends K,? extends V> m)

... with K and V both as String.

But Map<Object, Object> isn't a Map<? extends String, ? extends String>... it can contain non-string keys and values.

This would work:

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

... but it wouldn't be as useful to you.

Fundamentally, Properties should never have been made a subclass of HashTable... that's the problem. Since v1, it's always been able to store non-String keys and values, despite that being against the intention. If composition had been used instead, the API could have only worked with string keys/values, and all would have been well.

You may want something like this:

Map<String, String> map = new HashMap<String, String>();
for (String key : properties.stringPropertyNames()) {
    map.put(key, properties.getProperty(key));
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • apparently it is also not possible to explicitly do `Properties properties = new Properties();`. Peculiar. – eis Jun 20 '13 at 09:11
  • 1
    @eis That's by design, `Properties` itself is not generic. – Mattias Buelens Jun 20 '13 at 09:12
  • I'd rather say it's by unfortunate series of choices rather than by design, but yeah. – eis Jun 20 '13 at 09:18
  • 2
    @eis: No, it's by design that Properties is meant to be a string-to-string map. It makes sense that it's not generic. It *doesn't* make sense that you can add non-string keys/values. – Jon Skeet Jun 20 '13 at 09:21
10

I would use following Guava API: com.google.common.collect.Maps#fromProperties

Properties properties = new Properties();
Map<String, String> map = Maps.fromProperties(properties);
vatsal mevada
  • 5,148
  • 7
  • 39
  • 68
8

If you know that your Properties object only contains <String, String> entries, you can resort to a raw type:

Properties properties = new Properties();
Map<String, String> map = new HashMap<String, String>((Map) properties);
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
6

The problem is that Properties implements Map<Object, Object>, whereas the HashMap constructor expects a Map<? extends String, ? extends String>.

This answer explains this (quite counter-intuitive) decision. In short: before Java 5, Properties implemented Map (as there were no generics back then). This meant that you could put any Object in a Properties object. This is still in the documenation:

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.

To maintain compatibility with this, the designers had no other choice but to make it inherit Map<Object, Object> in Java 5. It's an unfortunate result of the strive for full backwards compatibility which makes new code unnecessarily convoluted.

If you only ever use string properties in your Properties object, you should be able to get away with an unchecked cast in your constructor:

Map<String, String> map = new HashMap<String, String>( (Map<String, String>) properties);

or without any copies:

Map<String, String> map = (Map<String, String>) properties;
Community
  • 1
  • 1
Mattias Buelens
  • 19,609
  • 4
  • 45
  • 51
  • This is the signature of HashMap constructor `public HashMap(Map extends K, ? extends V> m)`. It doesn't expects a `Map` – Mubin Jun 20 '13 at 09:01
  • @Mubin Okay, I oversimplified the matter a bit. Still, the argument holds: a `Map` cannot be used for a formal argument of type` Map extends String, ? extends String>`. – Mattias Buelens Jun 20 '13 at 09:08
2

this is only because the constructor of HashMap requires an arg of Map generic type and Properties implements Map.

This will work, though with a warning

    Properties properties = new Properties();
    Map<String, String> map = new HashMap(properties);
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
1

You can use this:

Map<String, String> map = new HashMap<>();

props.forEach((key, value) -> map.put(key.toString(), value.toString()));
Raman Sahasi
  • 30,180
  • 9
  • 58
  • 71
0

First thing,

Properties class is based on Hashtable and not Hashmap. Properties class basically extends Hashtable

There is no such constructor in HashMap class which takes a properties object and return you a hashmap object. So what you are doing is NOT correct. You should be able to cast the object of properties to hashtable reference.

Juned Ahsan
  • 67,789
  • 12
  • 98
  • 136
0

i use this:

for (Map.Entry<Object, Object> entry:properties.entrySet()) {
    map.put((String) entry.getKey(), (String) entry.getValue());
}
atom
  • 1
0

When I see Spring framework source code,I find this way

Properties props = getPropertiesFromSomeWhere();
 // change properties to map
Map<String,String> map = new HashMap(props)
Q10Viking
  • 982
  • 8
  • 9