تحياتي ، أيها القارئ!
هذا المقال سيخفف من وعيي بالإنتاجية. دعنا نتحدث عن أشياء مضحكة في Java ومن حولك ربما لم تكن تعرفها. لقد تعلمت بنفسي مؤخرًا بعض ما سبق ، لذلك أعتقد أن معظم القراء سيجدون لحظات ممتعة على الأقل لأنفسهم.
تأكيد يمكن أن تأخذ 2 الحجج
عادة ، assert استخدام assert للتحقق من بعض الحالات وإلقاء AssertionError إذا لم تكن الحالة مرضية. في أغلب الأحيان ، يبدو الاختيار كما يلي:
 assert list.isEmpty(); 
ومع ذلك ، يمكن أن يكون مثل هذا:
 assert list.isEmpty() : list.toString(); 
لقد خمّن القارئ الذكي بالفعل أن التعبير الثاني (بالمناسبة ، كسول) يُرجع قيمة النوع Object ، والتي يتم تمريرها إلى AssertionError ويعطي المستخدم معلومات إضافية حول الخطأ. للحصول على وصف أكثر رسمية ، راجع القسم المقابل من مواصفات اللغة: https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.10
منذ ما يقرب من 6 سنوات ونصف من العمل مع Java ، رأيت الاستخدام المطول للكلمة الرئيسية assert مرة واحدة فقط.
strictfp
هذه ليست كلمة لعنة - إنها كلمة رئيسية غير معروفة. وفقًا للوثائق ، يشمل استخدامه حسابًا صارمًا لأرقام الفاصلة العائمة:
 public interface NonStrict { float sum(float a, float b); } 
يمكن أن تتحول إلى
 public strictfp interface Strict { float sum(float a, float b); } 
أيضًا ، يمكن تطبيق هذه الكلمة الأساسية على الأساليب الفردية:
 public interface Mixed { float sum(float a, float b); strictfp float strictSum(float a, float b); } 
اقرأ المزيد عن استخدامه في مقالة الويكي . باختصار: بمجرد إضافة هذه الكلمة الرئيسية لضمان إمكانية النقل ، مثل قد تكون دقة معالجة أرقام الفاصلة العائمة على معالجات مختلفة مختلفة.
متابعة قد يستغرق حجة
لقد اكتشفت ذلك الأسبوع الماضي. عادة نكتب مثل هذا:
 for (Item item : items) { if (item == null) { continue; } use(item); } 
يفترض هذا الاستخدام ضمنيًا العودة إلى بداية الدورة والتمرير التالي. بمعنى آخر ، يمكن إعادة كتابة الكود أعلاه على النحو التالي:
 loop: for (Item item : items) { if (item == null) { continue loop; } use(item); } 
ومع ذلك ، يمكنك العودة من الدورة إلى الدورة الخارجية ، إن وجدت:
 @Test void test() { outer: for (int i = 0; i < 20; i++) { for (int j = 10; j < 15; j++) { if (j == 13) { continue outer; } } } } 
لاحظ أن العداد i عند العودة إلى النقطة outer لا تتم إعادة ضبطه ، لذلك تكون الحلقة محدودة.
عند استدعاء طريقة vararg بدون وسائط ، يتم إنشاء صفيف فارغ على أي حال
عندما ننظر إلى نداء هذه الطريقة من الخارج ، يبدو أنه لا يوجد ما يدعو للقلق بشأن:
 @Benchmark public Object invokeVararg() { return vararg(); } 
لم نمر بأي شيء في الطريقة ، أليس كذلك؟ ولكن إذا نظرت من الداخل ، فكل شيء ليس وردياً:
 public Object[] vararg(Object... args) { return args; } 
تؤكد التجربة المخاوف:
 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 
يمكنك التخلص من مجموعة غير ضرورية في حالة عدم وجود وسيطات بتمرير null :
 @Benchmark public Object invokeVarargWithNull() { return vararg(null); } 
جامع القمامة حقًا يشعر بالتحسن:
 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 
يبدو الرمز الذي يحتوي على قيمة null قبيحًا للغاية ، وسيؤدي المحول البرمجي (والفكرة) اليمين ، لذا استخدم هذا النهج في الكود الساخن جدًا وقدم له تعليقًا.
تعبير تبديل الحالة لا يدعم java.lang.Class
هذا الكود فقط لا يجمع:
 String to(Class<?> clazz) { switch (clazz) { case String.class: return "str"; case Integer.class: return "int"; default: return "obj"; } } 
تعامل معها
التفاصيل الدقيقة للتخصيص و Class.isAssignableFrom ()
هناك كود:
 int a = 0; Integer b = 10; a = b;  
الآن فكر في القيمة التي ستعود بها هذه الطريقة:
 boolean check(Integer b) { return int.class.isAssignableFrom(b.getClass()); } 
بعد قراءة اسم الأسلوب Class.isAssignableFrom() ، Class.isAssignableFrom() يعطي انطباعًا خاطئًا بأن التعبير int.class.isAssignableFrom(b.getClass()) سيعود إلى true . يمكننا تعيين متغير من النوع int إلى متغير من النوع Integer ، هل يمكننا؟
ومع ذلك ، check() طريقة check() false ، حيث تنص الوثائق بوضوح على:
  @HotSpotIntrinsicCandidate public native boolean isAssignableFrom(Class<?> cls); 
على الرغم من أن int ليست خليفة Integer (والعكس صحيح) ، فإن الإحالة المتبادلة المحتملة هي سمة من سمات اللغة ، وحتى لا يتم تضليل المستخدمين ، يتم إجراء حجز خاص في الوثائق.
الأخلاقية: عندما يبدو - بحاجة إلى أن يعتمد بحاجة إلى إعادة قراءة الوثائق.
حقيقة أخرى غير واضحة تتبع من هذا المثال:
 assert int.class != Integer.class; 
فئة int.class هي في الواقع Integer.TYPE ، Integer.TYPE ، انظر فقط إلى ما سيتم تجميع هذا الرمز فيه:
 Class<?> toClass() { return int.class; } 
Vzhuh:
 toClass()Ljava/lang/Class; L0 LINENUMBER 11 L0 GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class; ARETURN 
عند فتح الكود المصدري لـ java.lang.Integer سنرى هذا:
 @SuppressWarnings("unchecked") public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); 
بالنظر إلى المكالمة إلى Class.getPrimitiveClass("int") قد تميل إلى قطعها واستبدالها بـ:
 @SuppressWarnings("unchecked") public static final Class<Integer> TYPE = int.class; 
الشيء الأكثر إثارة للدهشة هو أن JDK مع تغييرات مماثلة (لجميع البدائية) سوف تتجمع ، وسوف يبدأ الجهاز الظاهري. صحيح أنها لن تعمل طويلاً:
 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) 
الخطأ يزحف هنا:
 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);  
مع التغييرات المذكورة ، تقوم byte.class بإرجاع قيمة null وكسر الأمان.
Spring Data JPA تعلن عن مستودع وظيفي جزئي
أختتم المقال بخطأ غريب وقع عند تقاطع Spring Date و Hibernate. تذكر كيف نعلن أن مستودعنا يخدم كيانًا معينًا:
 @Entity public class SimpleEntity { @Id private Integer id; @Column private String name; } public interface SimpleRepository extends JpaRepository<SimpleEntity, Integer> { } 
يعرف المستخدمون المتمرسون أنه عند رفع السياق ، يتحقق Spring Date من جميع المستودعات ويقوم بتدمير التطبيق بالكامل على الفور عند محاولة وصف ، على سبيل المثال ، منحنى الاستعلام:
 public interface SimpleRepository extends JpaRepository<SimpleEntity, Integer> { @Query(", ,  ?") Optional<SimpleEntity> findLesserOfTwoEvils(); } 
ومع ذلك ، لا شيء يمنعنا من إعلان مستودع بنوع مفتاح يسار:
 public interface SimpleRepository extends JpaRepository<SimpleEntity, Long> { } 
لن يرتفع هذا المستودع فحسب ، بل سيتم أيضًا تشغيله جزئيًا ، على سبيل المثال ، findAll() طريقة findAll() "مع اثارة ضجة". ولكن من المتوقع أن تفشل الطرق التي تستخدم المفتاح مع وجود خطأ:
 IllegalArgumentException: Provided id of the wrong type for class SimpleEntity. Expected: class java.lang.Integer, got class java.lang.Long 
الشيء هو أن "تاريخ الربيع" لا يقارن بين فئات مفتاح الكيان ومفتاح المستودع المرفق به. لا يحدث هذا من حياة جيدة ، ولكن بسبب عدم قدرة Hibernate على إصدار نوع المفتاح الصحيح في بعض الحالات: https://hibernate.atlassian.net/browse/HHH-10690
في حياتي ، قابلت هذا مرة واحدة فقط: في اختبارات (عربة) لـ Spring Dates نفسها ، على سبيل المثال ، org.springframework.data.jpa.repository.query.PartTreeJpaQueryIntegrationTests$UserRepository كتابتها في Long ، Integer استخدام Integer في كيان User . وهي تعمل!
هذا كل شيء ، آمل أن يكون تقييمي مفيدًا وممتعًا لك.
أهنئكم بالعام الجديد وأتمنى لكم أن تحفروا أعمق وأوسع!