الأشياء التي ربما لم تعرفها عن جافا

تحياتي ، أيها القارئ!


هذا المقال سيخفف من وعيي بالإنتاجية. دعنا نتحدث عن أشياء مضحكة في 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 ، حيث تنص الوثائق بوضوح على:


 /** * Determines if the class or interface represented by this * {@code Class} object is either the same as, or is a superclass or * superinterface of, the class or interface represented by the specified * {@code Class} parameter. It returns {@code true} if so; * otherwise it returns {@code false}. If this {@code Class} // <---- !!! * object represents a primitive type, this method returns * {@code true} if the specified {@code Class} parameter is * exactly this {@code Class} object; otherwise it returns * {@code 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 . وهي تعمل!


هذا كل شيء ، آمل أن يكون تقييمي مفيدًا وممتعًا لك.


أهنئكم بالعام الجديد وأتمنى لكم أن تحفروا أعمق وأوسع!

Source: https://habr.com/ru/post/ar482364/


All Articles