Convertendo java.util.Properties em HashMap <String, String>

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

java.util.Propertiesé uma implementação de java.util.Map, java.util.HashMapo construtor de And recebe um Mapparâmetro de tipo. Então, por que ele deve ser convertido explicitamente?

Tardis Xu
fonte

Respostas:

89

Isso ocorre porque PropertiesestendeHashtable<Object, Object> (que, por sua vez, implementa Map<Object, Object>). Você tenta alimentar isso em um Map<String, String>. Portanto, é incompatível.

Você precisa alimentar as propriedades da string, uma por uma, em seu mapa ...

Por exemplo:

for (final String name: properties.stringPropertyNames())
    map.put(name, properties.getProperty(name));
fge
fonte
1
Sim, mas esse não é o problema aqui: os argumentos genéricos não coincidem. Você pode alimentar o que quiser em um Hashtable<Object, Object>, até mesmo coisas que não sejam strings - até mesmo chaves que não sejam strings.
fge
@assylias: Não, isso também não vai compilar.
Jon Skeet de
13
em 1,8 você pode fazer properties.forEach ((k, v) -> map.put ((String) k, (String) v));
ModdyFire de
1
Ou se você ainda não tiver o mapa em mãos, properties.entrySet (). Stream (). Collect (Collectors.toMap (e -> (String) e.getKey (), e -> (String) e.getValue ( )))
Tonsic
47

A maneira eficiente de fazer isso é lançar para um mapa genérico da seguinte maneira:

Properties props = new Properties();

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

Isso converterá um Map<Object, Object>em um mapa bruto, que é "ok" para o compilador (apenas aviso). Assim que tivermos um raw, Mapele será lançado para o Map<String, String>qual também estará "ok" (outro aviso). Você pode ignorá-los com anotações@SuppressWarnings({ "unchecked", "rawtypes" })

Isso funcionará porque na JVM o objeto não tem realmente um tipo genérico. Tipos genéricos são apenas um truque que verifica as coisas em tempo de compilação.

Se alguma chave ou valor não for uma String, ocorrerá um ClassCastExceptionerro. Com a Propertiesimplementação atual, é muito improvável que isso aconteça, contanto que você não use os métodos de chamada mutáveis ​​do super Hashtable<Object,Object>de Properties.

Portanto, se não fizer coisas desagradáveis ​​com sua instância de Propriedades, este é o caminho a seguir.

Padilo
fonte
A questão é converter para HashMap. Nenhum mapa.
AlikElzin-kilaka
3
Sim, o título da pergunta diz isso, mas o objetivo é ter uma Mapinstância pelo menos no código fornecido, então pensei que isso é o que ele precisa
padilo
Embora eu goste de outras soluções puristas, essa solução é útil para mim, pois é apenas uma linha simples.
Alfonso Nishikawa
28

Que tal agora?

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

Irá causar um aviso, mas funciona sem iterações.

Seshadri Sastry
fonte
4
@fge: Não é um Map<Object, Object>, é um Mapargumento (tipo bruto). Esta resposta está correta
Lukas Eder
2
Huh, sim, eu tentei com Eclipse. Uma dessas diferenças genéricas entre Eclipse e javac de novo? .... não, funciona com javac também
Lukas Eder
4
Isso funciona, mas a iteração ainda acontece. Se você olhar o código-fonte do HashMap, o construtor basicamente itera por meio do parâmetro de mapa genérico. Portanto, o tempo de cálculo não muda, mas o código é certamente mais conciso.
Simeon G
Como a resposta anterior afirmou, não há necessidade de criar uma nova instância e iterar sobre o objeto de propriedades. Basta usar uma sequência de elencos: (Map<String, String>) ((Map) properties)
Ricardo Veloso
23

O método Java 8:

properties.entrySet().stream().collect(
    Collectors.toMap(
         e -> e.getKey().toString(),
         e -> e.getValue().toString()
    )
);
Ben McCann
fonte
Existe uma maneira de usar a referência de método em vez de lambda. Por causa dos problemas do sonar.
Viyaan Jhiingade
17

Propertiesimplementos Map<Object, Object>- não Map<String, String>.

Você está tentando chamar este construtor:

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

... com Ke Vambos como String.

Mas Map<Object, Object>não é Map<? extends String, ? extends String>... pode conter chaves e valores que não sejam de string.

Isso funcionaria:

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

... mas não seria tão útil para você.

Fundamentalmente, Propertiesnunca deveria ter sido feita uma subclasse de HashTable... esse é o problema. Desde v1, ele sempre foi capaz de armazenar chaves e valores não String, apesar de ser contra a intenção. Se a composição tivesse sido usada em vez disso, a API poderia ter funcionado apenas com chaves / valores de string e tudo estaria bem.

Você pode querer algo assim:

Map<String, String> map = new HashMap<String, String>();
for (String key : properties.stringPropertyNames()) {
    map.put(key, properties.getProperty(key));
}
Jon Skeet
fonte
aparentemente, também não é possível fazer explicitamente Properties<String,String> properties = new Properties<String,String>();. Peculiar.
eis
1
@eis Isso é intencional, em Propertiessi não é genérico.
Mattias Buelens de
Eu prefiro dizer que é por uma série de escolhas infelizes do que pelo design, mas sim.
eis
2
@eis: Não, é por design que Propriedades deve ser um mapa string-a-string. Faz sentido que não seja genérico. Não faz sentido você adicionar chaves / valores que não sejam strings.
Jon Skeet de
8

Se você sabe que seu Propertiesobjeto contém apenas <String, String>entradas, pode recorrer a um tipo bruto:

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

O problema é que Propertiesimplementa Map<Object, Object>, enquanto o HashMapconstrutor espera um Map<? extends String, ? extends String>.

Essa resposta explica essa decisão (bastante contra-intuitiva). Resumindo: antes do Java 5, Propertiesimplementado Map(já que não havia genéricos naquela época). Isso significa que você pode colocar qualquer Object um em um Propertiesobjeto. Isso ainda está na documentação:

Como Propertiesherda de Hashtable, os métodos pute putAllpodem ser aplicados a um Propertiesobjeto. Seu uso é fortemente desencorajado, pois permitem que o chamador insira entradas cujas chaves ou valores não sejam Strings. O setPropertymétodo deve ser usado em seu lugar.

Para manter a compatibilidade com isso, os designers não tiveram outra escolha a não ser fazer com que ele herdasse Map<Object, Object>em Java 5. É um resultado infeliz do esforço para obter compatibilidade total com versões anteriores, o que torna o novo código desnecessariamente complicado.

Se você sempre usa propriedades de string em seu Propertiesobjeto, deverá conseguir fazer uma conversão não verificada em seu construtor:

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

ou sem quaisquer cópias:

Map<String, String> map = (Map<String, String>) properties;
Mattias Buelens
fonte
Esta é a assinatura do construtor HashMap public HashMap(Map<? extends K, ? extends V> m). Ele não espera umMap<String, String>
Mubin
@Mubin Ok, simplifiquei um pouco o assunto. Ainda assim, o argumento é válido: a Map<Object, Object>não pode ser usado para um argumento formal do tipo `Map <? extends String,? extends String> `.
Mattias Buelens
2

isso ocorre apenas porque o construtor de HashMap requer um argumento do tipo genérico Map e Properties implementa Map.

Isso funcionará, embora com um aviso

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

Você pode usar isto:

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

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

Primeira coisa,

A classe Properties é baseada em Hashtable e não em Hashmap. A classe Properties basicamente estende Hashtable

Não existe tal construtor na classe HashMap que pega um objeto de propriedades e retorna um objeto hashmap. Portanto, o que você está fazendo NÃO é correto. Você deve ser capaz de converter o objeto de propriedades para referência de hashtable.

Juned Ahsan
fonte
0

Eu uso isso:

for (Map.Entry<Object, Object> entry:properties.entrySet()) {
    map.put((String) entry.getKey(), (String) entry.getValue());
}
átomo
fonte