0Pricing
Java Academy · درس

الفئات الداخلية والوصول إلى الفئة الخارجية

استخدم فئات داخلية غير ثابتة تحتفظ بمرجع إلى كائن الفئة الخارجية

الفئات الداخلية والوصول إلى الفئة الخارجية درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.

الأصناف الداخلية

يحتفظ الصنف الداخلي غير الساكن بمرجع ضمني إلى مثيل الصنف الخارجي الحاوي. ويمكنه الوصول إلى جميع أعضاء الصنف الخارجي، بما في ذلك الأعضاء الخاصة.

تعريف صنف داخلي واستخدامه

أنشئ مثيلًا لصنف داخلي فقط من خلال مثيل للصنف الخارجي باستخدام outer.new InnerClass().

class BankAccount {
    private double balance;
    private String owner;

    BankAccount(String owner, double initial) {
        this.owner = owner; this.balance = initial;
    }

    class Transaction {
        void deposit(double amount) {
            balance += amount; // accesses outer field directly
            System.out.println(owner + " deposited " + amount);
        }
    }
}

BankAccount acct = new BankAccount("Alice", 500);
BankAccount.Transaction tx = acct.new Transaction();
tx.deposit(100); // Alice deposited 100.0

الوصول إلى أعضاء الصنف الخارجي

يمكن للأصناف الداخلية الوصول إلى جميع أعضاء الصنف الخارجي — حتى الحقول والأساليب الخاصة.

class Encryption {
    private final String secretKey = "s3cr3t"; // private!

    class Encryptor {
        String encrypt(String data) {
            // Inner class accesses private outer field
            return data + "|" + secretKey.hashCode();
        }
    }

    class Decryptor {
        String decrypt(String encrypted) {
            int idx = encrypted.lastIndexOf('|');
            return encrypted.substring(0, idx);
        }
    }
}

حجب الأسماء باستخدام this

عندما يحتوي الصنف الداخلي على حقل بالاسم نفسه الموجود في الصنف الخارجي، استخدم OuterClass.this.field للإشارة إلى الحقل الخارجي.

class Outer {
    int value = 10;

    class Inner {
        int value = 20; // shadows outer.value

        void printBoth() {
            System.out.println("Inner value: " + value);         // 20
            System.out.println("Outer value: " + Outer.this.value); // 10
        }
    }
}

new Outer().new Inner().printBoth();
// Inner value: 20
// Outer value: 10

نمط Iterator باستخدام صنف داخلي

من حالات الاستخدام التقليدية تنفيذ مكرّر بوصفه صنفًا داخليًا يصل إلى البيانات الخاصة للمجموعة الحاوية.

class NumberRange implements Iterable<Integer> {
    private final int start;
    private final int end;

    NumberRange(int start, int end) {
        this.start = start; this.end = end;
    }

    @Override
    public java.util.Iterator<Integer> iterator() {
        return new RangeIterator(); // inner class instance
    }

    private class RangeIterator implements java.util.Iterator<Integer> {
        private int current = start; // accesses outer start

        public boolean hasNext() { return current <= end; }
        public Integer next()    { return current++; }
    }
}

for (int n : new NumberRange(1, 5)) System.out.print(n + " ");
// 1 2 3 4 5

مستمعو الأحداث بوصفهم أصنافًا داخلية

تستخدم برمجة الواجهات الرسومية وبرمجة الأحداث عادةً أصنافًا داخلية لتنفيذ مستمعي الأحداث مع الوصول إلى حالة مكوّن واجهة المستخدم الحاوي.

// Swing-style example (conceptual)
class LoginPanel {
    private String username = "";

    class LoginButtonListener {
        void onButtonClicked() {
            if (username.isEmpty()) {
                System.out.println("Please enter username"); // accesses outer field
            } else {
                System.out.println("Logging in as: " + username);
            }
        }
    }

    void setUsername(String name) { this.username = name; }
}
LoginPanel panel = new LoginPanel();
panel.setUsername("alice");
panel.new LoginButtonListener().onButtonClicked();
// Logging in as: alice

الصنف الداخلي في المجموعات

تستخدم بعض تطبيقات المجموعات أصنافًا داخلية للاحتفاظ بمؤشرات أو عروض مدعومة من المجموعة الحاوية.

class SimpleStack<T> {
    private Object[] elements;
    private int size = 0;

    SimpleStack(int capacity) {
        elements = new Object[capacity];
    }

    void push(T item) {
        if (size >= elements.length) throw new StackOverflowError();
        elements[size++] = item;
    }

    T pop() {
        if (size == 0) throw new java.util.EmptyStackException();
        T item = (T) elements[--size];
        elements[size] = null;
        return item;
    }

    int size() { return size; }
}

تحذير من تسرّب الذاكرة

تحتفظ مثيلات الأصناف الداخلية بمرجع إلى المثيل الخارجي. وإذا خرج الصنف الداخلي من نطاق الصنف الخارجي (مثلًا عند تخزينه في مجموعة طويلة العمر)، فلن يتمكن جامع البيانات المهملة من استعادة المثيل الخارجي.

// Memory leak: anonymous inner class (implicit outer ref) stored statically
class LeakExample {
    byte[] data = new byte[1024 * 1024]; // 1MB

    // BAD: this Runnable holds a reference to LeakExample.this
    Runnable leak = new Runnable() {
        public void run() { System.out.println(data.length); }
    };
}
// If 'leak' is stored somewhere, LeakExample (and its 1MB) is never GC'd

// FIX: use a static nested class or a lambda that captures only needed values
static Runnable noLeak(byte[] data) {
    return () -> System.out.println(data.length); // captures array, not outer
}

مقارنة الصنف الداخلي بـ Lambda

غالبًا ما تستبدل تعبيرات Lambda في Java الحديثة الأصناف الداخلية المجهولة للواجهات ذات الأسلوب الواحد. وتكون Lambda أكثر اختصارًا، لكنها لا تملك this خاصًا بها.

// Old way: anonymous inner class
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running (inner class) — this is the Runnable");
    }
};

// Modern: lambda
Runnable r2 = () -> System.out.println("Running (lambda)");

r1.run();
r2.run();

// Lambda this = enclosing class (not the Runnable)
// Inner class this = the Runnable instance itself

متى تُستخدم الأصناف الداخلية

استخدم الأصناف الداخلية غير الساكنة عندما:

  • يحتاج الصنف إلى الوصول إلى الحالة الخاصة للمثيل الحاوي
  • يرتبط كل مثيل داخلي منطقيًا بمثيل خارجي محدد
  • تستخدم نمط Iterator لتغليف منطق الاجتياز

تطبيق عملي: مُقسِّم الصفحات

مُقسِّم صفحات عملي تحتفظ فيه الفئة الداخلية بحالة الصفحة وتصل إلى قائمة البيانات الخارجية.

import java.util.*;

class Paginator<T> {
    private final List<T> items;
    private final int pageSize;

    Paginator(List<T> items, int pageSize) {
        this.items = items; this.pageSize = pageSize;
    }

    class Page {
        private int pageIndex = 0;

        List<T> current() {
            int start = pageIndex * pageSize;
            int end = Math.min(start + pageSize, items.size()); // outer
            return start < items.size() ? items.subList(start, end) : List.of();
        }
        boolean hasNext() { return (pageIndex + 1) * pageSize < items.size(); }
        void next() { if (hasNext()) pageIndex++; }
    }
}
Paginator<String> p = new Paginator<>(List.of("a","b","c","d","e"), 2);
Paginator.Page page = p.new Page();
System.out.println(page.current()); // [a, b]
page.next();
System.out.println(page.current()); // [c, d]

تحقق سريع

ما الذي تحتفظ به الفئة الداخلية (غير الساكنة) ولا تحتفظ به الفئة المتداخلة الساكنة؟

مراجعة: الفئات الداخلية والوصول إلى الفئة الخارجية

أهم النقاط:

  • تحتفظ الفئات الداخلية غير الساكنة بمرجع ضمني إلى مثيل الفئة الخارجية المحتوية
  • أنشئ مثيلات الفئات الداخلية فقط من خلال مثيل للفئة الخارجية: outer.new Inner()
  • يمكن للفئات الداخلية الوصول إلى جميع أعضاء الفئة الخارجية، بما في ذلك الحقول الخاصة
  • استخدم Outer.this.field لإزالة الالتباس بين الحقول المحجوبة
  • أفضل حالات الاستخدام: Iterator ومستمعات الأحداث وهياكل البيانات الداخلية
  • الخطر: تمنع مثيلات الفئات الداخلية مثيلات الفئة الخارجية من أن يجمعها جامع القمامة

الأسئلة الشائعة

هل درس «الفئات الداخلية والوصول إلى الفئة الخارجية» مجاني؟

نعم — نص درس «الفئات الداخلية والوصول إلى الفئة الخارجية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.

ماذا ستتعلم في «الفئات الداخلية والوصول إلى الفئة الخارجية»؟

استخدم فئات داخلية غير ثابتة تحتفظ بمرجع إلى كائن الفئة الخارجية تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟

لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «الفئات الداخلية والوصول إلى الفئة الخارجية»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟

نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الفئات المتداخلة الثابتة
  2. الفئات الداخلية والوصول إلى الفئة الخارجية
  3. الفئات المحلية والمجهولة
  4. اختيار استراتيجية التداخل المناسبة
← العودة إلى Java Academy