Kirish
Looplar dasturlash tillarining asosiy tuzilmalaridan biridir. Masalan, Oracle veb-saytida "
Dars: Til asoslari " bo'limi mavjud bo'lib, unda tsikllarda alohida dars "
The for Statement " mavjud. Keling, asosiy ma'lumotlarni yangilaymiz: Loop uchta ifodadan (bayonotlardan) iborat:
ishga tushirish (boshlash),
shart (tugatish) va
o'sish (o'sish):
Qizig'i shundaki, ularning barchasi ixtiyoriy, ya'ni agar xohlasak, yozishimiz mumkin:
for (;;){
}
To'g'ri, bu holda biz cheksiz pastadir olamiz, chunki Biz tsikldan chiqish (tugatish) uchun shartni belgilamaymiz. Initsializatsiya ifodasi butun tsikl bajarilgunga qadar faqat bir marta bajariladi. Tsiklning o'z ko'lami borligini doimo yodda tutish kerak. Bu shuni anglatadiki,
ishga tushirish ,
tugatish ,
o'sish va tsikl tanasi bir xil o'zgaruvchilarni ko'radi. Qo'llanish doirasini jingalak qavslar yordamida aniqlash har doim oson. Qavslar ichidagi hamma narsa qavslar tashqarisida ko'rinmaydi, lekin qavslar ichidan tashqarida hamma narsa ko'rinadi.
Initializatsiya shunchaki ifodadir. Misol uchun, o'zgaruvchini ishga tushirish o'rniga, odatda hech narsa qaytarmaydigan usulni chaqirishingiz mumkin. Yoki birinchi nuqta-vergul oldidan bo'sh joy qoldirib, uni o'tkazib yuboring. Quyidagi ifoda
tugatish shartini bildiradi . Modomiki bu
true , tsikl bajariladi. Va agar
false bo'lsa , yangi iteratsiya boshlanmaydi. Agar siz quyidagi rasmga qarasangiz, kompilyatsiya paytida xatoga yo'l qo'yamiz va IDE shikoyat qiladi: tsikldagi bizning ifodamizga etib bo'lmaydi. Bizda tsiklda bitta iteratsiya bo'lmasligi sababli, biz darhol chiqamiz, chunki yolg'on:
Tugatish bayonotidagi iboraga e'tibor qaratish kerak : bu sizning ilovangizda cheksiz tsikllarga ega bo'lishini bevosita aniqlaydi.
O'sish - bu eng oddiy ifoda. U tsiklning har bir muvaffaqiyatli iteratsiyasidan keyin bajariladi. Va bu ifodani o'tkazib yuborish ham mumkin. Masalan:
int outerVar = 0;
for (;outerVar < 10;) {
outerVar += 2;
System.out.println("Value = " + outerVar);
}
Misoldan ko'rinib turibdiki, tsiklning har bir iteratsiyasi biz 2 ga oshib boramiz, lekin qiymat
outerVar
10 dan kichik bo'lgandagina. Bundan tashqari,
o'sish bayonotidagi ifoda aslida shunchaki ifoda bo'lgani uchun, u har qanday narsani o'z ichiga olishi mumkin. Shuning uchun, hech kim o'sish o'rniga kamayishdan foydalanishni taqiqlamaydi, ya'ni. qiymatini kamaytirish. Siz har doim o'sishning yozilishini kuzatishingiz kerak.
+=
avval ortishni, keyin esa topshiriqni bajaradi, lekin agar yuqoridagi misolda biz buning aksini yozsak, biz cheksiz tsiklga ega bo'lamiz, chunki o'zgaruvchi
outerVar
hech qachon o'zgartirilgan qiymatni olmaydi: bu holda u
=+
topshiriqdan keyin hisoblab chiqiladi. Aytgancha, ko'rish o'sishi bilan ham xuddi shunday
++
. Masalan, bizda pastadir bor edi:
String[] names = {"John","Sara","Jack"};
for (int i = 0; i < names.length; ++i) {
System.out.println(names[i]);
}
Tsikl ishladi va hech qanday muammo yo'q edi. Ammo keyin refaktorchi keldi. U o'sishni tushunmadi va shunchaki shunday qildi:
String[] names = {"John","Sara","Jack"};
for (int i = 0; i < names.length;) {
System.out.println(names[++i]);
}
Agar o'sish belgisi qiymat oldida paydo bo'lsa, demak, u avval ko'payadi va keyin ko'rsatilgan joyga qaytadi. Ushbu misolda biz darhol birinchisini o'tkazib yuborgan holda 1-indeksdagi elementni massivdan ajratib olishni boshlaymiz.
Va keyin 3-indeksda biz " java.lang.ArrayIndexOutOfBoundsException " xatosi bilan qulab tushamiz . Siz taxmin qilganingizdek, bu faqat oldin ishlagan, chunki o'sish iteratsiya tugagandan so'ng chaqiriladi. Ushbu ifodani iteratsiyaga o'tkazishda hamma narsa buzildi. Ma'lum bo'lishicha, hatto oddiy tsiklda ham tartibsizlik qilishingiz mumkin) Agar sizda massiv bo'lsa, ehtimol barcha elementlarni ko'rsatishning osonroq yo'li bormi?
Har bir tsikl uchun
Java 1.5 dan boshlab, Java ishlab chiquvchilari bizga Oracle saytida tasvirlangan "
Har bir Loop " yoki
1.5.0for each loop
versiyasi uchun qo'llanmada tasvirlangan dizaynni taqdim etdilar . Umuman olganda, u quyidagicha ko'rinadi:
Sehrli emasligiga ishonch hosil qilish uchun ushbu konstruksiya tavsifini Java tili spetsifikatsiyasida (JLS) o‘qishingiz mumkin.
Ushbu qurilish " 14.14.2. Bayonot uchun kengaytirilgan " bobida tasvirlangan . Ko'rib turganingizdek,
for har bir sikl massivlar va java.lang.Iterable interfeysini amalga oshiradiganlar bilan ishlatilishi mumkin . Ya'ni, agar chindan ham xohlasangiz,
java.lang.Iterable interfeysini amalga oshirishingiz mumkin va
har bir tsikl uchun sinfingiz bilan foydalanish mumkin. Siz darhol shunday deysiz: "Yaxshi, bu takrorlanadigan ob'ekt, lekin massiv ob'ekt emas. Bir xil." Va siz xato qilasiz, chunki ... Java-da massivlar dinamik ravishda yaratilgan ob'ektlardir. Til spetsifikatsiyasi bizga buni aytadi: "
Java dasturlash tilida massivlar ob'ektlardir ." Umuman olganda, massivlar biroz JVM sehridir, chunki... massivning ichki tuzilishi noma'lum va Java virtual mashinasi ichida bir joyda joylashgan. Har bir qiziquvchi stackoverflow bo'yicha javoblarni o'qishi mumkin: "
Javada massiv sinfi qanday ishlaydi? " Ma'lum bo'lishicha, agar biz massivdan foydalanmasak, unda
Iterable ni amalga oshiradigan narsadan foydalanishimiz kerak . Masalan:
List<String> names = Arrays.asList("John", "Sara", "Jack");
for (String name : names) {
System.out.println("Name = " + name);
}
Bu erda shuni esda tutishingiz mumkinki, agar biz to'plamlardan foydalansak (
java.util.Collection ), buning natijasida biz aynan
Iterable ga erishamiz . Agar ob'ektda Iterable-ni amalga oshiradigan sinf mavjud bo'lsa, u iterator usuli chaqirilganda ushbu ob'ektning mazmunini takrorlaydigan Iteratorni ta'minlashi shart. Yuqoridagi kod, masalan, bayt-kodga ega bo'ladi (IntelliJ Idea-da siz "Ko'rish" -> "Bayt kodini ko'rsatish" ni bajarishingiz mumkin:
Ko'rib turganingizdek, iterator aslida ishlatiladi.
Agar bu har bir tsikl uchun bo'lmasa , biz shunday yozishimiz kerak edi:
List<String> names = Arrays.asList("John", "Sara", "Jack");
for (Iterator i = names.iterator(); i.hasNext(); ) {
String name = (String) i.next();
System.out.println("Name = " + name);
}
Iterator
Yuqorida ko'rganimizdek,
Iterable interfeysi ba'zi bir ob'ekt misollari uchun siz tarkibni takrorlashingiz mumkin bo'lgan iteratorni olishingiz mumkinligini aytadi.
Shunga qaramay, buni SOLID dan Yagona javobgarlik printsipi deb aytish mumkin . Ma'lumotlar strukturasining o'zi o'tishni boshqarmasligi kerak, lekin u kerak bo'lgan narsani ta'minlay oladi.
Iteratorning asosiy amalga oshirilishi shundaki, u odatda tashqi sinf tarkibiga kirish huquqiga ega bo'lgan va tashqi sinf tarkibidagi kerakli elementni ta'minlaydigan ichki sinf sifatida e'lon qilinadi.
ArrayList
Bu erda iterator elementni qanday qaytarishi haqidagi sinfdan misol :
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
Ko'rib turganimizdek, iterator yordamida
ArrayList.this
tashqi sinfga va uning o'zgaruvchisiga kiradi
elementData
va u erdan elementni qaytaradi. Shunday qilib, iteratorni olish juda oddiy:
List<String> names = Arrays.asList("John", "Sara", "Jack");
Iterator<String> iterator = names.iterator();
Uning ishi shundan kelib chiqadiki, biz elementlarning mavjudligini tekshirishimiz mumkin (
hasNext usuli ), keyingi elementni olish (
keyingi usul ) va
keyingi orqali olingan oxirgi elementni olib tashlaydigan
olib tashlash usuli .
O'chirish usuli ixtiyoriy va amalga oshirilishi kafolatlanmaydi. Aslida, Java rivojlanishi bilan interfeyslar ham rivojlanadi. Shu sababli, Java 8 da iterator tashrif buyurmagan qolgan elementlarda ba'zi harakatlarni bajarishga imkon beruvchi usul ham mavjud edi . Iterator va to'plamlar haqida nima qiziq? Masalan, sinf mavjud . Bu va ning ota-onasi bo'lgan mavhum sinfdir .
Va modCount kabi soha tufayli biz uchun qiziq . Har bir o'zgarish ro'yxat mazmunini o'zgartiradi. Xo'sh, buning bizga nima ahamiyati bor? Va iterator ish paytida u takrorlangan to'plam o'zgarmasligiga ishonch hosil qiladi. Siz tushunganingizdek, ro'yxatlar uchun iteratorni amalga oshirish
modcount bilan bir joyda , ya'ni sinfda joylashgan . Keling, oddiy misolni ko'rib chiqaylik:
forEachRemaining
AbstractList
ArrayList
LinkedList
AbstractList
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Iterator<String> iterator = names.iterator();
names.add("modcount++");
System.out.println(iterator.next());
Mavzu bo'yicha bo'lmasa-da, birinchi qiziqarli narsa. Aslida
Arrays.asList
o'zining maxsus qismini qaytaradi
ArrayList
(
java.util.Arrays.ArrayList ). U qo'shish usullarini qo'llamaydi, shuning uchun uni o'zgartirib bo'lmaydi. Bu haqida JavaDoc da yozilgan:
fixed-size . Lekin, aslida, bu
belgilangan o'lchamdan ko'proq . U ham
o'zgarmas , ya'ni o'zgarmasdir; olib tashlash ham unda ishlamaydi. Biz ham xatoga duch kelamiz, chunki... Iteratorni yaratganimizdan so'ng, biz undagi
modcountni esladik . Keyin biz to'plamning holatini "tashqi" (ya'ni, iterator orqali emas) o'zgartirdik va iterator usulini bajardik. Shuning uchun biz xatoni olamiz:
java.util.ConcurrentModificationException . Bunga yo'l qo'ymaslik uchun iteratsiya paytidagi o'zgartirish to'plamga kirish orqali emas, balki iteratorning o'zi orqali amalga oshirilishi kerak:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Iterator<String> iterator = names.iterator();
iterator.next();
iterator.remove();
System.out.println(iterator.next());
Siz tushunganingizdek, agar
iterator.remove()
siz buni oldin qilmagan bo'lsangiz
iterator.next()
, demak, chunki. iterator hech qanday elementga ishora qilmaydi, keyin biz xatoga yo'l qo'yamiz. Misolda, iterator
Jon elementiga o'tadi , uni olib tashlaydi va keyin
Sara elementini oladi . Va bu erda hamma narsa yaxshi bo'lar edi, lekin omadsizlik, yana "nyuanslar" bor)
java.util.ConcurrentModificationException faqat true qiymatinihasNext()
qaytarganda paydo bo'ladi . Ya'ni, agar siz oxirgi elementni to'plamning o'zi orqali o'chirsangiz, iterator tushmaydi.
Batafsil ma’lumot uchun “ #ITsubbotnik JAVA: Java boshqotirmalari” bo‘limidagi Java boshqotirmalari haqidagi hisobotni tomosha qilgan ma’qul . Biz shunday batafsil suhbatni oddiy sababga ko'ra boshladikki, xuddi shu nuanslar qachon qo'llaniladi ... Bizning sevimli iteratorimiz qopqoq ostida ishlatiladi. Va bu nuancelarning barchasi u erda ham qo'llaniladi. Bitta narsa shundaki, biz iteratorga kira olmaymiz va elementni xavfsiz olib tashlay olmaymiz. Aytgancha, siz tushunganingizdek, davlat iterator yaratilgan paytda esga olinadi. Va xavfsiz o'chirish faqat chaqirilgan joyda ishlaydi. Ya'ni, bu variant ishlamaydi:
for each loop
Iterator<String> iterator1 = names.iterator();
Iterator<String> iterator2 = names.iterator();
iterator1.next();
iterator1.remove();
System.out.println(iterator2.next());
Chunki iterator2 uchun iterator1 orqali o'chirish "tashqi" edi, ya'ni u tashqarida bir joyda amalga oshirildi va u bu haqda hech narsa bilmaydi. Iteratorlar mavzusida men buni ham ta'kidlamoqchiman. Maxsus kengaytirilgan iterator interfeyslarni amalga oshirish uchun maxsus yaratilgan
List
. Va unga ism qo'yishdi
ListIterator
. Bu nafaqat oldinga, balki orqaga ham harakat qilish imkonini beradi, shuningdek, oldingi va keyingi elementning indeksini aniqlashga imkon beradi. Bunga qo'shimcha ravishda, u joriy elementni almashtirish yoki joriy iterator pozitsiyasi va keyingisi o'rtasidagi joyga yangisini qo'shish imkonini beradi. Siz taxmin qilganingizdek,
ListIterator
buni qilishga ruxsat berilgan, chunki
List
indeks bo'yicha kirish amalga oshirilgan.
Java 8 va iteratsiya
Java 8-ning chiqarilishi ko'pchilikning hayotini osonlashtirdi. Shuningdek, biz ob'ektlarning mazmuni bo'yicha iteratsiyani e'tiborsiz qoldirmadik. Bu qanday ishlashini tushunish uchun siz bu haqda bir necha so'z aytishingiz kerak. Java 8
java.util.function.Consumer sinfini taqdim etdi . Mana bir misol:
Consumer consumer = new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
};
Iste'molchi - bu funktsional interfeys, ya'ni interfeys ichida faqat 1 ta bajarilmagan mavhum usul mavjud bo'lib, u ushbu interfeysning vositalarini belgilaydigan sinflarda majburiy amalga oshirilishini talab qiladi. Bu lambda kabi sehrli narsadan foydalanishga imkon beradi. Ushbu maqola bu haqda emas, lekin biz uni nima uchun ishlatishimiz mumkinligini tushunishimiz kerak. Shunday qilib, lambdalardan foydalangan holda, yuqoridagi
Consumer quyidagicha qayta yozilishi mumkin:
Consumer consumer = (obj) -> System.out.println(obj);
Bu Java obj deb nomlangan narsa kirishga uzatilishini ko'radi va keyin -> dan keyingi ifoda ushbu obj uchun bajariladi. Iteratsiyaga kelsak, endi buni qilishimiz mumkin:
List<String> names = Arrays.asList("John", "Sara", "Jack");
Consumer consumer = (obj) -> System.out.println(obj);
names.forEach(consumer);
Agar siz usulga o'tsangiz
forEach
, hamma narsa aqldan ozganini ko'rasiz. Mana bizning eng sevimlimiz
for-each loop
:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
Iterator yordamida elementni chiroyli tarzda olib tashlash ham mumkin, masalan:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Predicate predicate = (obj) -> obj.equals("John");
names.removeIf(predicate);
Bunday holda,
removeIf usuli kirish sifatida Consumer ni emas , balki
Predikatni oladi . Bu
booleanni qaytaradi . Bunday holda, agar predikat "
to'g'ri " deb aytsa, u holda element o'chiriladi. Qizig'i shundaki, bu erda ham hamma narsa aniq emas)) Xo'sh, nima xohlaysiz? Konferentsiyada boshqotirmalarni yaratish uchun odamlarga joy berish kerak. Misol uchun, iterator ba'zi iteratsiyadan keyin erisha oladigan barcha narsalarni o'chirish uchun quyidagi kodni olaylik:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Iterator<String> iterator = names.iterator();
iterator.next();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(names);
OK, bu erda hamma narsa ishlaydi. Ammo biz Java 8-ni eslaymiz. Shuning uchun, kodni soddalashtirishga harakat qilaylik:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Iterator<String> iterator = names.iterator();
iterator.next();
iterator.forEachRemaining(obj -> iterator.remove());
System.out.println(names);
Haqiqatan ham u yanada chiroyli bo'lib qoldimi?
Biroq, java.lang.IllegalStateException bo'ladi . Buning sababi esa... Java’dagi xato. Ma'lum bo'lishicha, u tuzatilgan, ammo JDK 9 da. Mana OpenJDK-dagi vazifaga havola:
Iterator.forEachRemaining va boshqalar. Iterator.remove . Tabiiyki, bu allaqachon muhokama qilingan:
Nima uchun iterator.forEachRemaining Consumer lambda elementini olib tashlamaydi? Boshqa yo'l to'g'ridan-to'g'ri Stream API orqali:
List<String> names = new ArrayList(Arrays.asList("John", "Sara", "Jack"));
Stream<String> stream = names.stream();
stream.forEach(obj -> System.out.println(obj));
xulosalar
Yuqoridagi barcha materiallardan ko'rganimizdek, halqa
for-each loop
faqat iterator tepasida joylashgan "sintaktik shakar" dir. Biroq, u hozir ko'p joylarda qo'llaniladi. Bundan tashqari, har qanday mahsulotni ehtiyotkorlik bilan ishlatish kerak. Misol uchun, zararsiz kishi
forEachRemaining
yoqimsiz kutilmagan hodisalarni yashirishi mumkin. Va bu birlik sinovlari zarurligini yana bir bor isbotlaydi. Yaxshi test sizning kodingizda bunday foydalanish holatini aniqlashi mumkin. Mavzu bo'yicha nimani ko'rishingiz/o'qishingiz mumkin:
#Viacheslav
GO TO FULL VERSION