JavaRush /جاوا بلاگ /Random-UR /ArrayList.forEach میں لیمبڈاس اور طریقہ کار کے حوالے - یہ...

ArrayList.forEach میں لیمبڈاس اور طریقہ کار کے حوالے - یہ کیسے کام کرتا ہے۔

گروپ میں شائع ہوا۔
جاوا سنٹیکس زیرو کی تلاش میں لیمبڈا اظہار کا تعارف ایک خاص مثال سے شروع ہوتا ہے:
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
لیکچر کے مصنفین ArrayList کلاس کے ہر فنکشن کے لیے معیاری استعمال کرتے ہوئے لیمبڈاس اور طریقہ کار کے حوالہ جات کو پارس کرتے ہیں۔ ذاتی طور پر، مجھے یہ سمجھنا مشکل تھا کہ کیا ہو رہا ہے، کیونکہ اس فنکشن کے نفاذ کے ساتھ ساتھ اس سے منسلک انٹرفیس بھی "ہڈ کے نیچے" رہتا ہے۔ دلیل (s) کہاں سے آتی ہے ، جہاں سے println() فنکشن پاس ہوتا ہے وہ سوالات ہیں جن کا جواب ہمیں خود دینا پڑے گا۔ خوش قسمتی سے، IntelliJ IDEA کے ساتھ، ہم آسانی سے ArrayList کلاس کے اندرونی حصوں کو دیکھ سکتے ہیں اور اس نوڈل کو شروع سے ہی کھول سکتے ہیں۔ اگر آپ کو بھی کچھ سمجھ نہیں آرہا ہے اور اس کا پتہ لگانا چاہتے ہیں تو میں کم از کم اس میں آپ کی مدد کرنے کی کوشش کروں گا۔ لیمبڈا ایکسپریشن اور ArrayList.forEach - یہ کیسے کام کرتا ہے لیکچر سے ہم پہلے ہی جان چکے ہیں کہ لیمبڈا ایکسپریشن ایک فنکشنل انٹرفیس کا نفاذ ہے ۔ یعنی، ہم ایک ہی فنکشن کے ساتھ ایک انٹرفیس کا اعلان کرتے ہیں، اور یہ بیان کرنے کے لیے لیمبڈا استعمال کرتے ہیں کہ یہ فنکشن کیا کرتا ہے۔ ایسا کرنے کے لیے آپ کو ضرورت ہے: 1. ایک فعال انٹرفیس بنائیں۔ 2. ایک متغیر بنائیں جس کی قسم فنکشنل انٹرفیس سے مطابقت رکھتی ہو۔ 3. اس متغیر کو ایک لیمبڈا ایکسپریشن تفویض کریں جو فنکشن کے نفاذ کو بیان کرتا ہے۔ 4. ایک متغیر تک رسائی حاصل کرکے فنکشن کو کال کریں (شاید میں اصطلاحات میں خام ہوں، لیکن یہ سب سے واضح طریقہ ہے)۔ میں گوگل کی طرف سے ایک سادہ سی مثال دوں گا، اس پر تفصیلی تبصرے (سائٹ metanit.com کے مصنفین کا شکریہ):
interface Operationable {
    int calculate(int x, int y);
    // Единственная функция в интерфейсе — значит, это функциональный интерфейс,
    // который можно реализовать с помощью лямбды
}

public class LambdaApp {

    public static void main(String[] args) {

        // Создаём переменную operation типа Operationable (так называется наш функциональный интерфейс)
        Operationable operation;
        // Прописываем реализацию функции calculate с помощью лямбды, на вход подаём x и y, на выходе возвращаем их сумму
        operation = (x,y)->x+y;

        // Теперь мы можем обратиться к функции calculate через переменную operation
        int result = operation.calculate(10, 20);
        System.out.println(result); //30
    }
}
اب لیکچر سے مثال کی طرف لوٹتے ہیں۔ اسٹرنگ کی قسم کے کئی عناصر فہرست کے مجموعہ میں شامل کیے گئے ہیں ۔ پھر عناصر کو معیاری forEach فنکشن کا استعمال کرتے ہوئے بازیافت کیا جاتا ہے، جسے فہرست آبجیکٹ پر کہا جاتا ہے ۔ کچھ عجیب پیرامیٹر s کے ساتھ ایک لیمبڈا اظہار فنکشن کی دلیل کے طور پر پاس کیا جاتا ہے ۔
ArrayList<string> list = new ArrayList<>();
Collections.addAll(list, "Hello", "How", "дела?");

list.forEach( (s) -> System.out.println(s) );
اگر آپ فوری طور پر یہ نہیں سمجھتے کہ یہاں کیا ہوا ہے، تو آپ اکیلے نہیں ہیں۔ خوش قسمتی سے، IntelliJ IDEA کے پاس ایک زبردست کی بورڈ شارٹ کٹ ہے: Ctrl+Left_Mouse_Button ۔ اگر ہم forEach پر ہوور کریں اور اس مجموعہ پر کلک کریں تو معیاری ArrayList کلاس کا سورس کوڈ کھل جائے گا، جس میں ہم forEach طریقہ کا نفاذ دیکھیں گے :
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++)
        action.accept(elementAt(es, i));
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
ہم دیکھتے ہیں کہ ان پٹ دلیل کنزیومر کی قسم کی کارروائی ہے ۔ آئیے کرسر کو لفظ Consumer پر منتقل کریں اور جادوئی امتزاج Ctrl+LMB کو دوبارہ دبائیں ۔ کنزیومر انٹرفیس کی تفصیل کھل جائے گی ۔ اگر ہم اس سے پہلے سے طے شدہ نفاذ کو ہٹا دیتے ہیں (ابھی یہ ہمارے لیے اہم نہیں ہے)، تو ہم درج ذیل کوڈ دیکھیں گے۔
public interface Consumer<t> {
   void accept(T t);
}
تو ہمارے پاس ایک کنزیومر انٹرفیس ہے جس میں ایک ہی قبول فنکشن ہے جو کسی بھی قسم کی ایک دلیل کو قبول کرتا ہے۔ چونکہ صرف ایک فنکشن ہے، تو انٹرفیس فعال ہے، اور اس کے نفاذ کو لیمبڈا اظہار کے ذریعے لکھا جا سکتا ہے۔ ہم پہلے ہی دیکھ چکے ہیں کہ ArrayList میں forEach فنکشن ہوتا ہے جو کنزیومر انٹرفیس کو ایکشن دلیل کے طور پر نافذ کرتا ہے ۔ اس کے علاوہ، forEach فنکشن میں ہمیں درج ذیل کوڈ ملتا ہے:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
for loop بنیادی طور پر ایک ArrayList کے تمام عناصر کے ذریعے اعادہ کرتا ہے۔ لوپ کے اندر ہمیں ایکشن آبجیکٹ کے قبول فنکشن کے لیے ایک کال نظر آتی ہے - یاد رکھیں کہ ہم نے آپریشن کو کیسے کہا تھا؟ مجموعہ کے موجودہ عنصر کو قبول فنکشن میں منتقل کیا جاتا ہے ۔ اب ہم آخر میں اصل لیمبڈا اظہار پر واپس جا سکتے ہیں اور سمجھ سکتے ہیں کہ یہ کیا کرتا ہے۔ آئیے تمام کوڈ کو ایک ڈھیر میں جمع کریں:
public interface Consumer<t> {
   void accept(T t); // Функция, которую мы реализуем лямбда-выражением
}

public void forEach(Consumer<? super E> action) // В action хранится an object Consumer, в котором функция accept реализована нашей лямбдой {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++)
        action.accept(elementAt(es, i)); // Вызываем нашу реализацию функции accept интерфейса Consumer для каждого element коллекции
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

//...

list.forEach( (s) -> System.out.println(s) );
ہمارا لیمبڈا اظہار کنزیومر انٹرفیس میں بیان کردہ قبول فنکشن کا نفاذ ہے ۔ لیمبڈا کا استعمال کرتے ہوئے، ہم نے واضح کیا کہ قبول فنکشن ایک دلیل لیتا ہے اور اسے اسکرین پر دکھاتا ہے۔ لیمبڈا ایکسپریشن کو forEach فنکشن کو اس کے ایکشن دلیل کے طور پر منتقل کیا گیا تھا، جو کنزیومر انٹرفیس کے نفاذ کو اسٹور کرتا ہے ۔ اب forEach فنکشن ہمارے کنزیومر انٹرفیس کے نفاذ کو اس طرح کی لائن کے ساتھ کال کر سکتا ہے:
action.accept(elementAt(es, i));
اس طرح، lambda اظہار میں ان پٹ آرگیومنٹ s ArrayList مجموعہ کا ایک اور عنصر ہے ، جو ہمارے کنزیومر انٹرفیس کے نفاذ کو دیا جاتا ہے ۔ بس: ہم نے ArrayList.forEach میں لیمبڈا اظہار کی منطق کا تجزیہ کیا ہے۔ ArrayList.forEach میں ایک طریقہ کا حوالہ - یہ کیسے کام کرتا ہے؟ لیکچر کا اگلا مرحلہ طریقہ کے حوالہ جات کو دیکھنا ہے۔ سچ ہے، وہ اسے بہت ہی عجیب طریقے سے سمجھتے ہیں - لیکچر پڑھنے کے بعد، مجھے یہ سمجھنے کا کوئی موقع نہیں ملا کہ یہ کوڈ کیا کرتا ہے:
list.forEach( System.out::println );
سب سے پہلے، ایک چھوٹا سا نظریہ پھر. ایک طریقہ کا حوالہ ہے، بہت موٹے الفاظ میں، ایک فنکشنل انٹرفیس کا نفاذ جو کسی دوسرے فنکشن کے ذریعہ بیان کیا گیا ہے ۔ ایک بار پھر، میں ایک سادہ مثال کے ساتھ شروع کروں گا:
public interface Operationable {
    int calculate(int x, int y);
    // Единственная функция в интерфейсе — значит, это функциональный интерфейс
}

public static class Calculator {
    // Создадим статический класс Calculator и пропишем в нём метод methodReference.
    // Именно он будет реализовывать функцию calculate из интерфейса Operationable.
    public static int methodReference(int x, int y) {
        return x+y;
    }
}

public static void main(String[] args) {
    // Создаём переменную operation типа Operationable (так называется наш функциональный интерфейс)
    Operationable operation;
    // Теперь реализацией интерфейса будет не лямбда-выражение, а метод methodReference из нашего класса Calculator
    operation = Calculator::methodReference;

    // Теперь мы можем обратиться к функции интерфейса через переменную operation
    int result = operation.calculate(10, 20);
    System.out.println(result); //30
}
آئیے لیکچر سے مثال کی طرف لوٹتے ہیں:
list.forEach( System.out::println );
میں آپ کو یاد دلاتا ہوں کہ System.out PrintStream قسم کا ایک آبجیکٹ ہے جس میں println فنکشن ہوتا ہے ۔ آئیے پرنٹ ایل این پر ہوور کریں اور کلک کریں Ctrl+LMB :
public void println(String x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
آئیے دو اہم خصوصیات کو نوٹ کریں: 1. پرنٹ ایل این فنکشن کچھ بھی واپس نہیں کرتا (باطل)۔ 2. println فنکشن ایک دلیل بطور ان پٹ وصول کرتا ہے۔ کیا آپ کو کچھ یاد نہیں آتا؟
public interface Consumer<t> {
   void accept(T t);
}
یہ ٹھیک ہے - قبول فنکشن دستخط پرنٹ ایلن میتھڈ دستخط کا زیادہ عام معاملہ ہے ! اس کا مطلب یہ ہے کہ مؤخر الذکر کو ایک طریقہ کے حوالے کے طور پر کامیابی کے ساتھ استعمال کیا جا سکتا ہے - یعنی println قبول فنکشن کا ایک مخصوص نفاذ بن جاتا ہے :
list.forEach( System.out::println );
ہم نے System.out آبجیکٹ کے println فنکشن کو forEach فنکشن کی دلیل کے طور پر پاس کیا ۔ اصول وہی ہے جو لیمبڈا کے ساتھ ہے: اب forEach ایک کلیکشن عنصر کو ایکشن کے ذریعے پرنٹ ایل این فنکشن میں پاس کر سکتا ہے۔accept(elementAt(es,i)) کال ۔ درحقیقت، اسے اب System.out.println(elementAt(es, i)) کے طور پر پڑھا جا سکتا ہے ۔
public void forEach(Consumer<? super E> action) // В action хранится an object Consumer, в котором функция accept реализована методом println {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        final Object[] es = elementData;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++)
            action.accept(elementAt(es, i)); // Функция accept теперь реализована методом System.out.println!
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
مجھے امید ہے کہ میں نے کم از کم ان لوگوں کے لیے جو لیمبڈاس اور طریقہ کار کے حوالے سے نئے ہیں صورتحال کو واضح کر دیا ہے۔ آخر میں، میں رابرٹ شیلڈٹ کی مشہور کتاب "جاوا: اے بیگنرز گائیڈ" تجویز کرتا ہوں - میری رائے میں، اس میں لیمبڈاس اور فنکشن کے حوالے کافی سمجھداری سے بیان کیے گئے ہیں۔
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION