سؤال

تحقق من هذا الرمز.

// Print object and recurse if iterable
private static void deep_print(Object o) {
  System.out.println(o.getClass().toString() + ", " + o.toString());

  boolean iter = false;
  Iterable<?> i1 = null;
  Object[] i2 = null;

  if (o instanceof Iterable<?>) {
    iter = true;
    i1 = (Iterable<?>) o;
  } else if (o instanceof Object[]) {
    iter = true;
    i2 = (Object[]) o;
  }

  if (iter) {
    for (Object o_ : i2 == null ? i1 : i2) deep_print(o_); // ERROR: Can only iterate over an array or an instance of java.lang.Iterable
  }

أنا أعرف كيفية حلها. أريد فقط أن أعرف لماذا يحدث ذلك. ألا ينبغي للمترجم ببساطة التحقق من كل المخرجات المحتملة؟

هل كانت مفيدة؟

المحلول

نتيجة النتيجة الثابتة للتعبير (i2 == null) ? i1 : i2 هو سلف مشترك i1 و i2 وهو كائن. أ for بيان يتطلب نوع ثابت التعبير ليكون إما Iterable أو نوع الصفيف. هذا ليس هو الحال، لذلك يمكنك الحصول على خطأ في تجميع.

الآن إذا كنت تسأل لماذا لا يستنتج مترجم ذلك (i2 == null) ? i1 : i2 ستكون دائما مجموعة أو قابلة للتكرار:

  1. مواصفات لغة Java (JLS) لا تسمح بذلك.
  2. إذا سمحت JLS (ولكن لا تتطلب ذلك)، فسوف يتصرف مترجمات مختلفة بشكل مختلف، اعتمادا على مدى جودة جيدة في نظرية الإثبات. سيئة.
  3. إذا طلب jls ذلك، فإن المحول البرمجي يجب أن يشتمل على نظرية متطورة`1 (بطيئة سيئة)، وترتدي "إزعاج طفيف" لحل مشكلة وقف2 (سيء سيء سيء).
  4. في الواقع الفعلي، يحتاج المحول البرمجي إلى معرفة أي نوعين من النوعين من النوع يتمتع التعبير لأنه يحتاج إلى إنشاء رمز مختلف في كل حالة.

افترضين، إذا كان نظام نوع Java مختلفا بعض الشيء، فهذه الحالة بالذات استطاع يتم التعامل معها بشكل أفضل. على وجه التحديد، إذا دعمت جافا أنواع البيانات الجبرية ثم سيكون من الممكن الإعلان o باعتبارها "إما مجموعة كائن أو قابلة للقرحة" ... و for سيكون حلقة نوع القابلة للتفاعل.


1 - لنفترض ذلك o قد تم تهيئة كما o = (x * x < 0) ? new Object() : new Object[0]. وبعد تحديد أن ذلك سيؤدي دائما إلى Object[] لا يستلزم المثيل دليلا صغيرا ينطوي على حقيقة أن مربع رقم (حقيقي) ليس سلبيا. هذا مثال بسيط، من الممكن بناء أمثلة معقدة بشكل تعسفي تتطلب البراهين الصعبة التعسفية.

2 - مشكلة توقف مثبتة رياضيا لتكون وظيفة غير تكافؤية. وبعبارة أخرى، توجد وظائف تكون مستحيلة رياضيا لإثبات ما إذا كانوا ينتهيون أم لا.

نصائح أخرى

لتوضيح إجابة Stephen C، فكر في التعليمات البرمجية التالية:

void test() {
      Iterable<Integer> i1 = new ArrayList<Integer>();
      Object[] i2 = { 1, 2, 3 };      
      method1(false ? i1 : i2);
      method1(true ? i1 : i2);  
}

void method1(Object o) {
    System.out.println("method1(Object) called");
}

void method1(Object[] o) {
    System.out.println("method1(Object[]) called");
}

void method1(Iterable<?> o) {
    System.out.println("method1(Iterable<?>) called");
}

هذا هو إخراج الاختبار ():

method1(Object) called
method1(Object) called

نظرا لأن طريقة التحميل الزائد في الطريقة تتم في وقت الترجمة، يمكنك أن ترى أن النوع الثابت للتعبير المشغل الشعبي هو كائن، لأن أنواع المعاملات تختلف. لذلك، عند القيام:

for (Object o_ : i2 == null ? i1 : i2)

أنت حقا تطلب التحويل البرمجي لتوليد حلقة foreach على كائن، وهو أمر غير قانوني.

في هذه الحالة، يحتوي التعبير الشرطي على نوع الحد الأعلى العلوي من النوعين، وهو Object, ، ولم تعمل حلقة foreach على النوع Object

يجب عليك حقا إضافة اثنين من بطاقات التعميد المثقلة (كائن []) وطرق Deyprint (ITERATOR I) وتفعل الإرسال في DeymPrint (كائن كائن). نعم بسبب طبيعة كيفية عمله لصالح / كل حلقة، ستحتاج إلى نسخ الرمز نفسه والصقه مع تغييرات طفيفة.

محاولة وضع كل ذلك في طريقة واحدة كبيرة هي رائحة.

يمكنك تجنب ازدواجية التعليمات البرمجية عن طريق التفاف الكائن [] في صفيفات. قائمة (كائن []) ثم لديك قابلة للتكرار في جميع الحالات. نعم أبطأ قليلا من العمل على صفيف، ولكن لديها ميزة يجف، والتي يجب أن تكون دائما التقريب الأول، IMHO.

لذلك سوف ينتهي بك الأمر مع شيء مثل هذا:

Iterable<?> it = null;
if (o instanceof Iterable<?>) {
    it = (Iterable<?>) o;
} else if (o instanceof Object[]) {
    it = Arrays.asList((Object[]) o);
}

if (it != null) {
   for (Object o_ : it) deep_print(o_);
}

من أجل البساطة (انظر إجابة Stephen C حول كيفية أبسط كتاب مترجم، ولكن من القول أن تصميم لغة أبسط أيضا) يفترض مشغل Ternary أن يكون النوع الذي يكون أقل شيطان شائع بين نوعين الإرجاع، بدلا من إقامة واحدة من نوعين العودة. النظر في حالة طريقة التحميل الزائد. الطريقة الصحيحة مصممة وتجميع الوقت. هذا يعني أن التحويل البرمجي يجب أن يكون قادرا على اتخاذ قرار بشأن نوع العائد المعلن في تجميع الوقت، ولا يمكن أن تأخير هذا القرار لتشغيل الوقت.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top