
Aloha!
Jadi salah satu konferensi paling keras di dunia Jawa - Joker 2018, yang secara tradisional berlangsung di St. Petersburg di Expoforum, telah berakhir. Tahun ini konferensi dihadiri oleh sejumlah peserta. Odnoklassniki secara tradisional ditawarkan untuk membantu pengembang kami memecahkan masalah non-sepele yang muncul saat membuat salah satu proyek Java yang paling banyak dimuat.
Mereka yang menjawab pertanyaan menerima hadiah dengan baik, dan kami menawarkan Anda analisis singkat tentang masalah kami. Kami menyembunyikan jawaban yang benar di bawah spoiler, chur, untuk membuka hanya setelah kami sendiri yang menemukan solusinya ;-)
Ayo pergi!
Deduplicator
Cyril ingin menghemat memori dengan menduplikasi objek sama
equals()
. Bantu dia menerapkan metode dedup thread-aman dengan analogi dengan
String.intern
, tetapi tidak hanya untuk string.
public static Object dedup(Object obj) { }
SolusiSetelah menggaruk bagian belakang kepalanya, Cyril dapat menemukan beberapa opsi untuk menyelesaikan masalah ini, tetapi mereka semua entah bagaimana salah. Kemudian, menggaruk hidung dan berlabuh tentang
java.util.concurrent
, dia ingat metode
computeIfAbsent
luar
computeIfAbsent
. Metode ini akan mengeksekusi lambda yang diteruskan ke parameter hanya jika tidak ada kunci dalam
Map
, tulis hasilnya dan kembali. Jika kunci semacam itu sudah ada, lambda tidak akan dihitung, dan nilai saat ini yang terkait dengan kunci akan dikembalikan. Selain itu, kenang Kirill, untuk
ConcurrentHashMap
metode ini bekerja secara atom, yang memungkinkan Anda untuk menyelesaikan masalah dengan sangat elegan. Satisfied Cyril menulis kode ini:
private static final ConcurrentHashMap map = new ConcurrentHashMap(); public static Object dedup(Object obj) { return map.computeIfAbsent(obj, o -> o); }
dan dengan senang hati menggaruk hidungnya lagi.
Alamat IP
Dima sedang mengembangkan protokol jaringan baru. Perbaiki kesalahan dalam metodenya untuk menerjemahkan alamat IPv4 yang direpresentasikan sebagai array byte ke dalam string.
String ipToString(byte[] ip) { return ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]; }
SolusiKesalahan pertama segera ditunjukkan oleh IDE, mencegah Dima bahkan menambahkan metode sampai akhir. Simbol
'.'
memiliki tipe
char
ditambahkan ke byte sebagai tipe integer. Mengganti
'.'
ke
"."
, Dima sangat senang dengan kode yang berhasil dikompilasi sehingga ia segera meluncurkannya tanpa pengujian. "Ay-ah-ah, Dima", pikir JVM dan memberikan beberapa omong kosong bukannya alamat IP. Tidak seperti Dima, JVM tahu pasti bahwa di Jawa, tipe
byte
digunakan untuk menyimpan angka yang ditandatangani, yaitu, semua alamat dengan oktet lebih besar dari 127 akan diwakili oleh angka negatif di Jawa. Dengan aturan memasukkan angka-angka ini ke
int
, tanda negatif dari nomor tersebut sama dengan di byte asli. Ah, Dmitry, perlu mengambil tindakan tambahan untuk membuang bagian tanda, misalnya seperti ini:
return (ip[0] & 255) + "." + (ip[1] & 255) + "." + (ip[2] & 255) + "." + (ip[3] & 255)
Mixer
Marina perlu mencampur item daftar dalam urutan acak. Mengapa opsi ini tidak cocok, dan bagaimana Anda memperbaikinya?
Random random = ThreadLocalRandom.current(); list.sort((o1, o2) -> { return random.nextBoolean() ? +1 : -1; });
SolusiMarina, jelas, lupa bahwa kontrak Comparator
membutuhkan stabilitas: ketika membandingkan dua nilai yang identik, hasil perbandingan harus sama. Dan dalam implementasi Marina, hasil untuk setiap pasangan benar-benar acak, yang dengan mudah dapat menyebabkan java.lang.IllegalArgumentException: Comparison method violates its general contract
pengecualian java.lang.IllegalArgumentException: Comparison method violates its general contract
! Jika Marina membaca dokumentasi di malam hari, dia akan tahu bahwa dalam hal ini yang terbaik adalah menggunakan metode Collections.shuffle()
.
Jawab: Kontrak pembanding dilanggar. Penyortiran dapat menimbulkan pengecualian. Lebih baik menggunakan metode Collections.shuffle()
.
Adegan kelahiran fungsional
Egor suka menulis dengan gaya fungsional, tidak peduli dengan efektivitas kode. Perkirakan berapa banyak objek setiap panggilan ke metode ini dibuat jika
ArrayList
10 baris dilewatkan ke sana?
Predicate<String> equalsAny(List<String> list) { Predicate<String> p = s -> false; for (String s : list) { p = p.or(s::contains); } return p; }
SolusiTidak seperti Yegor, Alina yang bertele-tele tidak suka menulis semuanya dengan gaya fungsional, karena dia tahu bagaimana menghitung biaya overhead. Satu baris
p = p.or(s::contains);
Itu membuat dua objek sekaligus: satu sebagai hasil dari memanggil p.or()
, dan yang kedua untuk membuat s::contains
predikat. Yang terakhir tidak bisa di-cache karena menangkap variabel dalam konteks. Mengalikan dengan jumlah iterasi, kita mendapatkan 20 objek. Tetapi juga Iterator
tersembunyi dapat dibuat jika JIT tidak mengoptimalkannya. "20 atau bahkan 21 objek, jika kamu tidak beruntung, adalah orang berdosa," pikir Alina.
Jawaban: 10 predikat or
+ 10 predikat contains
+1 Iterator
tergantung pada optimasi JIT.
Pepatah menyala ke maksimum
Maxim menghitung maksimum dalam program multi-utas, tetapi ingin melakukannya tanpa kunci. Bantu dia memperbaiki kesalahan.
AtomicLong max = new AtomicLong(); void addValue(long v) { if (v > max.get()) { max.set(v); } }
SolusiOh, Maxim! Menggunakan
AtomicLong
tidak membuat utas program aman. Ada operasi atom
AtomicLong.compareAndSwap
. Dan mulai dengan Java 8, sama sekali tidak perlu untuk menulis siklus CAS sendiri, karena metode atom yang hebat
accumulateAndGet
. Dan di sini nyaman untuk menggunakannya:
void addValue(long v) { max.accumulateAndGet(v, Math::max); }