
De 28 a 29 de outubro, o
Joker 2019 foi realizado em São Petersburgo - a maior e mais hardcore conferência na vastidão da Rússia dedicada ao desenvolvimento de Java. O evento foi realizado pela sétima vez e, como sempre, bateu o recorde de presença, desta vez atraindo mais de 2000 especialistas.
Os colegas de classe tradicionalmente participam do Joker como parceiros do evento. Este ano, em nosso estande, pode-se tentar lidar com as famosas tarefas "insolúveis" dos principais engenheiros da OK.RU. Os participantes da conferência que responderam às perguntas receberam os prêmios corretamente.
Para ser honesto, devo dizer que, dos 1.000 folhetos com as tarefas que entregamos, menos de 100 foram devolvidos.O melhor foi a solução, que obteve 4,5 pontos em 5.
Publicamos tarefas e suas soluções para que você possa testar sua força.
1. Enum heróico
O código fonte de um jogo pouco conhecido revelou esse código. Qual é a má implementação do
Group.of
e como corrigi-lo?
enum Group { Few(1, 4), Several(5, 9), Pack(10, 19), Lots(20, 49), Swarm(50, Integer.MAX_VALUE); Group(int min, int max) { ... } public static Group of(int count) { for (Group group : Group.values()) { if (count >= group.min && count <= group.max) { return group; } } throw new IllegalArgumentException(); } }
SoluçãoSe você não fala sobre estilos de codificação, esse fragmento tem uma desvantagem objetiva - um potencial problema de desempenho. Embora a pesquisa linear frequentemente se mostre um gargalo, nesse caso, não é o caso, porque esse enum possui apenas cinco elementos. E o que realmente pode afetar negativamente o desempenho é a alocação excessiva de memória ao chamar
Group.values()
. O problema é que o método
values()
de enum sempre retorna uma nova cópia da matriz e o HotSpot ainda não é capaz de otimizá-la. Uma solução simples é fazer sua própria cópia da matriz
values()
e iterar sobre ela:
private static final Group[] ALL_GROUPS = Group.values(); public static Group of(int count) { for (Group group : ALL_GROUPS) { .... }
2. Sonhos
O Java 13 já foi lançado e o Nikolai ainda compreende apenas os fluxos. Indique erros no método que calcula a diferença entre os elementos de fluxo máximo e mínimo.
int getDiameter(Stream<Integer> stream) { int min = stream.min(Integer::compare).get(); int max = stream.max(Integer::compare).get(); return max - min; }
SoluçãoOs fluxos em Java geralmente são únicos: a chamada da segunda operação do terminal (neste caso,
max
) falhará:
java.lang.IllegalStateException: stream has already been operated upon or closed
Além disso,
min
e
max
retornam
Optional
, a operação
get()
na qual lançará
NoSuchElementException
para um fluxo vazio. Portanto, é mais correto verificar
isPresent()
antes de chamar
get()
ou usar outros métodos
Optional
:
orElse ,
ouElseThrow , etc.
Finalmente, o fato de que a diferença entre as duas
int
não pode mais se encaixar no
int
não escapará ao desenvolvedor cuidadoso e valeria a pena alterar o tipo do valor de retorno para
long
.
3. Buffer seguro
ByteBuffer
primitivo de sincronização Java pode tornar o thread de operações de
put
e
get
seguro em um
ByteBuffer
genérico?
final ByteBuffer buf = ByteBuffer.allocate(SIZE); int get(int offset) { return buf.get(offset); } void put(int offset, int value) { buf.putInt(offset, value); }
Escolha a opção mais eficiente se você souber que existem muitos threads e obtenha execuções com muito mais frequência do que colocar.
- sincronizado
- Reentrantlock
- ReentrantReadWriteLock
- Stampedlock
- Semáforo
- Ler e escrever int em Java é sempre atômico
SoluçãoReentrantReadWriteLock
implora por
ReentrantReadWriteLock
leitor e gravador, e geralmente essa será uma solução eficaz. Mas observe que, nesse caso, as operações get e put são muito simples - a probabilidade de uma put competitiva poder interferir com get é pequena; além disso, as condições de put são menos prováveis de ocorrer com operações de put. Portanto, você pode aplicar o mecanismo de
bloqueio otimista fornecido pelo
StampedLock .
StampedLock
será mais eficiente que o
ReentrantReadWriteLock
devido ao fato de que, em caso de sucesso otimista do caminho rápido, as variáveis compartilhadas não serão atualizadas, enquanto o
ReentrantReadWriteLock
realiza no mínimo um
CAS .
4. Presentes
Ilya está desenvolvendo uma vitrine de presentes em uma rede social. Ajude-o a escrever o método
add
para uma estrutura que não contenha mais que N dos presentes mais recentes. Um presente não deve ser adicionado se ele já estiver presente ou se for mais antigo que o restante de N.
interface Present { long getId(); Date getCreated(); } void add(Present p) {
SoluçãoTreeSet
ou o
PriorityQueue
naturalmente adequado como uma estrutura de dados para adicionar efetivamente presentes e excluir os mais antigos não pior do que para O (log N). Todo o truque é apenas no comparador: não é suficiente comparar presentes apenas com
getCreated()
, porque a data de criação não precisa ser única. Portanto, você precisa comparar primeiro por
getCreated()
e depois por
getId()
. Esse comparador garantirá a exclusividade dos elementos e a ordenação por data.
TreeSet<Present> tree = new TreeSet<>( Comparator.comparing(Present::getCreated) .thenComparing(Present::getId));
Ainda é uma questão pequena: ao adicionar um presente, verifique se o tamanho não excede N e, se necessário, exclua o primeiro elemento mais antigo da coleção.
void add(Present p) { if (tree.add(p) && tree.size() > N) { tree.pollFirst(); } }
5. Você não vai esperar
Por que Julia nunca esperará o final deste programa?
var executor = Executors.newFixedThreadPool(4); for (File f : File.listRoots()) { executor.submit(() -> f.delete()); } executor.awaitTermination(2, TimeUnit.HOURS);