باگ در Stream API جاوا ۸

در Stream API جاوا ۸ خطایی پیدا شده که باعث شده عملکرد مورد انتظار آن مشاهده نشود. این مساله برطرف شده است اما شاید برایتان جالب باشد بدانید دقیقا مساله چه بوده است.
Stream API روی SubList از یک ArrayList آن طور که انتظار میرود کار نمیکند.
Stream API از lazy evaluation پشتیبانی میکند. این بدان معناست که عملیاتهای میانی تا اتمام عملیات پایانی اعمال نمیشوند. مثال زیر را ببینید.
1 2 3 4 5 6 7 8 9 10 11 | List<Integer> ints = new ArrayList<>(); ints.add(1); ints.add(2); ints.add(3); ints.add(4); ints.add(5); ints.add(6); Stream stream = ints.stream() .peek(System.out::println) .filter(i -> i % 2 == 0); |
اگر کد بالا را اجرا کنیم خروجی نخواهد داشت. عملیاتهای peak و filter ارزیابی نخواهند شد. اینها همواره lazy هستند. اما اگر یک عملیات پایانی برای مثال forEach قرار دهیم، انجام خواهند شد.
اگر این چنین است، کد زیر هم همیشه درست است.
1 2 3 4 5 6 7 8 9 | List<Integer> ints = new ArrayList<>(); ints.add(1); ints.add(2); ints.add(3); ints.add(4); Stream stream = ints.stream(); ints.add(5); ints.add(6); stream.forEach(System.out::println); |
ما یک لیست از اعداد صحیح ساختیم و تعدادی عدد صحیح اضافه کردیم. سپس متد stream را فراخوانی کرده و در یک متغیر قرار دادیم. دوباره تعدادی عدد صحیح دیگر اضافه کردیم. از آنجایی که Stream API در جاوا ۸ lazy است تا زمانی که عملیات forEach را صدا نزنیم هیچ اتفاقی نمیافتد.
مثال بالا به خوبی کار میکند. حالا بیایید یک زیرلیست از لیست اعداد صحیح ایجاد کرده و چند عدد صحیح اضافه کنیم.
1 2 3 4 5 6 7 8 9 10 | List<Integer> ints = new ArrayList<>(); ints.add(1); ints.add(2); ints.add(3); ints.add(4); ints = ints.subList(0, 2); Stream stream = ints.stream(); ints.add(5); ints.add(6); stream.forEach(System.out::println); |
حال اگر کد بالا را اجرا کنیم خطای ConcurrentModificationException پرتاب میکند.
این یک باگ جاواست که گزارش شده و در نسخه ۹ جاوا مرتفع گردیده است. جزئیات این خطا را در این لینک بخوانید.
با این حال، این خطا به Stream API مربوط نیست بلکه به پیادهسازی Spliterator در ArrayList مرتبط است.
در جاوا ۸، یک واسط کاربری جدید به بسته java.util به اسم Spliterator اضافه شده است. میدانیم که چارچوب collection در جاوا یک واسط کاربری Iterator برای پیمایش مجموعهها دارد. این واسط کاربری با یک تابع جدید spliterator() به روز رسانی شده است که Spliterator برمیگرداند. Spliterator میتواند مجموعه را بشکند و بعضی از المانهای آن را به عنوان یک Spliterator جدا کند. این قابلیت برای پردازش موازی روی بخشهای یک مجموعه اضافه شده است.
ایده آن این است که Spliterator در اساس داده را تقسیم میکند. حال ما میتوانیم به راحتی با استفاده و fork و join کردن، کار را موازی سازیم.این نحوهای است که Stream API دقیقا کار میکند و به همین دلیل به Spliterator وابسته است.
ArrayList یک زیر کلاس به اسم SubList دارد که دارای تابعی است که یک نمونه از یک کلاس داخلی دیگر به نام ArrayListSpliterator را برمیگرداند. که ArrayListSpliterator کلاس پیادهسازی شده از Spliterator میباشد و این همانجایی است که خطاساز است.
هرچند این خطا در نسخه ۹ جاوا برطرف شده است اما کد آن را میتوانید اینجا بخوانید.
منبع:
این خطا رو میشه به همین سادگی دور زد :
ints = new ArrayList(ints.subList(0, 2));