Konvertieren von java.util.Properties in HashMap <String, String>

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

java.util.Propertiesist eine Implementierung von java.util.Map. java.util.HashMapDer Konstruktor von And empfängt einen MapTypparameter. Warum muss es also explizit konvertiert werden?

Tardis Xu
quelle

Antworten:

85

Dies liegt daran, dass PropertieserweitertHashtable<Object, Object> (was wiederum implementiert Map<Object, Object>). Sie versuchen, das in ein zu füttern Map<String, String>. Es ist daher nicht kompatibel.

Sie müssen die Zeichenfolgeneigenschaften einzeln in Ihre Karte einspeisen ...

Zum Beispiel:

for (final String name: properties.stringPropertyNames())
    map.put(name, properties.getProperty(name));
fge
quelle
1
Ja, aber das ist hier nicht das Problem: Die generischen Argumente stimmen nicht überein. Sie können alles füttern, was Sie wollen Hashtable<Object, Object>, auch Dinge, die keine Zeichenfolgen sind - sogar Schlüssel, die keine Zeichenfolgen sind.
fge
@assylias: Nein, das wird auch nicht kompiliert.
Jon Skeet
12
in 1,8 können Sie properties.forEach ((k, v) -> map.put ((String) k, (String) v)) ausführen;
ModdyFire
1
Oder wenn Sie die Karte noch nicht in der Hand haben properties.entrySet (). Stream (). Collect (Collectors.toMap (e -> (String) e.getKey (), e -> (String) e.getValue ( )))
Tonsic
45

Der effizienteste Weg, dies zu tun, besteht darin, wie folgt auf eine generische Karte zu übertragen:

Properties props = new Properties();

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

Dadurch wird a Map<Object, Object>in eine unformatierte Map konvertiert , was für den Compiler "ok" ist (nur Warnung). Sobald wir ein Raw Maphaben, wird es gewirkt, zu Map<String, String>dem es auch "ok" sein wird (eine weitere Warnung). Sie können sie mit Anmerkungen ignorieren@SuppressWarnings({ "unchecked", "rawtypes" })

Dies funktioniert, da das Objekt in der JVM keinen generischen Typ hat. Generische Typen sind nur ein Trick, der die Dinge beim Kompilieren überprüft.

Wenn ein Schlüssel oder Wert kein String ist, wird ein ClassCastExceptionFehler ausgegeben. Bei der aktuellen PropertiesImplementierung ist dies sehr unwahrscheinlich, solange Sie nicht die veränderlichen Aufrufmethoden von super Hashtable<Object,Object>of verwenden Properties.

Wenn Sie also mit Ihrer Properties-Instanz keine bösen Dinge tun, ist dies der richtige Weg.

Padilo
quelle
Die Frage ist, in HashMap zu konvertieren. Keine Karte.
AlikElzin-Kilaka
3
Ja, der Fragentitel sagt das aus, aber das Ziel ist es, zumindest eine MapInstanz mit dem angegebenen Code zu haben, also dachte ich, dass er das braucht
Padilo
Obwohl ich andere puristische Lösungen mag, ist diese Lösung für mich praktisch, da es sich nur um eine einfache Zeile handelt.
Alfonso Nishikawa
27

Wie wäre es damit?

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

Verursacht eine Warnung, funktioniert aber ohne Iterationen.

Seshadri Sastry
quelle
4
@fge: Es ist kein Map<Object, Object>, es ist ein MapArgument ( Rohtyp ). Diese Antwort ist richtig
Lukas Eder
2
Huh, ja, ich habe es mit Eclipse versucht. Einer dieser generischen Unterschiede zwischen Eclipse und Javac? .... nein, arbeitet auch mit Javac
Lukas Eder
4
Dies funktioniert, aber die Iteration findet immer noch statt. Wenn Sie sich den Quellcode für HashMap ansehen, durchläuft der Konstruktor im Wesentlichen den generischen Map-Parameter. Die Rechenzeit ändert sich also nicht, aber der Code ist sicherlich prägnanter.
Simeon G
Wie in der vorherigen Antwort angegeben, muss keine neue Instanz erstellt und das Eigenschaftenobjekt durchlaufen werden. Verwenden Sie einfach eine Folge von Besetzungen: (Map<String, String>) ((Map) properties)
Ricardo Veloso
22

Der Java 8 Weg:

properties.entrySet().stream().collect(
    Collectors.toMap(
         e -> e.getKey().toString(),
         e -> e.getValue().toString()
    )
);
Ben McCann
quelle
Gibt es eine Möglichkeit, anstelle von Lambda eine Methodenreferenz zu verwenden? Cos von Sonar Qube Fragen.
Viyaan Jhiingade
16

PropertiesGeräte Map<Object, Object>- nicht Map<String, String>.

Sie versuchen, diesen Konstruktor aufzurufen:

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

... mit Kund Vbeides als String.

Aber Map<Object, Object>ist nicht Map<? extends String, ? extends String>... es kann Schlüssel und Werte enthalten, die keine Zeichenfolgen sind.

Das würde funktionieren:

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

... aber es wäre nicht so nützlich für dich.

Grundsätzlich Propertieshätte niemals eine Unterklasse von HashTable... das ist das Problem. Seit v1 ist es immer möglich, Nicht-String-Schlüssel und -Werte zu speichern, obwohl dies gegen die Absicht ist. Wenn stattdessen Komposition verwendet worden wäre, hätte die API nur mit Zeichenfolgenschlüsseln / -werten funktionieren können, und alles wäre gut gewesen.

Vielleicht möchten Sie so etwas:

Map<String, String> map = new HashMap<String, String>();
for (String key : properties.stringPropertyNames()) {
    map.put(key, properties.getProperty(key));
}
Jon Skeet
quelle
anscheinend ist es auch nicht möglich, dies explizit zu tun Properties<String,String> properties = new Properties<String,String>();. Eigenartig.
Eis
1
@eis Das ist beabsichtigt, Propertiesselbst ist nicht generisch.
Mattias Buelens
Ich würde eher sagen, dass es eher eine unglückliche Reihe von Entscheidungen als eine Absicht ist, aber ja.
Eis
2
@eis: Nein, es ist beabsichtigt, dass Eigenschaften eine String-zu-String-Zuordnung sein sollen. Es macht Sinn, dass es nicht generisch ist. Es ist nicht sinnvoll, dass Sie Schlüssel / Werte ohne Zeichenfolge hinzufügen können.
Jon Skeet
8

Wenn Sie wissen, dass Ihr PropertiesObjekt nur <String, String>Einträge enthält , können Sie auf einen Rohtyp zurückgreifen:

Properties properties = new Properties();
Map<String, String> map = new HashMap<String, String>((Map) properties);
Lukas Eder
quelle
8

Ich würde die folgende Guava-API verwenden: com.google.common.collect.Maps # fromProperties

Properties properties = new Properties();
Map<String, String> map = Maps.fromProperties(properties);
Vatsal Mevada
quelle
4

Das Problem ist, dass Propertiesimplementiert Map<Object, Object>, während der HashMapKonstruktor a erwartet Map<? extends String, ? extends String>.

Diese Antwort erklärt diese (ziemlich kontraintuitive) Entscheidung. Kurzum: vor Java 5 Propertiesimplementiert Map(da es damals keine Generika gab). Dies bedeutete, dass Sie jedes Object in ein PropertiesObjekt einfügen konnten. Dies ist noch in der Dokumentation:

Da Propertieserbt von Hashtable, können die Methoden putund putAllauf ein PropertiesObjekt angewendet werden. Von ihrer Verwendung wird dringend abgeraten, da der Anrufer Einträge einfügen kann, deren Schlüssel oder Werte nicht Strings sind. Die setPropertyMethode sollte stattdessen verwendet werden.

Um die Kompatibilität damit aufrechtzuerhalten, hatten die Designer keine andere Wahl, als es Map<Object, Object>in Java 5 zu erben . Es ist ein unglückliches Ergebnis des Strebens nach vollständiger Abwärtskompatibilität, das neuen Code unnötig kompliziert macht.

Wenn Sie in Ihrem PropertiesObjekt immer nur Zeichenfolgeneigenschaften verwenden , sollten Sie in der Lage sein, mit einer ungeprüften Umwandlung in Ihrem Konstruktor davonzukommen:

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

oder ohne Kopien:

Map<String, String> map = (Map<String, String>) properties;
Mattias Buelens
quelle
Dies ist die Signatur des HashMap-Konstruktors public HashMap(Map<? extends K, ? extends V> m). Es erwartet keineMap<String, String>
Mubin
@ Mubin Okay, ich habe die Sache ein bisschen vereinfacht. Dennoch gilt das Argument: a Map<Object, Object>kann nicht für ein formales Argument vom Typ `Map <verwendet werden? erweitert String ,? erweitert String> `.
Mattias Buelens
2

Dies liegt nur daran, dass der Konstruktor von HashMap ein Argument vom generischen Map-Typ benötigt und Properties Map implementiert.

Dies wird funktionieren, allerdings mit einer Warnung

    Properties properties = new Properties();
    Map<String, String> map = new HashMap(properties);
Evgeniy Dorofeev
quelle
0

Erste Sache,

Die Eigenschaftenklasse basiert auf Hashtable und nicht auf Hashmap. Die Eigenschaftenklasse erweitert Hashtable grundsätzlich

In der HashMap-Klasse gibt es keinen solchen Konstruktor, der ein Eigenschaftenobjekt verwendet und Ihnen ein Hashmap-Objekt zurückgibt. Was Sie also tun, ist NICHT korrekt. Sie sollten in der Lage sein, das Objekt von Eigenschaften in eine Hashtabellenreferenz umzuwandeln.

Juned Ahsan
quelle
0

ich benutze das:

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

Sie können dies verwenden:

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

props.forEach((key, value) -> map.put(key.toString(), value.toString()));
Raman Sahasi
quelle