
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); }