Salam, pembaca!
Artikel ini akan mencairkan aliran kesadaran saya tentang produktivitas. Mari kita bicara tentang hal-hal lucu di Jawa dan di sekitar Anda yang mungkin tidak Anda ketahui. Saya sendiri baru-baru ini belajar tentang beberapa hal di atas, jadi saya percaya bahwa sebagian besar pembaca akan menemukan setidaknya beberapa momen menarik untuk diri mereka sendiri.
menegaskan dapat mengambil 2 argumen
Biasanya, assert
digunakan untuk menguji beberapa kondisi dan melemparkan AssertionError
jika kondisinya tidak memuaskan. Paling sering, ceknya terlihat seperti ini:
assert list.isEmpty();
Namun, bisa seperti ini:
assert list.isEmpty() : list.toString();
Pembaca pintar sudah menebak bahwa ekspresi kedua (omong-omong, itu malas) mengembalikan nilai tipe Object
, yang diteruskan ke AssertionError
dan memberikan pengguna informasi tambahan tentang kesalahan. Untuk deskripsi yang lebih formal, lihat bagian yang sesuai dari spesifikasi bahasa: https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.10
Selama hampir 6 setengah tahun bekerja dengan Java, saya telah melihat perpanjangan penggunaan kata kunci hanya sekali.
strictfp
Ini bukan kata kutukan - ini adalah kata kunci yang sedikit dikenal. Menurut dokumentasi , penggunaannya termasuk aritmatika yang ketat untuk angka floating point:
public interface NonStrict { float sum(float a, float b); }
bisa diubah menjadi
public strictfp interface Strict { float sum(float a, float b); }
Juga, kata kunci ini dapat diterapkan pada metode individual:
public interface Mixed { float sum(float a, float b); strictfp float strictSum(float a, float b); }
Baca lebih lanjut tentang penggunaannya dalam artikel wiki . Singkatnya: setelah kata kunci ini ditambahkan untuk memastikan portabilitas, sebagai akurasi pemrosesan angka floating point pada prosesor yang berbeda dapat berbeda.
lanjutkan dapat mengambil argumen
Saya mengetahuinya minggu lalu. Biasanya kami menulis seperti ini:
for (Item item : items) { if (item == null) { continue; } use(item); }
Penggunaan seperti itu secara implisit mengasumsikan kembali ke awal siklus dan lintasan berikutnya. Dengan kata lain, kode di atas dapat ditulis ulang sebagai:
loop: for (Item item : items) { if (item == null) { continue loop; } use(item); }
Namun, Anda dapat kembali dari siklus ke siklus eksternal, jika ada:
@Test void test() { outer: for (int i = 0; i < 20; i++) { for (int j = 10; j < 15; j++) { if (j == 13) { continue outer; } } } }
Perhatikan bahwa penghitung i
saat kembali ke titik outer
tidak diatur ulang, sehingga loop terbatas.
Saat memanggil metode vararg tanpa argumen, array kosong tetap dibuat
Ketika kita melihat panggilan metode semacam itu dari luar, tampaknya tidak ada yang perlu dikhawatirkan:
@Benchmark public Object invokeVararg() { return vararg(); }
Kami tidak memasukkan apa pun ke dalam metode, bukan? Tetapi jika Anda melihat dari dalam, maka semuanya tidak begitu cerah:
public Object[] vararg(Object... args) { return args; }
Pengalaman menegaskan kekhawatiran:
Benchmark Mode Cnt Score Error Units invokeVararg avgt 20 3,715 ± 0,092 ns/op invokeVararg:·gc.alloc.rate.norm avgt 20 16,000 ± 0,001 B/op invokeVararg:·gc.count avgt 20 257,000 counts
Anda dapat menyingkirkan array yang tidak perlu tanpa adanya argumen dengan memberikan null
:
@Benchmark public Object invokeVarargWithNull() { return vararg(null); }
Pengumpul sampah benar-benar merasa lebih baik:
invokeVarargWithNull avgt 20 2,415 ± 0,067 ns/op invokeVarargWithNull:·gc.alloc.rate.norm avgt 20 ≈ 10⁻⁵ B/op invokeVarargWithNull:·gc.count avgt 20 ≈ 0 counts
Kode dengan null
terlihat sangat jelek, kompiler (dan Ide) akan bersumpah, jadi gunakan pendekatan ini dalam kode yang benar-benar panas dan berikan komentar.
Ekspresi sakelar kasus tidak mendukung java.lang.Class
Kode ini tidak mengkompilasi:
String to(Class<?> clazz) { switch (clazz) { case String.class: return "str"; case Integer.class: return "int"; default: return "obj"; } }
Menghadapinya.
Subtleties of assignment dan Class.isAssignableFrom ()
Ada kode:
int a = 0; Integer b = 10; a = b;
Sekarang pikirkan nilai apa metode ini akan kembali:
boolean check(Integer b) { return int.class.isAssignableFrom(b.getClass()); }
Setelah membaca nama metode Class.isAssignableFrom()
, Class.isAssignableFrom()
memberikan kesan yang salah bahwa ekspresi int.class.isAssignableFrom(b.getClass())
akan mengembalikan true
. Kita dapat menetapkan variabel tipe int
ke variabel tipe Integer
, bukan?
Namun, metode check()
akan mengembalikan false
, karena dokumentasi dengan jelas menyatakan bahwa:
@HotSpotIntrinsicCandidate public native boolean isAssignableFrom(Class<?> cls);
Meskipun int
bukan merupakan penerus Integer
(dan sebaliknya), kemungkinan tugas bersama adalah fitur bahasa, dan agar tidak menyesatkan pengguna, reservasi khusus dibuat dalam dokumentasi.
Moral: ketika tampaknya - perlu dibaptis perlu membaca kembali dokumentasi.
Fakta tidak jelas lainnya mengikuti dari contoh ini:
assert int.class != Integer.class;
Kelas int.class
sebenarnya adalah Integer.TYPE
, dan untuk melihat ini, lihat saja apa kode ini akan dikompilasi menjadi:
Class<?> toClass() { return int.class; }
Mendera:
toClass()Ljava/lang/Class; L0 LINENUMBER 11 L0 GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class; ARETURN
Membuka kode sumber untuk java.lang.Integer
kita akan melihat ini:
@SuppressWarnings("unchecked") public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
Melihat panggilan ke Class.getPrimitiveClass("int")
mungkin tergoda untuk menghentikannya dan menggantinya dengan:
@SuppressWarnings("unchecked") public static final Class<Integer> TYPE = int.class;
Yang paling menakjubkan adalah bahwa JDK dengan perubahan yang serupa (untuk semua primitif) akan berkumpul, dan mesin virtual akan dimulai. Benar, dia tidak akan bekerja lama:
java.lang.IllegalArgumentException: Component type is null at jdk.internal.misc.Unsafe.allocateUninitializedArray(java.base/Unsafe.java:1379) at java.lang.StringConcatHelper.newArray(java.base/StringConcatHelper.java:458) at java.lang.StringConcatHelper.simpleConcat(java.base/StringConcatHelper.java:423) at java.lang.String.concat(java.base/String.java:1968) at jdk.internal.util.SystemProps.fillI18nProps(java.base/SystemProps.java:165) at jdk.internal.util.SystemProps.initProperties(java.base/SystemProps.java:103) at java.lang.System.initPhase1(java.base/System.java:2002)
Kesalahan merangkak di sini:
class java.lang.StringConcatHelper { @ForceInline static byte[] newArray(long indexCoder) { byte coder = (byte)(indexCoder >> 32); int index = (int)indexCoder; return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, index << coder);
Dengan perubahan yang disebutkan, byte.class
mengembalikan nol dan merusak keamanan.
Spring Data JPA Mengumumkan Repositori Fungsional Sebagian
Saya menyimpulkan artikel dengan kesalahan aneh yang muncul di persimpangan Spring Date dan Hibernate. Ingat bagaimana kami mendeklarasikan repositori yang melayani entitas tertentu:
@Entity public class SimpleEntity { @Id private Integer id; @Column private String name; } public interface SimpleRepository extends JpaRepository<SimpleEntity, Integer> { }
Pengguna yang berpengalaman tahu bahwa ketika meningkatkan konteks, Tanggal Musim Semi memeriksa semua repositori dan segera menghancurkan seluruh aplikasi ketika mencoba menjelaskan, misalnya, kurva kueri:
public interface SimpleRepository extends JpaRepository<SimpleEntity, Integer> { @Query(", , ?") Optional<SimpleEntity> findLesserOfTwoEvils(); }
Namun, tidak ada yang mencegah kita dari mendeklarasikan repositori dengan tipe kunci kiri:
public interface SimpleRepository extends JpaRepository<SimpleEntity, Long> { }
Repositori ini tidak hanya akan naik, tetapi juga akan sebagian operasional, misalnya, metode findAll()
bekerja "dengan bang." Tetapi metode menggunakan kunci diharapkan gagal dengan kesalahan:
IllegalArgumentException: Provided id of the wrong type for class SimpleEntity. Expected: class java.lang.Integer, got class java.lang.Long
Masalahnya adalah bahwa Spring Date tidak membandingkan kelas - kelas dari kunci entitas dan kunci repositori yang terlampir padanya. Ini tidak terjadi dari kehidupan yang baik, tetapi karena ketidakmampuan Hibernate untuk mengeluarkan jenis kunci yang benar dalam kasus-kasus tertentu: https://hibernate.atlassian.net/browse/HHH-10690
Dalam hidup saya, saya hanya bertemu sekali ini: dalam tes (troli) Musim Semi Tanggal itu sendiri, misalnya, org.springframework.data.jpa.repository.query.PartTreeJpaQueryIntegrationTests$UserRepository
diketik dalam Long
, dan Integer
digunakan dalam entitas User
. Dan itu berhasil!
Itu saja, saya harap ulasan saya bermanfaat dan menarik bagi Anda.
Saya mengucapkan selamat kepada Anda pada Tahun Baru dan berharap Anda menggali lebih dalam dan lebih luas!