
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çãoTendo 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çãoO 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çãoMarina, 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çãoAo 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çãoOh, 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); }