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