การแนะนำ
ลูปเป็นหนึ่งในโครงสร้างพื้นฐานของภาษาการเขียนโปรแกรม ตัวอย่างเช่น บนเว็บไซต์ Oracle มีส่วน "
บทเรียน: พื้นฐานภาษา " ซึ่งในลูปจะมีบทเรียนแยกต่างหาก "
The for Statement " มารีเฟรชพื้นฐานกันดีกว่า: การวนซ้ำประกอบด้วยสามนิพจน์ (คำสั่ง):
การเริ่มต้น (การเริ่มต้น)
เงื่อนไข (การสิ้นสุด) และ
การเพิ่มขึ้น (การเพิ่มขึ้น):
สิ่งที่น่าสนใจคือทั้งหมดนี้เป็นทางเลือก ซึ่งหมายความว่าเราสามารถเขียนได้หากต้องการว่า:
for (;;){
}
จริงอยู่ ในกรณีนี้ เราจะได้วนซ้ำไม่สิ้นสุดเพราะว่า เราไม่ได้ระบุเงื่อนไขในการออกจากลูป (การสิ้นสุด) นิพจน์การเริ่มต้นจะดำเนินการเพียงครั้งเดียว ก่อนที่จะดำเนินการวนซ้ำทั้งหมด ควรจำไว้เสมอว่าวงจรมีขอบเขตของมันเอง ซึ่งหมายความว่า
การกำหนดค่าเริ่มต้น การยกเลิกการ
เพิ่มขึ้นและเนื้อหาลูปจะเห็นตัวแปรเดียวกัน กำหนดขอบเขตได้ง่ายเสมอโดยใช้เครื่องหมายปีกกา ทุกสิ่งที่อยู่ในวงเล็บจะไม่สามารถมองเห็นได้ภายนอกวงเล็บ แต่ทุกสิ่งที่อยู่นอกวงเล็บจะมองเห็นได้ภายในวงเล็บ
การเริ่มต้นเป็นเพียงการแสดงออก ตัวอย่างเช่น แทนที่จะเริ่มต้นตัวแปร คุณสามารถเรียกใช้เมธอดที่จะไม่ส่งคืนสิ่งใดๆ ได้ หรือข้ามไป โดยเว้นช่องว่างก่อนเครื่องหมายอัฒภาคแรก นิพจน์ต่อไปนี้ระบุเงื่อนไข
การสิ้นสุด ตราบใดที่เป็น
จริงการวนซ้ำก็จะถูกดำเนินการ และหากเป็น
falseการวนซ้ำใหม่จะไม่เริ่มต้นขึ้น หากคุณดูภาพด้านล่าง เราได้รับข้อผิดพลาดระหว่างการคอมไพล์ และ IDE จะบ่น: ไม่สามารถเข้าถึงนิพจน์ของเราในลูปได้ เนื่องจากเราจะไม่มีการวนซ้ำเพียงครั้งเดียวในลูป เราจะออกทันทีเพราะว่า เท็จ:
ควรจับตาดู นิพจน์ใน
คำสั่งยุติเนื่องจากจะกำหนดโดยตรงว่าแอปพลิเคชันของคุณจะมีการวนซ้ำไม่สิ้นสุดหรือไม่
การเพิ่มขึ้นเป็นนิพจน์ที่ง่ายที่สุด มันถูกดำเนินการหลังจากการวนซ้ำสำเร็จแต่ละครั้ง และสำนวนนี้ก็สามารถข้ามได้ ตัวอย่างเช่น:
int outerVar = 0;
for (;outerVar < 10;) {
outerVar += 2;
System.out.println("Value = " + outerVar);
}
ดังที่คุณเห็นจากตัวอย่าง การวนซ้ำแต่ละครั้งเราจะเพิ่มทีละ 2 แต่ตราบเท่าที่ค่า
outerVar
น้อยกว่า 10 เท่านั้น นอกจากนี้ เนื่องจากนิพจน์ใน
คำสั่งส่วนเพิ่มนั้นแท้จริงแล้วเป็นเพียงนิพจน์ ดังนั้น สามารถมีอะไรก็ได้ ดังนั้นจึงไม่มีใครห้ามการใช้การลดลงแทนการเพิ่มขึ้น กล่าวคือ ลดมูลค่า คุณควรติดตามการเขียนส่วนเพิ่มเสมอ
+=
ดำเนินการเพิ่มก่อนแล้วจึงมอบหมายงาน แต่ถ้าในตัวอย่างด้านบนเราเขียนตรงกันข้าม เราจะได้วงวนไม่สิ้นสุด เนื่องจากตัวแปร
outerVar
จะไม่มีวันได้รับค่าที่เปลี่ยนแปลง: ในกรณีนี้ ตัวแปร
=+
จะถูกคำนวณหลังจากการมอบหมาย อย่างไรก็ตาม การเพิ่มจำนวนการดูก็เหมือน
++
กัน ตัวอย่างเช่น เรามีการวนซ้ำ:
String[] names = {"John","Sara","Jack"};
for (int i = 0; i < names.length; ++i) {
System.out.println(names[i]);
}
วงจรใช้งานได้และไม่มีปัญหา แต่แล้วคนปรับโครงสร้างก็มา เขาไม่เข้าใจการเพิ่มขึ้นและทำสิ่งนี้:
String[] names = {"John","Sara","Jack"};
for (int i = 0; i < names.length;) {
System.out.println(names[++i]);
}
หากเครื่องหมายเพิ่มขึ้นปรากฏด้านหน้าค่า หมายความว่าจะเพิ่มขึ้นก่อนแล้วจึงกลับไปยังตำแหน่งที่ระบุไว้ ในตัวอย่างนี้ เราจะเริ่มแยกองค์ประกอบที่ดัชนี 1 ออกจากอาร์เรย์ทันที โดยข้ามองค์ประกอบแรก จากนั้นที่ดัชนี 3 เราจะเกิดข้อผิดพลาด "
java.lang.ArrayIndexOutOfBoundsException " ดังที่คุณอาจเดาได้ สิ่งนี้ได้ผลก่อนหน้านี้เพียงเพราะการเพิ่มขึ้นถูกเรียกหลังจากการวนซ้ำเสร็จสิ้น เมื่อถ่ายโอนนิพจน์นี้ไปสู่การวนซ้ำ ทุกอย่างพัง ปรากฎว่าแม้แต่ในวงธรรมดา ๆ คุณก็สามารถสร้างความยุ่งเหยิงได้) หากคุณมีอาร์เรย์ บางทีอาจมีวิธีที่ง่ายกว่าในการแสดงองค์ประกอบทั้งหมดหรือไม่
สำหรับแต่ละวง
เริ่มต้นด้วย Java 1.5 นักพัฒนา Java ได้ให้การออกแบบ
for each loop
ที่อธิบายไว้ในไซต์ Oracle ในคำแนะนำชื่อ "
The For-Each Loop " หรือสำหรับเวอร์ชัน
1.5.0แก่ เรา โดยทั่วไปจะมีลักษณะดังนี้:
คุณสามารถอ่านคำอธิบายของโครงสร้างนี้ได้ใน Java Language Specification (JLS) เพื่อให้แน่ใจว่าไม่ใช่สิ่งมหัศจรรย์ โครงสร้างนี้อธิบายไว้ในบท "
14.14.2. การปรับปรุงสำหรับคำสั่ง " อย่างที่คุณเห็น
for แต่ละวงสามารถใช้กับอาร์เรย์และอาร์เรย์ที่ใช้
java.lang.Iterable interface นั่นคือถ้าคุณต้องการจริงๆ คุณสามารถใช้ อินเทอร์เฟซ
java.lang.Iterableและ
สำหรับแต่ละลูปสามารถใช้กับคลาสของคุณได้ คุณจะพูดทันทีว่า “โอเค มันเป็นวัตถุที่สามารถทำซ้ำได้ แต่อาร์เรย์ไม่ใช่วัตถุ เรียงลำดับของ” แล้วคุณจะคิดผิดเพราะ... ใน Java อาร์เรย์เป็นวัตถุที่สร้างขึ้นแบบไดนามิก ข้อกำหนดภาษาบอกเราสิ่งนี้: “
ในภาษาการเขียนโปรแกรม Java อาร์เรย์คือวัตถุ ” โดยทั่วไปแล้ว อาร์เรย์เป็นเหมือนเวทมนตร์ JVM เล็กน้อย เพราะ... ไม่ทราบโครงสร้างอาร์เรย์ภายในอย่างไรและอยู่ที่ไหนสักแห่งภายใน Java Virtual Machine ใครที่สนใจสามารถอ่านคำตอบเกี่ยวกับ stackoverflow: "
คลาสอาร์เรย์ทำงานอย่างไรใน Java " ปรากฎว่าถ้า เราไม่ได้ใช้อาร์เรย์ เราก็ต้องใช้บางอย่างที่ Implement
Iterable ตัวอย่างเช่น:
List<String> names = Arrays.asList("John", "Sara", "Jack");
for (String name : names) {
System.out.println("Name = " + name);
}
ที่นี่คุณคงจำได้ว่าถ้าเราใช้คอลเลกชัน (
java.util.Collection ) ด้วยเหตุนี้เราจึงได้รับ
Iterable อย่าง แน่นอน หากวัตถุมีคลาสที่ใช้ Iterable ก็จำเป็นต้องจัดเตรียม Iterator ที่จะวนซ้ำเนื้อหาของวัตถุนั้นเมื่อมีการเรียกใช้เมธอดตัววนซ้ำ ตัวอย่างเช่นโค้ดด้านบนจะมีโค้ดไบต์ประมาณนี้ (ใน IntelliJ Idea คุณสามารถ "View" -> "Show bytecode" :
อย่างที่คุณเห็น มีการใช้ตัววนซ้ำจริงๆ หากไม่ใช่
สำหรับ for แต่ละ loopเราจะต้องเขียนดังนี้:
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);
}
ตัววนซ้ำ
ดังที่เราเห็นข้างต้น อินเทอร์เฟซ
Iterableบอกว่าสำหรับอินสแตนซ์ของวัตถุบางอย่าง คุณสามารถรับตัววนซ้ำซึ่งคุณสามารถวนซ้ำเนื้อหาได้ อาจกล่าวได้ ว่าเป็น Single Responsibility Principle จาก
SOLID อีกครั้ง โครงสร้างข้อมูลไม่ควรขับเคลื่อนการข้ามผ่าน แต่สามารถจัดเตรียมสิ่งที่ควรได้ การใช้งานพื้นฐาน
ของ Iteratorคือโดยปกติแล้วจะประกาศให้เป็นคลาสภายในที่สามารถเข้าถึงเนื้อหาของคลาสภายนอกและจัดเตรียมองค์ประกอบที่ต้องการที่มีอยู่ในคลาสภายนอก นี่คือตัวอย่างจากคลาส
ArrayList
ของวิธีที่ตัววนซ้ำส่งคืนองค์ประกอบ:
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];
}
ดังที่เราเห็น ด้วยความช่วยเหลือของ
ArrayList.this
ตัววนซ้ำจะเข้าถึงคลาสภายนอกและตัวแปรของมัน จาก
elementData
นั้นจึงส่งคืนองค์ประกอบจากที่นั่น ดังนั้นการรับตัววนซ้ำจึงง่ายมาก:
List<String> names = Arrays.asList("John", "Sara", "Jack");
Iterator<String> iterator = names.iterator();
งานของมันขึ้นอยู่กับข้อเท็จจริงที่ว่าเราสามารถตรวจสอบได้ว่ามีองค์ประกอบเพิ่มเติมหรือไม่ ( วิธี
hasNext ) รับองค์ประกอบถัดไป ( วิธี
ถัดไป ) และ วิธี
ลบซึ่งจะลบองค์ประกอบสุดท้ายที่ได้รับผ่าน
next วิธี
การลบเป็นทางเลือกและไม่รับประกันว่าจะนำไปใช้ได้ ในความเป็นจริง เมื่อ Java พัฒนาขึ้น อินเทอร์เฟซก็วิวัฒนาการไปด้วย ดังนั้นใน Java 8 จึงมีวิธีการ
forEachRemaining
ที่ช่วยให้คุณสามารถดำเนินการบางอย่างกับองค์ประกอบที่เหลือที่ตัววนซ้ำไม่ได้เยี่ยมชม สิ่งที่น่าสนใจเกี่ยวกับตัววนซ้ำและคอลเลกชัน? ตัวอย่างเช่นมีชั้น
AbstractList
เรียน นี่คือคลาสนามธรรมที่เป็นพาเรนต์ของ
ArrayList
และ และเป็นเรื่องที่น่าสนใจสำหรับ เรา
LinkedList
เนื่องจากมีฟิลด์เช่น
modCount การเปลี่ยนแปลงแต่ละครั้งเนื้อหาของการเปลี่ยนแปลงรายการ แล้วมันสำคัญอะไรกับเราล่ะ? และความจริงที่ว่าตัววนซ้ำทำให้แน่ใจว่าในระหว่างการดำเนินการคอลเลกชันที่มีการวนซ้ำจะไม่เปลี่ยนแปลง ตามที่คุณเข้าใจ การใช้งานตัววนซ้ำสำหรับรายการจะอยู่ในตำแหน่งเดียวกับmodcount
นั่นคือในคลาส
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());
นี่คือสิ่งแรกที่น่าสนใจแม้ว่าจะไม่อยู่ในหัวข้อก็ตาม จริงๆ แล้ว
Arrays.asList
ส่งคืนอันพิเศษของตัวเอง
ArrayList
(
java.util.Arrays.ArrayList ) มันไม่ได้ใช้วิธีการเพิ่ม ดังนั้นจึงไม่สามารถแก้ไขได้ มีเขียนเกี่ยวกับใน JavaDoc:
ขนาดคงที่ แต่จริงๆ แล้ว มันเป็นมากกว่า
ขนาดคงที่ มัน ไม่
เปลี่ยนรูปเช่น กัน นั่นคือไม่เปลี่ยนแปลง ลบจะไม่ทำงานเช่นกัน เราก็จะได้รับข้อผิดพลาดเช่นกันเพราะ...
เมื่อสร้างตัววนซ้ำแล้ว เราก็จำ modcountไว้ในนั้นได้ จากนั้นเราเปลี่ยนสถานะของคอลเลกชัน “ภายนอก” (เช่น ไม่ใช่ผ่านตัววนซ้ำ) และดำเนินการเมธอดตัววนซ้ำ ดังนั้นเราจึงได้รับข้อผิดพลาด:
java.util.ConcurrentModificationException เพื่อหลีกเลี่ยงปัญหานี้ การเปลี่ยนแปลงระหว่างการวนซ้ำจะต้องดำเนินการผ่านตัววนซ้ำเอง ไม่ใช่ผ่านการเข้าถึงคอลเลกชัน:
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());
อย่างที่คุณเข้าใจถ้า
iterator.remove()
คุณไม่ทำมา ก่อน
iterator.next()
ก็เพราะว่า ตัววนซ้ำไม่ได้ชี้ไปที่องค์ประกอบใด ๆ จากนั้นเราจะได้รับข้อผิดพลาด ในตัวอย่าง ตัววนซ้ำจะไปที่ องค์ประกอบ
Johnลบออก และรับองค์ประกอบ
Sara และที่นี่ทุกอย่างจะเรียบร้อยดี แต่โชคร้ายอีกครั้งที่มี "ความแตกต่าง")
java.util.ConcurrentModificationExceptionจะเกิดขึ้นก็ต่อเมื่อ
hasNext()
มันส่งกลับ
trueเท่านั้น นั่นคือ หากคุณลบองค์ประกอบสุดท้ายผ่านคอลเลกชัน ตัววนซ้ำจะไม่ตก สำหรับรายละเอียดเพิ่มเติม ควรดูรายงานเกี่ยวกับปริศนา Java จาก “
#ITsubbotnik Section JAVA: Java Puzzles ” เราเริ่มการสนทนาโดยละเอียดด้วยเหตุผลง่ายๆ ที่ใช้ความแตกต่างเดียวกันทุกประการเมื่อ
for each loop
... ตัววนซ้ำที่เราชื่นชอบนั้นถูกใช้ภายใต้ประทุน และความแตกต่างทั้งหมดนี้ก็มีผลเช่นกัน สิ่งเดียวคือ เราจะไม่สามารถเข้าถึงตัววนซ้ำ และเราไม่สามารถลบองค์ประกอบได้อย่างปลอดภัย ตามที่คุณเข้าใจแล้ว สถานะจะถูกจดจำในขณะที่สร้างตัววนซ้ำ และการลบอย่างปลอดภัยจะทำงานเฉพาะเมื่อมีการเรียกเท่านั้น นั่นคือตัวเลือกนี้จะไม่ทำงาน:
Iterator<String> iterator1 = names.iterator();
Iterator<String> iterator2 = names.iterator();
iterator1.next();
iterator1.remove();
System.out.println(iterator2.next());
เนื่องจากสำหรับตัววนซ้ำ 2 การลบผ่านตัววนซ้ำ 1 นั้นเป็น "ภายนอก" นั่นคือมีการดำเนินการที่ไหนสักแห่งภายนอกและเขาไม่รู้อะไรเลย ในหัวข้อตัววนซ้ำ ฉันอยากจะทราบสิ่งนี้ด้วย
List
ตัววนซ้ำ แบบ ขยายพิเศษถูกสร้างขึ้นมา สำหรับการใช้งานอินเทอร์เฟซโดยเฉพาะ และพวกเขาก็ตั้งชื่อเขา
ListIterator
ว่า ช่วยให้คุณเคลื่อนที่ไม่เพียง แต่ไปข้างหน้า แต่ยังย้อนกลับและยังช่วยให้คุณค้นหาดัชนีขององค์ประกอบก่อนหน้าและองค์ประกอบถัดไป นอกจากนี้ยังช่วยให้คุณสามารถแทนที่องค์ประกอบปัจจุบันหรือแทรกองค์ประกอบใหม่ที่ตำแหน่งระหว่างตำแหน่งตัววนซ้ำปัจจุบันและตำแหน่งถัดไป ตามที่คุณเดา
ListIterator
อนุญาตให้ทำเช่นนี้ได้เนื่องจาก
List
มีการใช้งานการเข้าถึงโดยดัชนี
Java 8 และการวนซ้ำ
การเปิดตัว Java 8 ทำให้ชีวิตของหลายๆ คนง่ายขึ้น นอกจากนี้เรายังไม่ได้ละเลยการวนซ้ำเนื้อหาของวัตถุ เพื่อให้เข้าใจถึงวิธีการทำงาน คุณต้องพูดสักสองสามคำเกี่ยวกับเรื่องนี้ Java 8 เปิดตัวคลาส
java.util.function.Consumer นี่คือตัวอย่าง:
Consumer consumer = new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
};
Consumerเป็นอินเทอร์เฟซที่ใช้งานได้ซึ่งหมายความว่าภายในอินเทอร์เฟซมีเพียง 1 วิธีนามธรรมที่ไม่ได้ใช้งานเท่านั้นที่ต้องมีการใช้งานบังคับในคลาสที่ระบุการใช้งานของอินเทอร์เฟซนี้ สิ่งนี้ทำให้คุณสามารถใช้สิ่งมหัศจรรย์เช่นแลมบ์ดาได้ บทความนี้ไม่เกี่ยวกับเรื่องนั้น แต่เราต้องเข้าใจว่าเหตุใดเราจึงนำไปใช้ได้ ดังนั้น เมื่อใช้ lambdas
Consumer ข้างต้น สามารถเขียนใหม่ได้ดังนี้:
Consumer consumer = (obj) -> System.out.println(obj);
ซึ่งหมายความว่า Java เห็นว่าสิ่งที่เรียกว่า obj จะถูกส่งผ่านไปยังอินพุต จากนั้นนิพจน์หลังจาก -> จะถูกดำเนินการสำหรับ obj นี้ สำหรับการวนซ้ำ ตอนนี้เราสามารถทำได้ดังนี้:
List<String> names = Arrays.asList("John", "Sara", "Jack");
Consumer consumer = (obj) -> System.out.println(obj);
names.forEach(consumer);
หากคุณไปที่วิธีการ
forEach
คุณจะเห็นว่าทุกอย่างเรียบง่ายมาก มีอันโปรดของเรา
for-each loop
:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
นอกจากนี้ยังสามารถลบองค์ประกอบได้อย่างสวยงามโดยใช้ตัววนซ้ำ เช่น:
List<String> names = Arrays.asList("John", "Sara", "Jack");
names = new ArrayList(names);
Predicate predicate = (obj) -> obj.equals("John");
names.removeIf(predicate);
ในกรณีนี้ เมธอด
RemoveIfจะใช้เป็นอินพุต ไม่ใช่
Consumerแต่
เป็นเพรดิเคต มันคืน
ค่าบูลีน ในกรณีนี้ หากภาคแสดงระบุว่า "
จริง " องค์ประกอบนั้นจะถูกลบออก เป็นเรื่องน่าสนใจที่ทุกอย่างไม่ชัดเจนที่นี่เช่นกัน)) คุณต้องการอะไร? ประชาชนจำเป็นต้องได้รับพื้นที่เพื่อสร้างปริศนาในการประชุม ตัวอย่างเช่น ลองใช้โค้ดต่อไปนี้เพื่อลบทุกสิ่งที่ตัววนซ้ำสามารถเข้าถึงได้หลังจากการวนซ้ำ:
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);
โอเค ทุกอย่างใช้งานได้ที่นี่ แต่เราจำได้ว่า Java 8 นั้น ดังนั้นเรามาลองทำให้โค้ดง่ายขึ้น:
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);
สวยขึ้นจริงมั้ย? อย่างไรก็ตาม จะมี
java.lang.IllegalStateException และเหตุผลก็คือ... มีข้อบกพร่องใน Java ปรากฎว่ามันได้รับการแก้ไขแล้ว แต่ใน JDK 9 นี่คือลิงค์ไปยังงานใน OpenJDK:
Iterator.forEachRemaining vs. Iterator.remove _ แน่นอนว่าสิ่งนี้ได้ถูกพูดคุยกันแล้ว:
เหตุใด iterator.forEachRemaining จึงไม่ลบองค์ประกอบใน Consumer lambda อีกวิธีหนึ่งคือผ่าน Stream API โดยตรง:
List<String> names = new ArrayList(Arrays.asList("John", "Sara", "Jack"));
Stream<String> stream = names.stream();
stream.forEach(obj -> System.out.println(obj));
ข้อสรุป
ดังที่เราเห็นจากเนื้อหาทั้งหมดข้างต้น การวนซ้ำ
for-each loop
เป็นเพียง "น้ำตาลเชิงวากยสัมพันธ์" ที่ด้านบนของตัววนซ้ำ อย่างไรก็ตาม ปัจจุบันมีการใช้กันในหลายแห่ง นอกจากนี้จะต้องใช้ผลิตภัณฑ์ใด ๆ ด้วยความระมัดระวัง ตัวอย่างเช่น ผู้ที่ไม่เป็นอันตราย
forEachRemaining
อาจซ่อนความประหลาดใจอันไม่พึงประสงค์ไว้ และนี่เป็นการพิสูจน์อีกครั้งว่าจำเป็นต้องมีการทดสอบหน่วย การทดสอบที่ดีสามารถระบุกรณีการใช้งานดังกล่าวในโค้ดของคุณได้ สิ่งที่คุณสามารถดู/อ่านในหัวข้อ:
#เวียเชสลาฟ
GO TO FULL VERSION