Como comunicar que o pedido de inserção é importante em um mapa?


24

Estou buscando um conjunto de tuplas no banco de dados e colocando-o em um mapa. A consulta ao banco de dados é cara.

Não há uma ordem natural óbvia dos elementos no mapa, mas a ordem de inserção é importante. A classificação do mapa seria uma operação pesada, por isso quero evitar fazer isso, já que o resultado da consulta já está classificado da maneira que eu quero. Portanto, apenas guardo o resultado da consulta em um LinkedHashMape retorno o mapa de um método DAO:

public LinkedHashMap<Key, Value> fetchData()

Eu tenho um método processDataque deve fazer algum processamento no mapa - modificando alguns valores, adicionando algumas novas chaves / valores. É definido como

public void processData(LinkedHashMap<Key, Value> data) {...}

No entanto, vários linters (Sonar etc) reclamam que O tipo de 'dados' deve ser uma interface como 'Mapa', em vez da implementação "LinkedHashMap" ( squid S1319 ).
Então, basicamente, está dizendo que eu deveria ter

public void processData(Map<Key, Value> data) {...}

Mas quero que a assinatura do método diga que a ordem do mapa é importante - é importante para o algoritmo processData- para que meu método não seja passado apenas a qualquer mapa aleatório.

Eu não quero usar SortedMap, porque (do javadoc dejava.util.SortedMap ) "é ordenado de acordo com a ordem natural de suas chaves ou por um Comparador normalmente fornecido no momento da criação do mapa classificado".

Minhas chaves não têm uma ordem natural e criar um Comparador para não fazer nada parece detalhado.

E eu ainda gostaria que fosse um mapa, para tirar vantagem de putevitar chaves duplicadas, etc. Se não, datapoderia ter sido a List<Map.Entry<Key, Value>>.

Então, como digo que meu método deseja um mapa que esteja classificado ? Infelizmente, não há java.util.LinkedMapinterface, ou eu teria usado isso.

Respostas:


56

Então use LinkedHashMap.

Sim , você deve usar Mapuma implementação específica sempre que possível e sim , essa é uma prática recomendada.

Dito isto, é uma situação estranhamente específica em que a implementação Maprealmente importa. Isso não será verdadeiro para 99,9% dos casos em seu código quando você usar Mape, no entanto, aqui está você, nessa situação de 0,1%. O Sonar não pode saber disso e, portanto, o Sonar simplesmente pede que você evite usar a implementação específica, porque estaria correta na maioria dos casos.

Eu argumentaria que, se você puder usar uma implementação específica, não tente colocar batom em um porco. Você precisa de um LinkedHashMap, não de um Map.

Dito isto, se você é iniciante em programação e se depara com essa resposta, não pense que isso permite que você vá contra as melhores práticas, porque não. Porém, ao substituir uma implementação por outra não é aceitável, a única coisa que você pode fazer é usar essa implementação específica e ser condenado ao Sonar.


1
Abordagem pragmática, que eu gosto.
Vidar S. Ramdal

20
Eu concordo quase completamente com a resposta. Eu apenas diria que você não é condenado ao Sonar. Você sempre pode configurá-lo para ignorar esse erro / aviso específico. Veja stackoverflow.com/questions/10971968/…
Vladimir Stokic

11
if you are new to programming and stumble upon this answer, don't think this allows you to go against best practice because it doesn't.- Bom conselho, se houver algo como "melhores práticas". Melhor conselho: aprenda a tomar as decisões corretas. Siga a prática se fizer sentido, mas deixe que as ferramentas e as autoridades guiem seu processo de pensamento, não o ditem.
Robert Harvey

13
Nota: quando o sonar relatar algo, você pode fechá-lo como "não será resolvido" e deixar uma nota como o motivo. Como tal, não apenas o sonar irá parar para incomodá-lo, mas você terá um rastreador do motivo pelo qual fez isso.
Walfrat 31/10

2
Eu acho que o aspecto que faz disso uma exceção ao princípio geral é que o LinkedHashMap possui um contrato específico para essa implementação e não expresso em nenhuma interface. Este não é o caso usual. Portanto, a única maneira de expressar a confiança nesse contrato é usar o tipo de implementação.
Dana

21

Você está lutando com três coisas:

Primeiro é a biblioteca de contêineres do Java. Nada em sua taxonomia fornece uma maneira de determinar se a classe itera ou não em uma ordem previsível. Não há IteratesInInsertedOrderMapinterface que possa ser implementada LinkedHashMap, o que torna impossível a verificação de tipo (e o uso de implementações alternativas que se comportam da mesma maneira). Isso é provavelmente por design, porque o espírito disso é que você realmente deve ser capaz de lidar com objetos que se comportam como o abstrato Map.

A segunda é a crença de que o que o seu interlocutor diz deve ser tratado como evangelho e que ignorar tudo o que diz é ruim. Ao contrário do que se passa com as boas práticas nos dias de hoje, os avisos intermediários não devem ser barreiras para chamar seu código de bom. São instruções para raciocinar sobre o código que você escreveu e usar sua experiência e julgamento para determinar se o aviso é ou não justificado. Avisos injustificados são o motivo pelo qual quase todas as ferramentas de análise estática fornecem um mecanismo para informar que você examinou o código, acha que está fazendo tudo bem e que não deve reclamar no futuro.

Terceiro, e essa provavelmente é a carne, LinkedHashMappode ser a ferramenta errada para o trabalho. Os mapas destinam-se a acesso aleatório, não ordenado. Se processData()simplesmente itera sobre os registros em ordem e não precisa encontrar outros registros por chave, você está forçando uma implementação específica Mapa fazer o trabalho de a List. Por outro lado, se você precisar de ambos, LinkedHashMapé a ferramenta certa, porque é conhecido por fazer o que você deseja e você tem mais do que justificativa em exigi-lo.


2
"LinkedHashMap pode ser a ferramenta errada para o trabalho". Sim talvez. Quando digo que preciso de um OrderedMap, eu poderia muito bem dizer UniqueList. Desde que seja algum tipo de coleção com uma ordem de iteração definida, que substitua duplicatas na inserção.
Vidar S. Ramdal

2
@ VidarS.Ramdal A consulta ao banco de dados seria o local ideal para eliminar as duplicatas. Se o seu banco de dados não puder fazer isso, você poderá manter temporariamente Setapenas as chaves enquanto cria a lista como uma maneira de identificá-las.
Blrfl

Oh, vejo que causei confusão. Sim, o resultado da consulta ao banco de dados não contém duplicatas. Mas processDatamodifica o mapa, substituindo alguns valores, introduzindo algumas novas chaves / valores. Assim, processDatapoderia introduzir duplicatas se estivesse operando em algo diferente de a Map.
Vidar S. Ramdal

7
@ VidarS.Ramdal: Parece que você precisa escrever o seu próprio UniqueList(ou OrderedUniqueList) e usá-lo. É bem fácil e facilita o uso pretendido.
TMN

2
@ TMN Sim, comecei a pensar nessa direção. Se você quiser postar sua sugestão como resposta, certamente receberá meu voto positivo.
Vidar S. Ramdal

15

Se tudo o que você obtém LinkedHashMapé a capacidade de sobrescrever duplicatas, mas você realmente a usa como uma List, sugiro que seja melhor comunicar esse uso com sua própria Listimplementação personalizada . Você pode baseá-lo em uma classe de coleções Java existente e simplesmente substituir qualquer adde removemétodos para atualizar seu armazenamento de backup e manter o controle da chave para garantir a exclusividade. Atribuir a esse nome um nome distinto ProcessingListtornará claro que os argumentos apresentados ao seu processDatamétodo precisam ser tratados de uma maneira específica.


5
Esta pode ser uma boa ideia de qualquer maneira. Heck, você pode até ter um arquivo de uma linha que cria ProcessingListcomo um alias para LinkedHashMap- você sempre pode optar por substituí-lo por algo mais tarde, desde que mantenha a interface pública intacta.
CompuChip

11

Estou ouvindo você dizer "Tenho uma parte do meu sistema que produz um LinkedHashMap e, em outra parte do meu sistema, preciso aceitar apenas objetos do LinkedHashMap que foram produzidos pela primeira parte, já que os produzidos por outro processo venceram" t funciona corretamente. "

Isso me faz pensar que o problema aqui é que você está tentando usar o LinkedHashMap, pois ele se encaixa principalmente nos dados que você está procurando, mas, na verdade, não pode ser substituído por nenhuma outra instância que não seja a que você cria. O que você realmente deseja fazer é criar sua própria interface / classe, que é o que sua primeira parte cria e sua segunda parte consome. Ele pode agrupar o LinkedHashMap "real" e fornecer um getter de mapa ou implementar a interface do mapa.

Isso é um pouco diferente da resposta de CandiedOrange, pois eu recomendaria encapsular o mapa real (e delegar chamadas, conforme necessário), em vez de estendê-lo. Às vezes é uma daquelas guerras santas do estilo, mas com certeza me parece que não é "Um mapa com algumas coisas adicionais", é "Minha bolsa de informações úteis sobre o estado, que eu posso representar internamente com um mapa".

Se você tivesse duas variáveis ​​que precisaria repassar assim, provavelmente teria feito uma aula para ela sem pensar muito sobre isso. Mas às vezes é útil ter uma classe, mesmo que seja apenas uma variável de membro, apenas porque é logicamente a mesma coisa, não um "valor", mas "o resultado da minha operação com a qual preciso fazer as coisas mais tarde".


I como este pensamento - Eu estive lá :) MyBagOfUsefulInformationseria necessário um método (ou construtor) para preenchê-lo: MyBagOfUsefulInformation.populate(SomeType data). Mas dataprecisaria ser o resultado da consulta classificada. Então, o que seria SomeType, se não LinkedHashMap? Não tenho certeza se sou capaz de quebrar essa captura 22.
Vidar S. Ramdal

Por que não pode MyBagOfUsefulInformationser criado pelo DAO ou o que quer que esteja gerando os dados em seu sistema? Por que você precisa expor o mapa subjacente ao resto do seu código fora do produtor e consumidor do Bag?

Dependendo da sua arquitetura, você poderá usar um construtor privado / protegido / somente pacote para impor que o objeto possa ser criado apenas pelo produtor que você deseja. Ou você pode apenas precisar fazê-lo como uma convenção, de que ele só pode ser criado pela "fábrica" ​​correta.

Sim, eu acabei fazendo algo um pouco semelhante, passando MyBagOfUsefulInformationcomo um parâmetro para o método DAO: softwareengineering.stackexchange.com/a/360079/52573
Vidar S. Ramdal

4

O LinkedHashMap é o único mapa java que possui o recurso de pedido de inserção que você está procurando. Portanto, descartar o Princípio da Inversão da Dependência é tentador e talvez até prático. Primeiro, considere o que seria necessário para segui-lo. Aqui está o que o SOLID solicitaria que você fizesse.

Nota: substitua o nome Ramdalpor um nome descritivo que comunique que o consumidor desta interface é o proprietário dessa interface. O que torna a autoridade que decide se o pedido de inserção é importante. Se você simplesmente chamar isso, InsertionOrderMaprealmente perdeu o ponto.

public interface Ramdal {
    //ISP asks for just the methods that processData() actually uses.
    ...
}

public class RamdalLinkedHashMap extends LinkedHashMap implements Ramdal{} 

Ramdal<Key, Value> ramdal = new RamdalLinkedHashMap<>();

ramdal.put(key1, value1);
ramdal.put(key2, value2);

processData(ramdal);

Esse é um grande projeto antecipadamente? Talvez, dependa da probabilidade de você precisar de uma implementação além disso LinkedHashMap. Mas se você não está seguindo o DIP apenas porque seria uma dor enorme, não acho que a placa da caldeira seja mais dolorosa do que isso. Esse é o padrão que eu uso quando desejo que o código intocável implemente uma interface que ele não faz. A parte mais dolorosa é realmente pensar em bons nomes.


2
Eu gosto da nomeação!
Vidar S. Ramdal

1

Obrigado por muitas sugestões e bons pensamentos.

Acabei estendendo a criação de uma nova classe de mapa, criando processDataum método de instância:

class DataMap extends LinkedHashMap<Key, Value> {

   processData();

}

Em seguida, refatorei o método DAO para que ele não retorne um mapa, mas, em vez disso, pega um targetmapa como parâmetro:

public void fetchData(Map<Key, Value> target) {
  ...
  // for each result row
  target.put(key, value);
}

Portanto, preencher DataMape processar os dados agora é um processo de duas etapas, o que é bom, já que existem outras variáveis ​​que fazem parte do algoritmo, provenientes de outros lugares.

public DataMap fetchDataMap() {
  var dataMap = new DataMap();
  dao.fetchData(dataMap);
  return dataMap;
}

Isso permite que minha implementação de mapa controle como as entradas são inseridas e oculta os requisitos de pedido - agora é um detalhe de implementação DataMap.


0

Se você deseja comunicar que a estrutura de dados que você usou existe por um motivo, adicione um comentário acima da assinatura do método. Se outro desenvolvedor, no futuro, encontrar essa linha de código e perceber um aviso de ferramenta, ele poderá observar o comentário e evitar "corrigir" o problema. Se não houver comentário, nada os impedirá de alterar a assinatura.

Suprimir avisos é inferior a comentar na minha opinião, porque a supressão em si não indica o motivo pelo qual o aviso foi suprimido. Uma combinação de supressão de aviso e comentário também será adequada.


0

Então, deixe-me tentar entender seu contexto aqui:

... a ordem de inserção é importante ... Classificar o mapa seria uma operação pesada ...

... o resultado da consulta está classificado da maneira que eu quero

Agora, o que você já está fazendo no momento:

Estou buscando um conjunto de tuplas no banco de dados e colocando-o em um mapa ...

E aqui está o seu código atual:

public void processData(LinkedHashMap<Key, Value> data) {...}

Minha sugestão é fazer o seguinte:

  • Use injeção de dependência e injete algum MyTupleRepository no método de processamento (MyTupleRepository é uma interface implementada por objetos que recuperam seus objetos de tupla, geralmente do DB);
  • internamente no método de processamento, coloque os dados do repositório (também conhecido como DB, que já retorna dados ordenados) na coleção LinkedHashMap específica, porque esse é um detalhe interno do algoritmo de processamento (porque depende de como os dados são organizados na estrutura de dados );
  • Observe que isso é praticamente o que você já está fazendo, mas, neste caso, isso seria feito dentro do método de processamento. Seu repositório é instanciado em outro lugar (você já tem uma classe que retorna dados, este é o repositório neste exemplo)

Exemplo de código

public interface MyTupleRepository {
    Collection<MyTuple> GetAll();
}

//Concrete implementation of data access object, that retrieves 
//your tuples from DB; this data is already ordered by the query
public class DbMyTupleRepository implements MyTupleRepository { }

//Injects some abstraction of repository into the processing method,
//but make it clear that some exception might be thrown if data is not
//arranged in some specific way you need
public void processData(MyTupleRepository tupleRepo) throws DataNotOrderedException {

    LinkedHashMap<Key, Value> data = new LinkedHashMap<Key, Value>();

    //Represents the query to DB, that already returns ordered data
    Collection<MyTuple> myTuples = tupleRepo.GetAll();

    //Optional: this would throw some exception if data is not ordered 
    Validate(myTuples);

    for (MyTupleData t : myTuples) {
        data.put(t.key, t.value);
    }

    //Perform the processing using LinkedHashMap...
    ...
}

Eu acho que isso eliminaria o aviso do Sonar e também especificaria no layout específico da assinatura dos dados exigidos pelo método de processamento.


Hmm, mas como o repositório seria instanciado? Não seria este ser apenas movendo o problema em outro lugar (para onde MyTupleRepositoryé criado?)
Vidar S. Ramdal

Acho que vou encontrar o mesmo problema da resposta de Peter Cooper .
Vidar S. Ramdal

Minha sugestão envolve a aplicação do Princípio de Injeção de Dependência; neste exemplo; MyTupleRepository é uma interface que define a capacidade de recuperar as tuplas mencionadas (que consultam o DB). Aqui, você injeta esse objeto no método de processamento. Você já tem alguma classe que retorna os dados; isso apenas o abstrai em uma interface e você injeta o objeto no método 'processData', que usa internamente o LinkedHashMap porque isso faz parte intrinsecamente do processamento.
Emerson Cardoso

Editei minha resposta, tentando ser mais claro sobre o que estou sugerindo.
Emerson Cardoso

-1

Esta questão é na verdade um monte de problemas com o seu modelo de dados agrupado em um. Você precisa começar a desembaraçá-los, um de cada vez. Soluções mais naturais e intuitivas desaparecerão quando você tentar simplificar cada peça do quebra-cabeça.

Problema 1: você não pode depender da ordem do banco de dados

Suas descrições de classificação de seus dados não são claras.

  • O maior problema em potencial é que você não está especificando uma classificação explícita no seu banco de dados, por meio de uma ORDER BYcláusula. Se você não é porque parece muito caro, seu programa tem um bug . Os bancos de dados podem retornar resultados em qualquer ordem, se você não especificar um; você não pode confiar no retorno coincidente de dados no pedido, apenas porque você executou a consulta algumas vezes e parece que sim. A ordem pode mudar porque as linhas são reorganizadas no disco, ou algumas são excluídas e novas são substituídas ou um índice é adicionado. Você deve especificar uma ORDER BYcláusula de algum tipo. A velocidade é inútil sem correção.
  • Também não está claro o que você quer dizer com ordem de inserção importante. Se você está falando sobre o próprio banco de dados, deve ter uma coluna que realmente rastreie isso e ela deve ser incluída na sua ORDER BYcláusula. Caso contrário, você tem bugs. Se essa coluna ainda não existir, você precisará adicionar uma. Opções típicas para colunas como essa seriam uma coluna de carimbo de data / hora de inserção ou uma chave de incremento automático. A chave de incremento automático é mais confiável.

Problema 2: Tornando a classificação de memória eficiente

Depois de garantir a devolução dos dados na ordem esperada, você pode aproveitar esse fato para tornar os tipos de memória muito mais eficientes. Basta adicionar um row_number()oudense_rank() coluna (ou equivalente de seu banco de dados) para definir o resultado de sua consulta. Agora, cada linha tem um índice que fornece uma indicação direta do que o pedido deve ser, e você pode classificá-lo na memória trivialmente. Apenas certifique-se de fornecer um nome significativo ao índice (como sortedBySomethingIndex).

Viola. Agora você não precisa mais depender da ordem do conjunto de resultados do banco de dados.

Problema 3: Você precisa fazer esse processamento no código?

SQL é realmente muito poderoso. É uma linguagem declarativa incrível que permite fazer muitas transformações e agregações em seus dados. Atualmente, a maioria dos bancos de dados ainda suporta operações entre linhas. Eles são chamados de janela ou funções analíticas:

Você precisa extrair seus dados para a memória assim? Ou você poderia fazer todo o trabalho na consulta SQL usando as funções da janela? Se você pode fazer todo (ou talvez apenas uma parte significativa) do trabalho no DB, fantástico! Seu problema de código desaparece (ou fica muito mais simples)!

Problema 4: Você está fazendo o que para isso data?

Supondo que você não possa fazer tudo isso no banco de dados, deixe-me ver se entendi. Você está tomando os dados como mapa (que é codificado por itens que você não deseja classificar), depois iterando sobre eles na ordem de inserção e modificando o mapa no local, substituindo o valor de algumas chaves e adicionando novos?

Sinto muito, mas que diabos?

Os chamadores não precisam se preocupar com tudo isso . O sistema que você criou é extremamente frágil. É preciso apenas um erro estúpido (talvez até cometido por você mesmo, como todos nós fizemos) para fazer uma pequena alteração errada e a coisa toda desmorona como um baralho de cartas.

Aqui está talvez uma ideia melhor:

  • Faça sua função aceitar a List.
  • Existem algumas maneiras de lidar com o problema de pedidos.
    1. Aplicar falha rápida. Lance um erro se a lista não estiver na ordem que a função exige. (Nota: você pode usar o índice de classificação do Problema 2 para saber se está.)
    2. Crie você mesmo uma cópia classificada (novamente usando o índice do problema 2).
    3. Descubra uma maneira de construir o mapa em ordem.
  • Construa o mapa que você precisa internamente para a função, para que o chamador não precise se preocupar com isso.
  • Agora itere sobre o que quer que esteja na representação de ordem e faça o que for necessário.
  • Retorne o mapa ou transforme-o em um valor de retorno apropriado

Uma variação possível poderia ser construir uma representação classificada e criar um mapa de chave para indexar . Isso permitiria modificar sua cópia classificada no local, sem criar duplicatas acidentalmente.

Ou talvez isso faça mais sentido: livrar-se do dataparâmetro e processDatarealmente buscar seus próprios dados. Em seguida, você pode documentar que está fazendo isso porque ele possui requisitos muito específicos sobre a maneira como os dados são buscados. Em outras palavras, torne a função proprietária de todo o processo, não apenas uma parte dele; as interdependências são fortes demais para dividir a lógica em partes menores. (Altere o nome da função no processo.)

Talvez isso não funcione para a sua situação. Eu não sei sem detalhes completos do problema. Mas conheço um design frágil e confuso quando o ouço.

Sumário

Penso que o problema aqui é, em última análise, que o diabo está nos detalhes. Quando começo a ter problemas como esse, geralmente é porque tenho uma representação inadequada dos meus dados para o problema que estou tentando resolver. A melhor solução é encontrar uma melhor representação e, em seguida, meu problema se torna simples (talvez não fácil, mas direto) de resolver.

Encontre alguém que entenda esse ponto: seu trabalho é reduzir seu problema a um conjunto de problemas simples e diretos. Então você pode criar um código robusto e intuitivo. Fale com eles. Um bom código e um bom design fazem você pensar que qualquer idiota poderia ter pensado neles, porque são simples e diretos. Talvez haja um desenvolvedor sênior com essa mentalidade com a qual você possa conversar.


"O que você quer dizer com não há ordem natural, mas a ordem de inserção é importante? Você está dizendo que importa a ordem em que os dados foram inseridos na tabela do banco de dados, mas você não possui uma coluna que possa dizer em que ordem as coisas foram inseridas?" - a pergunta afirma o seguinte: "Classificar o mapa seria uma operação pesada, por isso quero evitar fazer isso, já que o resultado da consulta já está classificado". Isso claramente significa que não é uma ordem definida calculatable aos dados, porque senão a classificação seria impossível em vez de pesado, mas que a ordem definida é diferente da ordem natural das chaves.
Jules

2
Em outras palavras, o OP está trabalhando nos resultados de uma consulta como select key, value from table where ... order by othercolumne precisa manter a ordem em seu processamento. A ordem de inserção a que eles estão se referindo é a ordem de inserção em seu mapa , definida pelo pedido usado em sua consulta, não a ordem de inserção no banco de dados . Isso fica claro pelo uso de LinkedHashMap, que é uma estrutura de dados que possui as características de a Mape de um Listdos pares de valores-chave.
Jules

@Jules Vou limpar um pouco essa seção, obrigado. (Na verdade, lembrei-me de ler isso, mas quando eu estava checando as coisas enquanto escrevia a pergunta, não consegui encontrá-la. Lol. Ficou muito bravo.) Mas a pergunta não está clara sobre o que eles estão fazendo com o DB consulta e se eles têm uma classificação explícita ou não. Eles também dizem que "o pedido de inserção é importante". O ponto é que, mesmo que a classificação seja pesada, você não pode confiar no banco de dados para ordenar magicamente as coisas corretamente, se não pedir explicitamente. E se você estiver fazendo isso no banco de dados, poderá usar um "índice" para torná-lo eficiente no código.
Jpmc26 #

* escrever a resposta (Parece-me que eu deveria ir para a cama cedo.)
jpmc26

Sim, @Jules está certo. Não é uma order bycláusula na consulta, mas não é trivial ( não apenas order by column), assim que eu quero evitar reimplementar a classificação em Java. Embora o SQL seja poderoso (e estamos falando de um banco de dados Oracle 11g aqui), a natureza do processDataalgoritmo facilita muito a expressão em Java. E sim, "pedido de inserção" significa " pedido de inserção de mapa ", ou seja, pedido de resultado da consulta.
Vidar S. Ramdal
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.