
Aloha!
Ainsi, l'une des conférences les plus hardcore du monde Java - Joker 2018, qui se déroule traditionnellement à Saint-Pétersbourg à l'Expoforum, est terminée. Cette année, la conférence a réuni un nombre record de participants. Odnoklassniki proposait traditionnellement d'aider nos développeurs à résoudre les problèmes non triviaux qui se posent lors de la création de l'un des projets Java les plus chargés.
Ceux qui ont répondu aux questions ont bien reçu des prix, et nous vous proposons une brève analyse de nos problèmes. Nous avons caché les bonnes réponses sous le spoiler, chur, pour ouvrir seulement après avoir nous-mêmes trouvé la solution ;-)
C'est parti!
Déduplicateur
Cyril veut économiser de la mémoire en dédoublant des objets égaux en
equals()
. Aidez-le à implémenter la méthode de dédoublonnage thread-safe par analogie avec
String.intern
, mais pas seulement pour les chaînes.
public static Object dedup(Object obj) { }
SolutionAprès s'être gratté l'arrière de la tête, Cyril a été en mesure de proposer plusieurs options pour résoudre ce problème, mais ils se sont tous trompés. Puis, se grattant le nez et s'arrêtant sur
java.util.concurrent
, il se souvint de la merveilleuse méthode
computeIfAbsent
. Cette méthode exécutera le lambda qui lui est passé dans le paramètre uniquement s'il n'y a pas de clé dans la
Map
, écrit son résultat et retourne. Si une telle clé existe déjà, lambda ne sera pas calculé et la valeur actuelle associée à la clé sera retournée. En outre, Kirill a rappelé que pour
ConcurrentHashMap
cette méthode fonctionne de manière atomique, ce qui vous permet de résoudre le problème de manière très élégante. Cyril satisfait a écrit ce code:
private static final ConcurrentHashMap map = new ConcurrentHashMap(); public static Object dedup(Object obj) { return map.computeIfAbsent(obj, o -> o); }
et gratta son nez avec plaisir.
Adresse IP
Dima développe un nouveau protocole réseau. Corrigez l'erreur dans sa méthode de traduction d'une adresse IPv4 représentée sous la forme d'un tableau d'octets en une chaîne.
String ipToString(byte[] ip) { return ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]; }
SolutionLa première erreur a été immédiatement montrée par l'IDE, empêchant Dima d'ajouter même la méthode à la fin. Le symbole
'.'
ayant le type
char
est ajouté à l'octet comme un type entier. Remplacement de
'.'
à
"."
, Dima était tellement content du code compilé avec succès qu'il l'a immédiatement lancé sans test. "Ay-ah-ah, Dima", a pensé la JVM et a donné un non-sens au lieu de l'adresse IP. Contrairement à Dima, la JVM savait avec certitude qu'en Java, le type d'
byte
est utilisé pour stocker des nombres signés, c'est-à-dire que toutes les adresses avec des octets supérieurs à 127 seront représentées par des nombres négatifs en Java. Selon les règles de conversion de ces nombres en
int
, le signe négatif du nombre est le même que dans l'octet d'origine. Ah, Dmitry, il a fallu prendre des mesures supplémentaires pour jeter la partie signe, par exemple comme ceci:
return (ip[0] & 255) + "." + (ip[1] & 255) + "." + (ip[2] & 255) + "." + (ip[3] & 255)
Mixeur
Marina doit mélanger les éléments de la liste dans un ordre aléatoire. Pourquoi cette option ne convient-elle pas et comment la corrigeriez-vous?
Random random = ThreadLocalRandom.current(); list.sort((o1, o2) -> { return random.nextBoolean() ? +1 : -1; });
SolutionMarina, évidemment, a oublié que le contrat Comparator
requiert de la stabilité: lors de la comparaison de deux valeurs identiques, le résultat de la comparaison doit être le même. Et dans l'implémentation de Marina, le résultat pour chaque paire est strictement aléatoire, ce qui peut facilement conduire à une exception java.lang.IllegalArgumentException: Comparison method violates its general contract
! Si Marina lisait la documentation le soir, elle saurait que dans ce cas, il est préférable d'utiliser la méthode Collections.shuffle()
.
Réponse: Le contrat du comparateur est violé. Le tri peut lever une exception. Il est préférable d'utiliser la méthode Collections.shuffle()
.
Crèche fonctionnelle
Egor aime écrire dans un style fonctionnel, sans se soucier de l'efficacité du code. Estimer le nombre d'objets créés par chaque appel à cette méthode si une liste de
ArrayList
de 10 lignes lui est transmise?
Predicate<String> equalsAny(List<String> list) { Predicate<String> p = s -> false; for (String s : list) { p = p.or(s::contains); } return p; }
SolutionContrairement à Yegor, la pédante Alina n'aime pas tout écrire dans un style fonctionnel, car elle sait calculer les frais généraux. Une seule ligne
p = p.or(s::contains);
Il crée deux objets à la fois: l'un à la suite de l'appel de p.or()
, et le second pour créer le prédicat s::contains
. Ce dernier ne peut pas être mis en cache car il capture la variable s
dans le contexte. En multipliant par le nombre d'itérations, nous obtenons 20 objets. Mais un Iterator
caché peut également être créé si le JIT ne l'optimise pas. "20 ou même 21 objets, si vous n'avez pas de chance, c'est un pécheur", pensa Alina.
Réponse: 10 prédicats or
+ 10 prédicats contains
+ 1 Iterator
fonction des optimisations JIT.
Maxim s'active au maximum
Maxim calcule le maximum dans un programme multithread, mais veut se passer de verrous. Aidez-le à corriger l'erreur.
AtomicLong max = new AtomicLong(); void addValue(long v) { if (v > max.get()) { max.set(v); } }
SolutionOh, Maxim! L'utilisation d'
AtomicLong
ne rend pas le thread du programme sûr. Il existe une opération atomique
AtomicLong.compareAndSwap
. Et à partir de Java 8, il n'est pas du tout nécessaire d'écrire le cycle CAS vous-même, car la merveilleuse méthode atomique
accumulateAndGet
. Et ici, il est pratique de l'utiliser uniquement:
void addValue(long v) { max.accumulateAndGet(v, Math::max); }