JavaRush /جاوا بلاگ /Random-SD /Lambdas ۽ طريقا حوالا ArrayList.forEach ۾ - اهو ڪيئن ڪم ڪ...

Lambdas ۽ طريقا حوالا 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 ڪلاس جي اندروني حصن کي ڳولي سگھون ٿا ۽ ھن نوڊل کي شروعات کان ئي ختم ڪري سگھون ٿا. جيڪڏهن توهان به ڪجهه نه ٿا سمجهو ۽ ان کي سمجهڻ چاهيو ٿا، ته آئون توهان جي مدد ڪرڻ جي ڪوشش ڪندس گهٽ ۾ گهٽ. Lambda ايڪسپريشن ۽ ArrayList.forEach - اهو ڪيئن ڪم ڪري ٿو ليڪچر مان اسان اڳ ۾ ئي ڄاڻون ٿا ته lambda ايڪسپريشن هڪ فنڪشنل انٽرفيس جو عمل آهي . اهو آهي، اسان هڪ انٽرفيس جو اعلان ڪريون ٿا هڪ واحد فنڪشن سان، ۽ هڪ lambda استعمال ڪريو بيان ڪرڻ لاءِ ته هي فنڪشن ڇا ڪري ٿو. هن کي ڪرڻ لاءِ توهان کي ضرورت آهي: 1. هڪ فنڪشنل انٽرفيس ٺاهيو؛ 2. ھڪڙو متغير ٺاھيو جنھن جو قسم فنڪشنل انٽرفيس سان ملندو آھي؛ 3. هن متغير کي هڪ ليمبڊا ايڪسپريشن مقرر ڪريو جيڪو بيان ڪري ٿو فنڪشن جي عمل کي؛ 4. ھڪ فنڪشن کي ڪال ڪريو ھڪ variable تائين پھچڻ سان (شايد مان اصطلاح ۾ خام آھيان، پر اھو صاف ترين طريقو آھي). مان گوگل کان هڪ سادي مثال ڏيندس، ان کي تفصيلي تبصرن سان مهيا ڪندي (سائيٽ 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
    }
}
هاڻي اچون ٿا ليڪچر مان مثال ڏي. لسٽ جي مجموعن ۾ String قسم جا ڪيترائي عنصر شامل ڪيا ويا آھن . عناصر وري حاصل ڪيا ويا معياري استعمال ڪندي هر فنڪشن لاء، جنهن کي سڏيو ويندو آهي لسٽ اعتراض تي . ڪجهه عجيب پيراميٽر سان گڏ هڪ lambda اظهار 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);
}
سو. اسان وٽ هڪ صارف انٽرفيس آهي هڪ واحد قبول فنڪشن سان جيڪو ڪنهن به قسم جي هڪ دليل کي قبول ڪري ٿو. جيئن ته اتي صرف هڪ فنڪشن آهي، پوء انٽرفيس فنڪشنل آهي، ۽ ان تي عمل درآمد هڪ lambda اظهار ذريعي لکي سگهجي ٿو. اسان اڳ ۾ ئي ڏسي چڪا آهيون ته ArrayList وٽ هر هڪ فنڪشن آهي جيڪو صارف جي انٽرفيس کي عمل جي دليل طور لاڳو ڪري ٿو . ان کان علاوه، هر فنڪشن لاء اسان هيٺ ڏنل ڪوڊ ڳوليندا آهيون:
for (int i = 0; modCount == expectedModCount && i < size; i++)
    action.accept(elementAt(es, i));
لوپ لاءِ لازمي طور تي هڪ ArrayList جي سڀني عنصرن ذريعي ورجائي ٿو. لوپ جي اندر اسان ايڪشن اعتراض جي قبول فنڪشن لاءِ هڪ ڪال ڏسون ٿا - ياد رکو ته اسان آپريشن.calculate کي ڪيئن سڏيو آهي؟ مجموعي جي موجوده عنصر کي منظور ٿيل فنڪشن ڏانهن منتقل ڪيو ويو آهي . هاڻي اسان آخرڪار اصل لامبڊا اظهار ڏانهن واپس وڃون ٿا ۽ سمجهي سگهون ٿا ته اهو ڇا ڪري ٿو. اچو ته سڀئي ڪوڊ گڏ ڪريون هڪ پائل ۾:
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) );
اسان جي ليمبڊا ايڪسپريس استعمال ڪندڙ انٽرفيس ۾ بيان ڪيل قبول فنڪشن جو نفاذ آهي . هڪ lambda استعمال ڪندي، اسان بيان ڪيو آهي ته قبول فنڪشن هڪ دليل s وٺندو آهي ۽ ان کي اسڪرين تي ڏيکاري ٿو. لامبڊا ايڪسپريس فار هر فنڪشن کي ان جي ايڪشن آرگيومينٽ طور منظور ڪيو ويو، جيڪو ڪنزيومر انٽرفيس جي عمل کي محفوظ ڪري ٿو . ھاڻي ForEach فنڪشن اسان جي ڪنزيومر انٽرفيس جي عمل کي ھن طرح ھڪڙي لائن سان سڏي سگھي ٿو:
action.accept(elementAt(es, i));
اهڙيء طرح، ان پٽ دليل s lambda ايڪسپريس ۾ ArrayList گڏ ڪرڻ جو هڪ ٻيو عنصر آهي ، جيڪو اسان جي صارفين جي انٽرفيس جي عمل درآمد ڏانهن منتقل ڪيو ويو آهي . اهو سڀ ڪجهه آهي: اسان ArrayList.forEach ۾ lambda اظهار جي منطق جو تجزيو ڪيو آهي. 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 فنڪشن آهي . اچو ته هوور ڪريون println تي ۽ ڪلڪ ڪريو Ctrl+LMB :
public void println(String x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
اچو ته ٻه اهم خاصيتون نوٽ ڪريو: 1. println فنڪشن ڪجھ به نه موٽائي ٿو (void). 2. println فنڪشن هڪ دليل ان پٽ طور وصول ڪري ٿو. ڇا توهان کي ڪجهه به ياد نه ٿو اچي؟
public interface Consumer<t> {
   void accept(T t);
}
اهو صحيح آهي - قبول فنڪشن دستخط هڪ وڌيڪ عام صورت آهي println طريقي جي دستخط ! هن جو مطلب اهو آهي ته بعد ۾ ڪاميابيء سان هڪ طريقي جي حوالي سان استعمال ڪري سگهجي ٿو - اهو آهي، println قبول فنڪشن جو هڪ مخصوص عمل بڻجي ٿو :
list.forEach( System.out::println );
اسان System.out اعتراض جي println فنڪشن کي 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