سؤال

نظرًا لتطبيق Java Generics، لا يمكنك الحصول على تعليمات برمجية مثل هذا:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

كيف يمكنني تنفيذ ذلك مع الحفاظ على سلامة النوع؟

لقد رأيت حلاً في منتديات Java يسير على النحو التالي:

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

لكنني حقًا لا أفهم ما يحدث.

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

المحلول

يجب أن أطرح سؤالاً في المقابل:هو الخاص بك GenSet "محددة" أو "غير محددة"؟ماذا يعني ذالك؟

  • التحقق: الكتابة القوية. GenSet يعرف بوضوح نوع الكائنات التي يحتوي عليها (أيتم استدعاء مُنشئه صراحةً بـ a Class<E> الوسيطة، وستطرح الأساليب استثناءً عند تمرير وسيطات ليست من النوع E.يرى Collections.checkedCollection.

    -> في هذه الحالة عليك أن تكتب:

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
    
  • غير محدد: كتابة ضعيفة.لا يتم إجراء أي فحص للنوع فعليًا على أي من الكائنات التي تم تمريرها كوسيطة.

    -> في هذه الحالة عليك أن تكتب

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }
    

    لاحظ أن نوع مكون المصفوفة يجب أن يكون محو معلمة النوع:

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }
    

كل هذا ناتج عن ضعف معروف ومتعمد في الأدوية الجنيسة في Java:تم تنفيذه باستخدام المحو، لذلك لا تعرف الفئات "العامة" وسيطة النوع التي تم إنشاؤها بها في وقت التشغيل، وبالتالي لا يمكنها توفير أمان النوع ما لم يتم تنفيذ بعض الآليات الواضحة (التحقق من النوع).

نصائح أخرى

ويمكنك القيام بذلك:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

وهذه هي واحدة من الطرق المقترحة لتنفيذ مجموعة عام في جافا الفعالة. البند 26 . عدم وجود أخطاء نوع، لا حاجة ليلقي مجموعة مرارا وتكرارا. ولكن هذا يطلق تحذيرا لأنه يحتمل أن تكون خطرة، وينبغي استخدامها بحذر. كما هو مفصل في التعليقات، وهذا Object[] الآن يتنكر لدينا نوع E[]، ويمكن أن يسبب أخطاء غير متوقعة أو ClassCastExceptions إذا ما استخدمت بطريقة غير آمنة.

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


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

وفيما يلي كيفية استخدام الأدوية للحصول على مجموعة من بالضبط نوع كنت تبحث عن مع الحفاظ على سلامة نوع (على العكس من الإجابات الأخرى، التي إما أن تعطيك عودة مجموعة Object أو تؤدي إلى التحذيرات في وقت الترجمة) :

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

وهذا يجمع دون التحذيرات، وكما ترون في main، لأي نوع قمت بتعريف مثيل GenSet كما يمكنك تعيين a إلى مجموعة من هذا النوع، ويمكنك تعيين عنصر من a إلى متغير هذا النوع، وهذا يعني أن مجموعة والقيم في مجموعة هي من النوع الصحيح.

وكان يعمل باستخدام الحرفية الدرجة كنوع وقت الرموز، كما نوقش في في جافا دروس . يتم التعامل مع الحرفية الدرجة من قبل المجمع مع حالات java.lang.Class. لاستخدام واحد، ببساطة اتبع اسم فئة مع .class. لذلك، يعمل String.class ككائن Class تمثل String الصف. هذا وتعمل أيضا من أجل واجهات، وتتضمن التعدادات، أي الأبعاد صفائف (مثل String[].class)، البدائيون (مثل int.class)، وvoid الكلمة (أي void.class).

وClass نفسها هي عامة (كما أعلن Class<T>، حيث تقف T لنوع أن الكائن Class وتمثل)، وهذا يعني أن هذا النوع من String.class هو Class<String>.

وهكذا، كلما استدعاء المنشئ للGenSet، الذي يمر في فئة الحرفي للحجة الأولى تمثل مجموعة من المقام GenSet من نوع المعلنة (على سبيل المثال String[].class لGenSet<String>). لاحظ أنك لن تكون قادرة على الحصول على مجموعة من الأوليات، منذ البدائيون لا يمكن أن تستخدم لمتغيرات النوع.

وداخل المنشئ، استدعاء الأسلوب cast إرجاع مرت يلقي الحجة Object إلى فئة ممثلة في وجوه Class الذي تم استدعاء الأسلوب. استدعاء newInstance طريقة ثابتة في عوائد java.lang.reflect.Array باعتبارها Object صفيف من نوع ممثلة في وجوه Class تمرير بوصفها الوسيطة الأولى وطول المحدد من قبل int تمرير بوصفها الوسيطة الثانية. استدعاء الأسلوب getComponentType بإرجاع كائن Class يمثل نوع مكون من مجموعة ممثلة من قبل الكائن Class التي كانت تسمى طريقة (String.class على سبيل المثال لString[].class، null إذا كان الكائن Class لا تمثل مجموعة).

وهذه الجملة الأخيرة ليست دقيقة تماما. داعيا String[].class.getComponentType() بإرجاع كائن Class تمثل String الدرجة، ولكن نوعه هو Class<?>، وليس Class<String>، الذي هو السبب في أنك لا تستطيع أن تفعل شيئا كما يلي.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

والشيء نفسه ينطبق على كل وسيلة في Class يقوم بإرجاع كائن Class.

وفيما يتعلق تعليق يواكيم سوير على هذه الإجابة (I لم يكن لديك ما يكفي من سمعة التعليق على نفسي)، فإن المثال باستخدام المدلى بها لT[] يؤدي إلى تحذير لأن المترجم لا يمكن أن تضمن سلامة نوع في هذه الحالة.


وتحرير بخصوص تعليق إنغو في:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

وهذا هو الجواب الوحيد الذي هو نوع آمنة

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

لتمتد إلى أبعاد أكثر، فقط إضافة معلمات [] والبعد إلى newInstance() (T هو نوع معلمة، cls هو Class<T>، d1 من خلال d5 أعداد صحيحة):

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

وانظر <لأ href = "HTTP: //docs.oracle.com/javase/7/docs/api/java/lang/reflect/Array.html#newInstance٪28java.lang.Class،٪20int ... ٪ 29 "يختلط =" noreferrer "> Array.newInstance() للحصول على مزيد من التفاصيل.

في جاوة 8، يمكننا أن نفعل نوع من خلق مجموعة عامة باستخدام امدا أو طريقة مرجعية. هذا هو مماثل لنهج العاكسة (التي يمر Class)، ولكن هنا لا نستخدم التفكير.

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

وعلى سبيل المثال، يتم استخدام هذا من قبل <وأ href = "http://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#toArray-java.util.function .IntFunction- "يختلط =" noreferrer "> <A> A[] Stream.toArray(IntFunction<A[]>) .

وهذا <م> قد أيضا أن يتم ذلك قبل جافا 8 باستخدام الفئات مجهول لكنه أكثر تعقيدا.

يتم تناول هذا في الفصل 5 (الأسماء العامة) من جافا الفعالة، الطبعة الثانية, البند 25 ...تفضيل القوائم على المصفوفات

سيعمل الكود الخاص بك، على الرغم من أنه سيولد تحذيرًا غير محدد (والذي يمكنك منعه باستخدام التعليق التوضيحي التالي:

@SuppressWarnings({"unchecked"})

ومع ذلك، قد يكون من الأفضل استخدام القائمة بدلاً من المصفوفة.

هناك مناقشة مثيرة للاهتمام حول هذا الخطأ/الميزة موقع مشروع OpenJDK.

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

ومنشئ Stack(Class<T> clazz,int capacity) العام يتطلب منك تمرير كائن الفئة في وقت التشغيل، مما يعني معلومات فئة <م> هو متوفرة في وقت التشغيل إلى التعليمات البرمجية التي يحتاج إليها. ويعني شكل Class<T> أن المجمع سوف تحقق أن الكائن الفئة التي تمرر على وجه التحديد هو كائن Class للنوع T. ليس فئة فرعية من T، وليس الفائقة من T، ولكن تحديدا T.

وهذا بعد ذلك يعني أنه يمكنك إنشاء كائن مجموعة من النوع المناسب في المنشئ الخاص بك، مما يعني أن هذا النوع من الكائنات التي تم تخزينها في مجموعتك سيكون أنواعها فحص في نقطة إضافتها إلى المجموعة.

مرحباً، على الرغم من أن الموضوع ميت، أود أن ألفت انتباهكم إلى هذا:

يتم استخدام الأدوية العامة للتحقق من النوع أثناء وقت الترجمة:

  • ولذلك فإن الغرض هو التأكد من أن ما يأتي هو ما تحتاجه.
  • ما تعيده هو ما يحتاجه المستهلك.
  • افحص هذا:

enter image description here

لا تقلق بشأن تحذيرات الكتابة عند كتابة فصل عام.تقلق عند استخدامه.

وماذا عن هذا الحل؟

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

وكان يعمل وتبدو بسيطة جدا ليكون صحيحا. هل هناك أي عيب؟

وبحث أيضا إلى هذا الرمز:

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

وكان تحويل قائمة من أي نوع من كائن إلى مجموعة من نفس النوع.

ولقد وجدت وسيلة سريعة وسهلة أن يعمل بالنسبة لي. لاحظ أن ولقد استخدمت هذا فقط في جاوا JDK 8. أنا لا أعرف ما اذا كانت ستعمل مع الإصدارات السابقة.

وعلى الرغم من أننا لا يمكن إنشاء مثيل مجموعة عامة من معلمة نوع محددة، يمكننا تمرير صفيف تم إنشاؤها بالفعل إلى منشئ فئة عام.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

والآن في main يمكننا خلق مجموعة مثل ذلك:

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

لمزيد من المرونة مع صفائف الخاص بك يمكنك استخدام قائمة مرتبطة على سبيل المثال. وArrayList وغيرها من الأساليب وجدت في فئة Java.util.ArrayList.

وأنت لا تحتاج إلى تمرير حجة الدرجة إلى منشئ. جرب هذا.

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

و

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

والنتيجة:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

والمثال هو استخدام انعكاس جافا لإنشاء صفيف. القيام بذلك بشكل عام لا ينصح، لأنه ليس من typesafe. بدلا من ذلك، ما يجب عليك القيام به هو مجرد استخدام قائمة الداخلية، وتجنب مجموعة على الإطلاق.

لقد قمت بإنشاء مقتطف التعليمات البرمجية هذا لإنشاء مثيل لفصل تم تمريره لأداة اختبار آلية بسيطة.

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

لاحظ هذا المقطع:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

لبدء المصفوفة حيث Array.newInstance (فئة المصفوفة وحجم المصفوفة).يمكن أن تكون الفئة بدائية (int.class) وكائنًا (Integer.class).

BeanUtils جزء من Spring.

والواقع أسهل طريقة للقيام بذلك، هو خلق مجموعة من الأشياء ويطرح للنوع المطلوب كما في المثال التالي:

T[] array = (T[])new Object[SIZE];

وحيث SIZE هو ثابت وT هو نوع المعرف

وتمرير قائمة القيم ...

public <T> T[] array(T... values) {
    return values;
}

وواضطر يلقي اقترح من قبل أشخاص آخرين لم تنجح بالنسبة لي، ورمي استثناء من صب غير قانوني.

ولكن، وهذا يلقي ضمني يعمل على ما يرام:

Item<K>[] array = new Item[SIZE];

وحيث السلعة بسهولة فئة I محددة تحتوي على عضو:

private K value;

وبهذه الطريقة يمكنك الحصول على مجموعة واسعة من نوع K (إذا كان العنصر لديه فقط قيمة) أو أي نوع عام التي تريد تعريفها في فئة البند.

لا أحد آخر قد أجاب على سؤال ما يجري في المثال قمت بنشرها.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

وكما قال آخرون الأدوية و"تمحى" أثناء الترجمة. حتى في وقت التشغيل مثيل عام لا يعرف ما هو نوع عنصرها. والسبب في ذلك هو تاريخي، والشمس أريد أن أضيف الأدوية دون كسر واجهة القائمة (المصدر وثنائي).

وصالحة من ناحية أخرى <م> لا يعرف نوع المكون الخاصة بهم في وقت التشغيل.

وهذا المثال يعمل على حل المشكلة عن طريق الحصول على التعليمات البرمجية الذي يستدعي المنشئ (التي لا تعرف نوع) تمرير معلمة قول فئة النوع المطلوب.

وهكذا التطبيق سيكون بناء الطبقة مع شيء من هذا القبيل

Stack<foo> = new Stack<foo>(foo.class,50)

وومنشئ يعرف الآن (في وقت التشغيل) ما هو نوع العنصر ويمكن استخدام هذه المعلومات لبناء مجموعة من خلال API التفكير.

Array.newInstance(clazz, capacity);

وأخيرا لدينا نوع الزهر لأن المترجم لا يوجد لديه وسيلة لمعرفة أن مجموعة إرجاعها بواسطة Array#newInstance() هو النوع الصحيح (على الرغم من أننا نعرف).

وهذا الاسلوب هو قبيح قليلا ولكن يمكن أن يكون أحيانا الحل الاقل سوءا لإنشاء أنواع العامة التي لا تحتاج إلى معرفة نوع المكون الخاصة بهم في وقت لأي سبب من الأسباب (خلق المصفوفات، أو خلق حالات من نوع المكون، الخ.) .

لقد وجدت نوعًا من الحل لهذه المشكلة.

يلقي السطر أدناه خطأً عامًا في إنشاء المصفوفة

List<Person>[] personLists=new ArrayList<Person>()[10];

ومع ذلك إذا قمت بتغليف List<Person> في فئة منفصلة، ​​فإنه يعمل.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

يمكنك تعريض الأشخاص في فئة PersonList من خلال أداة getter.سيعطيك السطر أدناه مصفوفة تحتوي على List<Person> في كل عنصر.وبعبارة أخرى مجموعة من List<Person>.

PersonList[] personLists=new PersonList[10];

كنت بحاجة إلى شيء من هذا القبيل في بعض التعليمات البرمجية التي كنت أعمل عليها وهذا ما فعلته لتشغيله.حتى الآن لا توجد مشاكل.

هل يمكن إنشاء مجموعة كائن ويطرح للê في كل مكان. نعم، انها ليست وسيلة نظيفة جدا للقيام بذلك ولكن يجب أن لا يقل عن العمل.

وهذه محاولة.

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

وللمشكلة سهلة، وإن كان فوضوي لهذا سيكون لعش الثانية "حامل" الطبقة داخل الطبقة الرئيسية الخاصة بك، واستخدامها للاحتفاظ بالبيانات الخاصة بك.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

وربما لا علاقة لها على هذا السؤال ولكن بينما كنت الحصول على الخطأ "generic array creation" لاستخدام

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

وأجد من الأعمال التالية (وعملت بالنسبة لي) مع @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

وأنا أتساءل عما اذا كان هذا الرمز من شأنه أن يخلق مجموعة عامة فعالة؟

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

وتحرير: ربما وسيلة بديلة لإنشاء مثل صفيف، إذا كان من المعروف أن حجم يطلب منك والصغيرة، سيكون لمجرد إطعام العدد المطلوب من الصورة "فارغة" في الأمر zeroArray

وعلى الرغم من الواضح أن هذا ليس تنوعا مثل استخدام رمز createArray.

هل يمكن استخدام جبيرة:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

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

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

وبعد ذلك في الطبقة مجموعة الخاص بك فقط انها قد تبدأ كما يلي:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

وبدء new Generic Invoker[] سوف يسبب مشكلة مع لحالها ولكن لا ينبغي أن يكون هناك في الواقع أي مشاكل.

ليحصل من مجموعة يجب عليك استدعاء مجموعة [أنا] .variable مثل ذلك:

public T get(int index){
    return array[index].variable;
}

وأما بقية، مثل تغيير حجم مجموعة يمكن القيام به مع Arrays.copyOf () كما يلي:

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

ووظيفة إضافية يمكن أن تضاف مثل ذلك:

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

وغير مسموح إنشاء مجموعة عام في جافا ولكن يمكنك أن تفعل ذلك مثل

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top