JavaRush /Java Blog /Random-TL /Para sa at Para sa-Bawat Loop: isang kuwento kung paano a...

Para sa at Para sa-Bawat Loop: isang kuwento kung paano ako umulit, naulit, ngunit hindi naulit

Nai-publish sa grupo

Panimula

Ang mga loop ay isa sa mga pangunahing istruktura ng mga programming language. Halimbawa, sa website ng Oracle mayroong isang seksyon na " Aralin: Mga Pangunahing Kaalaman sa Wika ", kung saan ang mga loop ay may hiwalay na aralin " Ang para sa Pahayag ". I-refresh natin ang mga pangunahing kaalaman: Binubuo ang loop ng tatlong expression (mga pahayag): initialization (initialization), kundisyon (termination) at increment (increment):
Para sa at Para sa-Bawat Loop: isang kuwento tungkol sa kung paano ako umulit, umulit, ngunit hindi umulit - 1
Kapansin-pansin, lahat sila ay opsyonal, ibig sabihin, maaari nating, kung gusto natin, sumulat:
for (;;){
}
Totoo, sa kasong ito makakakuha tayo ng walang katapusang loop, dahil Hindi namin tinukoy ang isang kundisyon para sa pag-alis sa loop (pagwawakas). Ang pagpasimula ng expression ay isasagawa lamang nang isang beses, bago ang buong loop ay naisakatuparan. Ito ay palaging nagkakahalaga ng pag-alala na ang isang cycle ay may sariling saklaw. Nangangahulugan ito na ang initialization , termination , increment at ang loop body ay nakikita ang parehong mga variable. Ang saklaw ay palaging madaling matukoy gamit ang mga kulot na brace. Lahat ng nasa loob ng bracket ay hindi nakikita sa labas ng bracket, ngunit lahat ng nasa labas ng bracket ay makikita sa loob ng bracket. Ang pagsisimula ay isang pagpapahayag lamang. Halimbawa, sa halip na simulan ang isang variable, maaari kang tumawag sa isang paraan na hindi magbabalik ng anuman. O laktawan lang ito, mag-iwan ng blangko na espasyo bago ang unang semicolon. Ang sumusunod na expression ay tumutukoy sa kondisyon ng pagwawakas . Hangga't ito ay totoo , ang loop ay naisakatuparan. At kung false , hindi magsisimula ang isang bagong pag-ulit. Kung titingnan mo ang larawan sa ibaba, nakakakuha kami ng isang error sa panahon ng compilation at ang IDE ay magrereklamo: ang aming expression sa loop ay hindi maabot. Dahil wala kaming isang pag-ulit sa loop, lalabas kami kaagad, dahil mali:
Para sa at Para sa-Bawat Loop: isang kuwento kung paano ako umulit, umulit, ngunit hindi umulit - 2
Ito ay nagkakahalaga ng pagsubaybay sa expression sa pahayag ng pagwawakas : direktang tinutukoy nito kung ang iyong aplikasyon ay magkakaroon ng walang katapusang mga loop. Ang increment ay ang pinakasimpleng expression. Ito ay isinasagawa pagkatapos ng bawat matagumpay na pag-ulit ng loop. At ang ekspresyong ito ay maaari ding laktawan. Halimbawa:
int outerVar = 0;
for (;outerVar < 10;) {
	outerVar += 2;
	System.out.println("Value = " + outerVar);
}
Tulad ng makikita mo mula sa halimbawa, ang bawat pag-ulit ng loop ay dagdagan natin ng 2, ngunit hangga't ang halaga outerVaray mas mababa sa 10. Bilang karagdagan, dahil ang expression sa increment na pahayag ay talagang isang expression lamang, ito maaaring maglaman ng kahit ano. Samakatuwid, walang sinuman ang nagbabawal sa paggamit ng decrement sa halip na isang increment, i.e. bawasan ang halaga. Dapat mong palaging subaybayan ang pagsulat ng pagtaas. +=nagsasagawa muna ng pagtaas at pagkatapos ay isang pagtatalaga, ngunit kung sa halimbawa sa itaas ay isusulat natin ang kabaligtaran, makakakuha tayo ng isang walang katapusang loop, dahil ang variable outerVaray hindi kailanman makakatanggap ng nabagong halaga: sa kasong ito ito =+ay kakalkulahin pagkatapos ng pagtatalaga. Siyanga pala, ito ay pareho sa mga pagtaas ng view ++. Halimbawa, nagkaroon kami ng loop:
String[] names = {"John","Sara","Jack"};
for (int i = 0; i < names.length; ++i) {
	System.out.println(names[i]);
}
Ang cycle ay gumana at walang mga problema. Ngunit pagkatapos ay dumating ang refactoring na tao. Hindi niya naintindihan ang pagtaas at ginawa lang ito:
String[] names = {"John","Sara","Jack"};
for (int i = 0; i < names.length;) {
	System.out.println(names[++i]);
}
Kung lalabas ang increment sign sa harap ng value, nangangahulugan ito na tataas muna ito at pagkatapos ay babalik sa lugar kung saan ito ipinahiwatig. Sa halimbawang ito, sisimulan agad naming i-extract ang elemento sa index 1 mula sa array, laktawan ang una. At pagkatapos ay sa index 3 kami ay mag-crash na may error na " java.lang.ArrayIndexOutOfBoundsException ". Tulad ng maaaring nahulaan mo, ito ay nagtrabaho bago dahil lamang ang pagtaas ay tinatawag pagkatapos makumpleto ang pag-ulit. Kapag inilipat ang expression na ito sa pag-ulit, nasira ang lahat. Tulad ng lumalabas, kahit na sa isang simpleng loop maaari kang gumawa ng gulo) Kung mayroon kang isang array, marahil mayroong ilang mas madaling paraan upang ipakita ang lahat ng mga elemento?
Para sa at Para sa-Bawat Loop: isang kuwento kung paano ako umulit, umulit, ngunit hindi umulit - 3

Para sa bawat loop

Simula sa Java 1.5, binigyan kami ng mga developer ng Java ng disenyo for each loopna inilarawan sa site ng Oracle sa Gabay na tinatawag na " The For-Each Loop " o para sa bersyon 1.5.0 . Sa pangkalahatan, magiging ganito ang hitsura:
Para sa at Para sa-Bawat Loop: isang kuwento kung paano ako umulit, umulit, ngunit hindi umulit - 4
Maaari mong basahin ang paglalarawan ng construct na ito sa Java Language Specification (JLS) upang matiyak na hindi ito magic. Ang pagbuo na ito ay inilarawan sa kabanata " 14.14.2. Ang pinahusay para sa pahayag ". Gaya ng nakikita mo, ang para sa bawat loop ay maaaring gamitin sa mga array at sa mga nagpapatupad ng java.lang.Iterable na interface . Iyon ay, kung talagang gusto mo, maaari mong ipatupad ang java.lang.Iterable na interface at para sa bawat loop ay maaaring gamitin sa iyong klase. Sasabihin mo kaagad, "Okay, ito ay isang iterable object, ngunit ang array ay hindi isang object. Uri ng." At magkakamali ka, dahil... Sa Java, ang mga array ay dynamic na nilikha na mga bagay. Sinasabi sa atin ng detalye ng wika ito: " Sa Java programming language, arrays are objects ." Sa pangkalahatan, ang mga array ay medyo JVM magic, dahil... kung paano ang array ay nakabalangkas sa loob ay hindi alam at matatagpuan sa isang lugar sa loob ng Java Virtual Machine. Maaaring basahin ng sinumang interesado ang mga sagot sa stackoverflow: " Paano gumagana ang array class sa Java? " Lumalabas na kung hindi tayo gumagamit ng array, dapat tayong gumamit ng isang bagay na nagpapatupad ng Iterable . Halimbawa:
List<String> names = Arrays.asList("John", "Sara", "Jack");
for (String name : names) {
	System.out.println("Name = " + name);
}
Dito mo lang maaalala na kung gagamit tayo ng mga koleksyon ( java.util.Collection ), salamat dito nakakakuha tayo ng eksaktong Iterable . Kung ang isang bagay ay may klase na nagpapatupad ng Iterable, obligado itong magbigay, kapag tinawag ang pamamaraan ng iterator, ng isang Iterator na mag-uulit sa mga nilalaman ng bagay na iyon. Ang code sa itaas, halimbawa, ay magkakaroon ng isang bytecode na tulad nito (sa IntelliJ Idea maaari mong gawin ang "View" -> "Show bytecode" :
Para sa at Para sa-Bawat Loop: isang kuwento tungkol sa kung paano ako umulit, umulit, ngunit hindi umulit - 5
Tulad ng nakikita mo, ang isang iterator ay aktwal na ginagamit. Kung hindi para sa para sa bawat loop , kailangan nating magsulat ng tulad ng:
List<String> names = Arrays.asList("John", "Sara", "Jack");
for (Iterator i = names.iterator(); /* continue if */ i.hasNext(); /* skip increment */) {
	String name = (String) i.next();
	System.out.println("Name = " + name);
}

Tagapag-ulit

Tulad ng nakita natin sa itaas, ang Iterable interface ay nagsasabi na para sa mga pagkakataon ng ilang bagay, maaari kang makakuha ng isang iterator kung saan maaari mong ulitin ang mga nilalaman. Muli, masasabing ito ang Single Responsibility Principle mula sa SOLID . Ang mismong istraktura ng data ay hindi dapat magmaneho ng traversal, ngunit maaari itong magbigay ng isa na dapat. Ang pangunahing pagpapatupad ng Iterator ay na ito ay karaniwang idineklara bilang isang panloob na klase na may access sa mga nilalaman ng panlabas na klase at nagbibigay ng nais na elemento na nilalaman sa panlabas na klase. Narito ang isang halimbawa mula sa klase ArrayListkung paano ibinabalik ng isang iterator ang isang elemento:
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];
}
Tulad ng nakikita natin, sa tulong ng ArrayList.thisisang iterator ay ina-access ang panlabas na klase at ang variable nito elementData, at pagkatapos ay nagbabalik ng isang elemento mula doon. Kaya, ang pagkuha ng isang iterator ay napaka-simple:
List<String> names = Arrays.asList("John", "Sara", "Jack");
Iterator<String> iterator = names.iterator();
Ang gawain nito ay bumaba sa katotohanan na maaari nating suriin kung may mga elemento pa (ang hasNext method ), kunin ang susunod na elemento (ang susunod na paraan ) at ang paraan ng pag-alis , na nag-aalis ng huling elemento na natanggap hanggang sa susunod . Ang paraan ng pag-alis ay opsyonal at hindi garantisadong maipapatupad. Sa katunayan, habang nagbabago ang Java, nagbabago rin ang mga interface. Samakatuwid, sa Java 8, lumitaw din ang isang pamamaraan forEachRemainingna nagpapahintulot sa iyo na magsagawa ng ilang aksyon sa mga natitirang elemento na hindi binisita ng iterator. Ano ang kawili-wili tungkol sa isang iterator at mga koleksyon? Halimbawa, mayroong isang klase AbstractList. Ito ay isang abstract na klase na ang magulang ng ArrayListat LinkedList. At ito ay kawili-wili sa amin dahil sa isang larangan tulad ng modCount . Ang bawat pagbabago ay nagbabago ang mga nilalaman ng listahan. Kaya ano ang mahalaga sa amin? At ang katotohanan na tinitiyak ng iterator na sa panahon ng operasyon ang koleksyon kung saan ito inuulit ay hindi nagbabago. Tulad ng naiintindihan mo, ang pagpapatupad ng iterator para sa mga listahan ay matatagpuan sa parehong lugar bilang modcount , iyon ay, sa klase AbstractList. Tingnan natin ang isang simpleng halimbawa:
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());
Narito ang unang kawili-wiling bagay, bagaman hindi sa paksa. Talagang Arrays.asListnagbabalik ng sarili nitong espesyal ArrayList( java.util.Arrays.ArrayList ). Hindi ito nagpapatupad ng pagdaragdag ng mga pamamaraan, kaya hindi ito nababago. Ito ay nakasulat tungkol sa JavaDoc: fixed-size . Ngunit sa katunayan, ito ay higit pa sa fixed-size . Ito rin ay hindi nababago , iyon ay, hindi nababago; ang tanggalin ay hindi rin gagana dito. Makakakuha din tayo ng error, dahil... Ang pagkakaroon ng paglikha ng iterator, naalala namin ang modcount sa loob nito . Pagkatapos ay binago namin ang estado ng koleksyon na "panlabas" (ibig sabihin, hindi sa pamamagitan ng iterator) at isinagawa ang pamamaraan ng iterator. Samakatuwid, nakukuha namin ang error: java.util.ConcurrentModificationException . Upang maiwasan ito, ang pagbabago sa panahon ng pag-ulit ay dapat gawin sa pamamagitan ng iterator mismo, at hindi sa pamamagitan ng pag-access sa koleksyon:
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());
Tulad ng naiintindihan mo, kung iterator.remove()hindi mo ito gagawin noon iterator.next(), dahil. ang iterator ay hindi tumuturo sa anumang elemento, pagkatapos ay makakakuha tayo ng isang error. Sa halimbawa, ang iterator ay pupunta sa John element , aalisin ito, at pagkatapos ay kunin ang Sara element . At dito magiging maayos ang lahat, ngunit malas, muli may mga "nuances") java.util.ConcurrentModificationException ay magaganap lamang kapag hasNext()ito ay nagbalik ng true . Iyon ay, kung tatanggalin mo ang huling elemento sa pamamagitan ng koleksyon mismo, ang iterator ay hindi mahuhulog. Para sa higit pang mga detalye, mas magandang panoorin ang ulat tungkol sa Java puzzle mula sa “ #ITsubbotnik Section JAVA: Java puzzles ”. Sinimulan namin ang isang detalyadong pag-uusap para sa simpleng dahilan na ang eksaktong parehong mga nuances ay nalalapat kapag for each loop... Ang aming paboritong iterator ay ginagamit sa ilalim ng hood. At lahat ng mga nuances na ito ay nalalapat din doon. Ang tanging bagay ay, hindi kami magkakaroon ng access sa iterator, at hindi namin ligtas na maalis ang elemento. Sa pamamagitan ng paraan, tulad ng naiintindihan mo, ang estado ay naaalala sa sandaling ang iterator ay nilikha. At gumagana lang ang secure na pagtanggal kung saan ito tinatawag. Iyon ay, ang pagpipiliang ito ay hindi gagana:
Iterator<String> iterator1 = names.iterator();
Iterator<String> iterator2 = names.iterator();
iterator1.next();
iterator1.remove();
System.out.println(iterator2.next());
Dahil para sa iterator2 ang pagtanggal sa pamamagitan ng iterator1 ay "panlabas", ibig sabihin, ito ay ginanap sa isang lugar sa labas at wala siyang alam tungkol dito. Sa paksa ng mga iterator, nais ko ring tandaan ito. Ang isang espesyal, pinahabang iterator ay partikular na ginawa para sa mga pagpapatupad ng interface List. At pinangalanan nila siya ListIterator. Pinapayagan ka nitong ilipat hindi lamang pasulong, kundi pati na rin pabalik, at pinapayagan ka ring malaman ang index ng nakaraang elemento at ang susunod. Bilang karagdagan, pinapayagan ka nitong palitan ang kasalukuyang elemento o magpasok ng bago sa isang posisyon sa pagitan ng kasalukuyang posisyon ng iterator at ng susunod. Tulad ng iyong nahulaan, ListIteratorpinapayagan itong gawin dahil Listipinatupad ang pag-access sa pamamagitan ng index.
Para sa at Para sa-Bawat Loop: isang kuwento kung paano ako umulit, umulit, ngunit hindi umulit - 6

Java 8 at Pag-ulit

Ang paglabas ng Java 8 ay nagpadali sa buhay para sa marami. Hindi rin namin pinansin ang pag-ulit sa mga nilalaman ng mga bagay. Upang maunawaan kung paano ito gumagana, kailangan mong magsabi ng ilang salita tungkol dito. Ipinakilala ng Java 8 ang java.util.function.Consumer class . Narito ang isang halimbawa:
Consumer consumer = new Consumer() {
	@Override
	public void accept(Object o) {
		System.out.println(o);
	}
};
Ang consumer ay isang functional na interface, na nangangahulugan na sa loob ng interface ay mayroon lamang 1 hindi naipapatupad na abstract na pamamaraan na nangangailangan ng mandatoryong pagpapatupad sa mga klaseng iyon na tumutukoy sa mga implement ng interface na ito. Pinapayagan ka nitong gumamit ng isang mahiwagang bagay tulad ng lambda. Ang artikulong ito ay hindi tungkol diyan, ngunit kailangan nating maunawaan kung bakit natin ito magagamit. Kaya, gamit ang mga lambdas, ang Consumer sa itaas ay maaaring muling isulat nang ganito: Consumer consumer = (obj) -> System.out.println(obj); Nangangahulugan ito na nakikita ng Java na may ipapasa sa input na tinatawag na obj, at pagkatapos ay isasagawa ang expression pagkatapos ng -> para sa obj na ito. Tulad ng para sa pag-ulit, maaari na nating gawin ito:
List<String> names = Arrays.asList("John", "Sara", "Jack");
Consumer consumer = (obj) -> System.out.println(obj);
names.forEach(consumer);
Kung pupunta ka sa pamamaraan forEach, makikita mo na ang lahat ay napakasimple. Nandiyan ang paborito namin for-each loop:
default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
}
Posible ring magandang alisin ang isang elemento gamit ang isang iterator, halimbawa:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Predicate predicate = (obj) -> obj.equals("John");
names.removeIf(predicate);
Sa kasong ito, ang paraan ng removeIf ay tumatagal bilang input hindi isang Consumer , ngunit isang Predicate . Nagbabalik ito ng boolean . Sa kasong ito, kung ang panaguri ay nagsasabing " totoo ", aalisin ang elemento. Ito ay kagiliw-giliw na hindi lahat ay halata dito)) Well, ano ang gusto mo? Ang mga tao ay kailangang bigyan ng espasyo upang lumikha ng mga palaisipan sa kumperensya. Halimbawa, kunin natin ang sumusunod na code para sa pagtanggal ng lahat ng maaaring maabot ng iterator pagkatapos ng ilang pag-ulit:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Iterator<String> iterator = names.iterator();
iterator.next(); // Курсор на John
while (iterator.hasNext()) {
    iterator.next(); // Следующий элемент
    iterator.remove(); // Удалor его
}
System.out.println(names);
Okay, gumagana ang lahat dito. Ngunit naaalala namin ang Java 8 pagkatapos ng lahat. Samakatuwid, subukan nating gawing simple ang code:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Iterator<String> iterator = names.iterator();
iterator.next(); // Курсор на John
iterator.forEachRemaining(obj -> iterator.remove());
System.out.println(names);
Naging mas maganda ba talaga? Gayunpaman, magkakaroon ng java.lang.IllegalStateException . At ang dahilan ay... isang bug sa Java. Ito ay lumiliko na ito ay naayos, ngunit sa JDK 9. Narito ang isang link sa gawain sa OpenJDK: Iterator.forEachRemaining vs. Iterator.alisin . Natural, ito ay napag-usapan na: Bakit hindi inaalis ng iterator.forEachRemaining ang elemento sa Consumer lambda? Well, ang isa pang paraan ay direkta sa pamamagitan ng Stream API:
List<String> names = new ArrayList(Arrays.asList("John", "Sara", "Jack"));
Stream<String> stream = names.stream();
stream.forEach(obj -> System.out.println(obj));

mga konklusyon

Tulad ng nakita natin mula sa lahat ng materyal sa itaas, ang isang loop for-each loopay "syntactic sugar" lamang sa ibabaw ng isang iterator. Gayunpaman, ginagamit na ito ngayon sa maraming lugar. Bilang karagdagan, ang anumang produkto ay dapat gamitin nang may pag-iingat. Halimbawa, ang isang hindi nakakapinsala forEachRemainingay maaaring magtago ng mga hindi kasiya-siyang sorpresa. At muli itong nagpapatunay na kailangan ang mga unit test. Maaaring matukoy ng isang mahusay na pagsubok ang gayong kaso ng paggamit sa iyong code. Ano ang maaari mong panoorin/basahin sa paksa: #Viacheslav
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION