
Du 28 au 29 octobre,
Joker 2019 s'est tenu à Saint-Pétersbourg - la conférence la plus grande et la plus hardcore dans l'immensité de la Russie consacrée au développement Java. L'événement a eu lieu pour la septième fois et, comme toujours, a battu le record de fréquentation, cette fois, l'événement a attiré plus de 2000 spécialistes.
Les camarades de classe participent traditionnellement au Joker en tant que partenaires de l'événement. Cette année, sur notre stand, on pourrait essayer de faire face aux fameuses tâches «insolubles» des principaux ingénieurs OK.RU. Les participants à la conférence qui ont répondu correctement aux questions ont reçu des prix.
En toute honnêteté, je dois dire que sur 1 000 dépliants avec les tâches que nous avons confiées, moins de 100 ont été retournés, la meilleure étant la solution, qui a obtenu 4,5 points sur 5.
Nous publions des tâches et leurs solutions afin que vous puissiez tester votre force.
1. Enum héroïque
Le code source d'un jeu peu connu a révélé un tel code. Quelle est la mauvaise implémentation de
Group.of
et comment y remédier?
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(); } }
SolutionSi vous ne parlez pas de styles de codage, ce fragment présente un inconvénient objectif - un problème potentiel de performances. Bien que la recherche linéaire se révèle souvent être un goulot d'étranglement, dans ce cas, ce n'est pas le cas, car cette énumération ne comporte que cinq éléments. Et ce qui peut vraiment affecter les performances, c'est l'allocation excessive de mémoire lors de l'appel de
Group.values()
. Le problème est que la méthode
values()
d'énumération renvoie à chaque fois une nouvelle copie du tableau, et HotSpot n'est pas encore en mesure de l'optimiser. Une solution simple consiste à créer votre propre copie du tableau
values()
et à itérer dessus:
private static final Group[] ALL_GROUPS = Group.values(); public static Group of(int count) { for (Group group : ALL_GROUPS) { .... }
2. Rêves
Java 13 a déjà été publié et Nikolai ne comprend toujours que les flux. Indiquez les erreurs dans la méthode qui calcule la différence entre les éléments de flux maximum et minimum.
int getDiameter(Stream<Integer> stream) { int min = stream.min(Integer::compare).get(); int max = stream.max(Integer::compare).get(); return max - min; }
SolutionLes flux en Java sont généralement uniques: l'appel de la deuxième opération de terminal (dans ce cas,
max
) échouera:
java.lang.IllegalStateException: stream has already been operated upon or closed
De plus,
min
et
max
retournent
Optional
, l'opération
get()
sur laquelle
NoSuchElementException
une
NoSuchElementException
pour un flux vide. Par conséquent, il est plus correct de vérifier
isPresent()
avant d'appeler
get()
ou d'utiliser d'autres méthodes
Optional
:
orElse ,
orElseThrow , etc.
Enfin, le fait que la différence entre les deux
int
ne puisse plus tenir dans l'
int
n'échappera pas au développeur prudent, et il vaudrait la peine de changer le type de la valeur de retour en
long
.
3. Tampon sûr
ByteBuffer
primitive de synchronisation Java peut sécuriser les threads
put
et
get
sur un
ByteBuffer
générique?
final ByteBuffer buf = ByteBuffer.allocate(SIZE); int get(int offset) { return buf.get(offset); } void put(int offset, int value) { buf.putInt(offset, value); }
Choisissez l'option la plus efficace si vous savez qu'il existe de nombreux threads et obtenez des exécutions beaucoup plus souvent que put.
- synchronisé
- Reentrantlock
- ReentrantReadWriteLock
- Stampedlock
- Sémaphore
- La lecture et l'écriture int en Java est toujours atomique
SolutionReentrantReadWriteLock
demande des
ReentrantReadWriteLock
lecture et d'écriture, et ce sera souvent une solution efficace. Mais notez que dans ce cas, les opérations get et put sont très simples - la probabilité qu'un put concurrentiel puisse interférer avec get est faible, de plus, les conditions de vente sont moins susceptibles de se produire avec les opérations put. Ainsi, vous pouvez appliquer le mécanisme de
verrouillage optimiste fourni par
StampedLock .
StampedLock
sera plus efficace que
ReentrantReadWriteLock
du fait qu'en cas de réussite d'un chemin rapide optimiste, les variables partagées ne sont pas du tout mises à jour, tandis que
ReentrantReadWriteLock
effectue au moins un
CAS au mieux.
4. Cadeaux
Ilya développe une vitrine de cadeaux sur un réseau social. Aidez-le à écrire la méthode d'
add
pour une structure qui ne contient pas plus de N des nouveaux cadeaux. Un cadeau ne doit pas être ajouté s'il est déjà présent ou s'il est plus ancien que le reste de N.
interface Present { long getId(); Date getCreated(); } void add(Present p) {
SolutionTreeSet
ou
PriorityQueue
naturellement comme structure de données afin d'ajouter efficacement des cadeaux et de supprimer les plus anciens pas moins que pour O (log N). Tout l'astuce n'est que dans le comparateur: il ne suffit pas de comparer les cadeaux uniquement avec
getCreated()
, car la date de création n'a pas à être unique. Par conséquent, vous devez comparer d'abord par
getCreated()
, puis par
getId()
. Un tel comparateur garantira à la fois l'unicité des éléments et la commande par date.
TreeSet<Present> tree = new TreeSet<>( Comparator.comparing(Present::getCreated) .thenComparing(Present::getId));
Cela reste une petite affaire: lors de l'ajout d'un cadeau, vérifiez que la taille ne dépasse pas N, et si nécessaire, supprimez le premier élément le plus ancien de la collection.
void add(Present p) { if (tree.add(p) && tree.size() > N) { tree.pollFirst(); } }
5. Tu n'attendras pas
Pourquoi Julia n'attendra jamais la fin de ce programme?
var executor = Executors.newFixedThreadPool(4); for (File f : File.listRoots()) { executor.submit(() -> f.delete()); } executor.awaitTermination(2, TimeUnit.HOURS);