
Aloha!
Damit ist eine der härtesten Konferenzen der Java-Welt - Joker 2018, die traditionell im Expoforum in St. Petersburg stattfindet - beendet. In diesem Jahr nahmen an der Konferenz eine Rekordzahl von Teilnehmern teil. Odnoklassniki bot traditionell an, unseren Entwicklern bei der Lösung nicht trivialer Probleme zu helfen, die beim Erstellen eines der am stärksten ausgelasteten Java-Projekte auftreten.
Diejenigen, die die Fragen gut beantwortet haben, haben Preise erhalten, und wir bieten Ihnen eine kurze Analyse unserer Probleme. Wir haben die richtigen Antworten unter dem Spoiler versteckt, chur, um sie erst zu öffnen, nachdem wir selbst die Lösung gefunden haben ;-)
Lass uns gehen!
Deduplikator
Cyril möchte Speicherplatz sparen, indem er Objekte in
equals()
. Helfen Sie ihm, die thread-sichere Dedup-Methode analog zu
String.intern
zu
String.intern
, aber nicht nur für Strings.
public static Object dedup(Object obj) { }
LösungCyril hatte sich am Hinterkopf gekratzt und konnte verschiedene Möglichkeiten zur Lösung dieses Problems finden, aber alle waren irgendwie falsch. Dann kratzte er sich an der Nase und
computeIfAbsent
java.util.concurrent
und erinnerte sich an die wunderbare Methode
computeIfAbsent
. Diese Methode führt das im Parameter übergebene Lambda nur aus, wenn die
Map
keinen Schlüssel enthält, schreibt das Ergebnis und gibt es zurück. Wenn ein solcher Schlüssel bereits vorhanden ist, wird Lambda nicht berechnet und der dem Schlüssel zugeordnete aktuelle Wert wird zurückgegeben. Kirill erinnerte sich außerdem, dass diese Methode für
ConcurrentHashMap
atomar funktioniert, wodurch Sie das Problem sehr elegant lösen können. Zufrieden Cyril schrieb diesen Code:
private static final ConcurrentHashMap map = new ConcurrentHashMap(); public static Object dedup(Object obj) { return map.computeIfAbsent(obj, o -> o); }
und kratzte sich gerne wieder an der Nase.
IP-Adresse
Dima entwickelt ein neues Netzwerkprotokoll. Korrigieren Sie den Fehler in seiner Methode zum Übersetzen einer als Byte-Array dargestellten IPv4-Adresse in eine Zeichenfolge.
String ipToString(byte[] ip) { return ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]; }
LösungDer erste Fehler wurde sofort von der IDE angezeigt, sodass Dima die Methode nicht einmal am Ende hinzufügen konnte. Das Symbol
'.'
Der Typ
char
wird dem Byte als ganzzahliger Typ hinzugefügt.
'.'
Ersetzen zu
"."
Dima freute sich so über den erfolgreich kompilierten Code, dass er ihn sofort ohne Test startete. "Ay-ah-ah, Dima", dachte die JVM und gab etwas Unsinn anstelle der IP-Adresse heraus. Im Gegensatz zu Dima wusste die JVM mit Sicherheit, dass in Java der
byte
zum Speichern von vorzeichenbehafteten Zahlen verwendet wird, dh, alle Adressen mit Oktetten größer als 127 werden in Java durch negative Zahlen dargestellt. Nach den Regeln für das Umwandeln dieser Zahlen in
int
ist das negative Vorzeichen der Zahl dasselbe wie im ursprünglichen Byte. Ah, Dmitry, es war notwendig, zusätzliche Maßnahmen zu ergreifen, um den Schilderteil zu verwerfen, zum Beispiel wie folgt:
return (ip[0] & 255) + "." + (ip[1] & 255) + "." + (ip[2] & 255) + "." + (ip[3] & 255)
Mischer
Marina muss die Listenelemente in zufälliger Reihenfolge mischen. Warum ist diese Option nicht geeignet und wie würden Sie sie beheben?
Random random = ThreadLocalRandom.current(); list.sort((o1, o2) -> { return random.nextBoolean() ? +1 : -1; });
LösungMarina hat offensichtlich vergessen, dass der Comparator
Stabilität erfordert: Beim Vergleich zweier identischer Werte sollte das Ergebnis des Vergleichs dasselbe sein. Bei der Implementierung von Marina ist das Ergebnis für jedes Paar streng zufällig, was leicht zu einer Ausnahme von java.lang.IllegalArgumentException: Comparison method violates its general contract
! Wenn Marina abends die Dokumentation lesen würde, würde sie wissen, dass es in diesem Fall am besten ist, die Collections.shuffle()
-Methode zu verwenden.
Antwort: Der Vergleichsvertrag wird verletzt. Das Sortieren kann eine Ausnahme auslösen. Es ist besser, die Collections.shuffle()
-Methode zu verwenden.
Funktionale Krippe
Egor liebt es, in einem funktionalen Stil zu schreiben, ohne sich um die Effektivität des Codes zu kümmern. Schätzen Sie, wie viele Objekte jeder Aufruf dieser Methode erstellt, wenn eine
ArrayList
mit 10 Zeilen an sie übergeben wird.
Predicate<String> equalsAny(List<String> list) { Predicate<String> p = s -> false; for (String s : list) { p = p.or(s::contains); } return p; }
LösungIm Gegensatz zu Yegor schreibt die pedantische Alina nicht gerne alles in einem funktionalen Stil, weil sie weiß, wie man Gemeinkosten berechnet. Einzeilige Zeile
p = p.or(s::contains);
Es werden zwei Objekte gleichzeitig erstellt: eines als Ergebnis des Aufrufs von p.or()
und das zweite zum Erstellen des Prädikats s::contains
. Letzteres kann nicht zwischengespeichert werden, da es die Variablen s
im Kontext erfasst. Multipliziert mit der Anzahl der Iterationen erhalten wir 20 Objekte. Es kann aber auch ein versteckter Iterator
erstellt werden, wenn die JIT ihn nicht optimiert. "20 oder sogar 21 Objekte, wenn Sie kein Glück haben, sind ein Sünder", dachte Alina.
Antwort: 10 Prädikate or
+ 10 Prädikate contains
je nach JIT-Optimierung + 1 Iterator
.
Maxim schaltet sich auf das Maximum ein
Maxim berechnet das Maximum in einem Multithread-Programm, möchte jedoch auf Sperren verzichten. Helfen Sie ihm, den Fehler zu beheben.
AtomicLong max = new AtomicLong(); void addValue(long v) { if (v > max.get()) { max.set(v); } }
LösungOh, Maxim!
AtomicLong
Verwendung von
AtomicLong
macht den Programm-Thread nicht sicher.
AtomicLong.compareAndSwap
gibt es eine atomare Operation
AtomicLong.compareAndSwap
. Und ab Java 8 ist es überhaupt nicht notwendig, den CAS-Zyklus selbst zu schreiben, da die wunderbare atomare Methode
accumulateAndGet
. Und hier ist es bequem, nur es zu verwenden:
void addValue(long v) { max.accumulateAndGet(v, Math::max); }