Coringa quebra-cabeças 2018



Aloha!

Então, uma das conferências mais hardcore do mundo Java - o Joker 2018, que tradicionalmente acontece em São Petersburgo no Expoforum, terminou. Este ano, a conferência contou com um número recorde de participantes. O Odnoklassniki ofereceu-se tradicionalmente para ajudar nossos desenvolvedores a resolver problemas não triviais que surgem ao criar um dos projetos Java mais carregados.

Os que responderam bem às perguntas receberam prêmios e oferecemos uma breve análise de nossos problemas. Escondemos as respostas corretas sob o spoiler, chur, para abrir somente depois que descobrimos a solução ;-)

Vamos lá!

Desduplicador


Cyril quer economizar memória deduplicando objetos iguais em equals() . Ajude-o a implementar o método de String.intern segura de thread por analogia com String.intern , mas não apenas para strings.

 public static Object dedup(Object obj) { } 

Solução
Tendo coçado a parte de trás da cabeça, Cyril conseguiu encontrar várias opções para resolver esse problema, mas todas estavam de alguma forma erradas. Depois, coçando o nariz e computeIfAbsent se a java.util.concurrent , ele lembrou-se do maravilhoso método computeIfAbsent . Este método executará o lambda passado a ele no parâmetro apenas se não houver chave no Map , gravará seu resultado e retornará. Se essa chave já existir, o lambda não será calculado e o valor atual associado à chave será retornado. Além disso, Kirill lembrou que, para o ConcurrentHashMap esse método funciona atomicamente, o que permite resolver o problema com muita elegância. Cyril satisfeito escreveu este código:

 private static final ConcurrentHashMap map = new ConcurrentHashMap(); public static Object dedup(Object obj) { return map.computeIfAbsent(obj, o -> o); } 

e alegremente coçou o nariz novamente.

Endereço IP


Dima está desenvolvendo um novo protocolo de rede. Corrija o erro em seu método para converter um endereço IPv4 representado como uma matriz de bytes em uma sequência.

 String ipToString(byte[] ip) { return ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]; } 

Solução
O primeiro erro foi mostrado imediatamente pelo IDE, impedindo o Dima de adicionar o método até o final. O símbolo '.' ter o tipo char é adicionado ao byte como um tipo inteiro. Substituindo '.' para "." , Dima ficou tão feliz com o código compilado com sucesso que o lançou imediatamente sem testar. “Ai-ai-ai, Dima”, pensou na JVM e deu algumas bobagens em vez de um endereço IP. Diferente do Dima, a JVM sabia com certeza que em Java, o tipo de byte é usado para armazenar números assinados, ou seja, todos os endereços com octetos maiores que 127 serão representados por números negativos em Java. Pelas regras de converter esses números para int , o sinal negativo do número é o mesmo do byte original. Ah, Dmitry, foi necessário tomar medidas adicionais para descartar a parte do sinal, por exemplo:

 return (ip[0] & 255) + "." + (ip[1] & 255) + "." + (ip[2] & 255) + "." + (ip[3] & 255); 


Misturador


Marina precisa misturar os itens da lista em ordem aleatória. Por que essa opção não é adequada e como você a corrige?

 Random random = ThreadLocalRandom.current(); list.sort((o1, o2) -> { return random.nextBoolean() ? +1 : -1; }); 

Solução
Marina, obviamente, esqueceu que o contrato do Comparator exige estabilidade: ao comparar dois valores idênticos, o resultado da comparação deve ser o mesmo. E na implementação de Marina, o resultado para cada par é estritamente aleatório, o que pode facilmente levar a uma exceção java.lang.IllegalArgumentException: Comparison method violates its general contract ! Se Marina lesse a documentação à noite, ela saberia que, nesse caso, é melhor usar o método Collections.shuffle() .

Resposta: O contrato do comparador é violado. A classificação pode gerar uma exceção. É melhor usar o método Collections.shuffle() .

Presépio funcional


Egor gosta de escrever em um estilo funcional, sem se importar com a eficácia do código. Estimar quantos objetos cada chamada para esse método cria se um ArrayList de 10 linhas é passado para ele?

 Predicate<String> equalsAny(List<String> list) { Predicate<String> p = s -> false; for (String s : list) { p = p.or(s::contains); } return p; } 

Solução
Ao contrário de Yegor, a pedante Alina não gosta de escrever tudo em um estilo funcional, porque sabe como calcular custos indiretos. Linha única

p = p.or(s::contains);

Ele cria dois objetos ao mesmo tempo: um como resultado da chamada p.or() e o segundo para criar o predicado s::contains . O último não pode ser armazenado em cache, pois captura a variável s no contexto. Multiplicando pelo número de iterações, obtemos 20 objetos. Mas também um Iterator oculto pode ser criado se o JIT não o otimizar. “20 ou até 21 objetos, se você não tiver sorte, é um pecador”, pensou Alina.

Resposta: 10 predicados or + 10 predicados contains + 1 Iterator dependendo das otimizações do JIT.

Maxim liga para o máximo


Maxim calcula o máximo em um programa multithread, mas quer ficar sem bloqueios. Ajude-o a corrigir o erro.

 AtomicLong max = new AtomicLong(); void addValue(long v) { if (v > max.get()) { max.set(v); } } 

Solução
Oh, Maxim! Usar o AtomicLong não torna o thread do programa seguro. Existe uma operação atômica AtomicLong.compareAndSwap . E a partir do Java 8, não é necessário escrever o ciclo CAS, porque o maravilhoso método atômico accumulateAndGet . E aqui é conveniente usá-lo exatamente:

 void addValue(long v) { max.accumulateAndGet(v, Math::max); } 

Source: https://habr.com/ru/post/pt427337/


All Articles