มีวิธีการเข้าถึงตัวนับซ้ำในแต่ละวงของ Java หรือไม่?


274

มีวิธีในการวนรอบของ Java สำหรับแต่ละ

for(String s : stringArray) {
  doSomethingWith(s);
}

เพื่อดูว่ามีการประมวลผลลูปบ่อยแค่ไหน?

นอกเหนือจากการใช้ลูปเก่าและที่รู้จักกันดีfor(int i=0; i < boundary; i++)- ลูปเป็นโครงสร้าง

int i = 0;
for(String s : stringArray) {
  doSomethingWith(s);
  i++;
}

วิธีเดียวที่จะมีตัวนับเช่นนี้ในแต่ละลูป?


2
น่าเสียดายอีกอย่างหนึ่งคือคุณไม่สามารถใช้ตัวแปรลูปนอกลูปได้Type var = null; for (var : set) dosomething; if (var != null) then ...
Val

@Val เว้นแต่การอ้างอิงจะมีผลบังคับใช้ขั้นสุดท้าย ดูคำตอบของฉันสำหรับวิธีการใช้คุณสมบัตินี้
rmuller

คำตอบ:


205

ไม่ แต่คุณสามารถให้เคาน์เตอร์ของคุณเอง

เหตุผลของเรื่องนี้ก็คือว่าห่วงสำหรับแต่ละภายในไม่ได้มีเคาน์เตอร์; มันใช้อินเทอร์เฟซIterableกล่าวคือใช้การIteratorวนซ้ำผ่าน "คอลเลกชัน" - ซึ่งอาจไม่ใช่คอลเลกชันเลยและในความเป็นจริงอาจเป็นสิ่งที่ไม่ได้อยู่บนพื้นฐานของดัชนี (เช่นรายการที่เชื่อมโยง)


5
ทับทิมมีโครงสร้างสำหรับการนี้และ Java ควรจะได้รับมันมากเกินไป ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
นิโคลัส DiPiazza

9
คำตอบควรเริ่มต้นด้วย "ไม่ แต่คุณสามารถให้เคาน์เตอร์ของคุณเอง"
user1094206

1
FYI @NicholasDiP Piazza มีeach_with_indexวิธีวนรอบสำหรับEnumerableใน Ruby ดู apidock.com/ruby/Enumerable/each_with_index
saywhatnow

@saywhatnow ใช่นั่นคือสิ่งที่ฉันหมายถึงขอโทษ - ไม่แน่ใจว่าสิ่งที่ฉันกำลังสูบบุหรี่เมื่อฉันได้แสดงความคิดเห็นก่อนหน้านี้
นิโคลัส DiP Piazza

2
"สาเหตุของเรื่องนี้คือการวนซ้ำแต่ละวงภายในไม่มีตัวนับ" ควรอ่านว่า "นักพัฒนา Java ไม่สนใจที่จะให้โปรแกรมเมอร์ระบุตัวแปรดัชนีด้วยค่าเริ่มต้นในforลูป" สิ่งที่ต้องการfor (int i = 0; String s: stringArray; ++i)
izogfif

64

มีวิธีอื่น

เนื่องจากคุณเขียนIndexคลาสของคุณเองและวิธีสแตติกที่ส่งคืนIterableอินสแตนซ์ของคลาสนี้ที่คุณสามารถทำได้

for (Index<String> each: With.index(stringArray)) {
    each.value;
    each.index;
    ...
}

ในกรณีที่การดำเนินการของWith.indexเป็นสิ่งที่ต้องการ

class With {
    public static <T> Iterable<Index<T>> index(final T[] array) {
        return new Iterable<Index<T>>() {
            public Iterator<Index<T>> iterator() {
                return new Iterator<Index<T>>() {
                    index = 0;
                    public boolean hasNext() { return index < array.size }
                    public Index<T> next() { return new Index(array[index], index++); }
                    ...
                }
            }
        }
    }
}

7
ไอเดียเรียบร้อย ฉันจะได้ upvoted ไม่ได้สำหรับการสร้างวัตถุอายุสั้นของคลาสดัชนี
mR_fr0g

3
@ mR_fr0g ไม่ต้องกังวลฉันเปรียบเทียบสิ่งนี้และสร้างวัตถุทั้งหมดเหล่านี้ไม่ได้ช้ากว่าการนำวัตถุเดียวกันมาใช้ซ้ำสำหรับการวนซ้ำแต่ละครั้ง เหตุผลก็คือวัตถุเหล่านี้ทั้งหมดได้รับการจัดสรรในพื้นที่เอเดนเท่านั้นและไม่เคยมีชีวิตที่ยาวนานพอที่จะไปถึงกอง ดังนั้นการปันส่วนให้เร็วพอ ๆ กับการจัดสรรตัวแปรท้องถิ่น
akuhn

1
@akuhn รอ พื้นที่ Eden ไม่ได้หมายความว่าจะไม่เรียกคืน GC ค่อนข้างตรงข้ามคุณจะต้องเรียกใช้ตัวสร้างสแกนเร็วขึ้นด้วย GC และจบ สิ่งนี้ไม่เพียง แต่โหลด CPU แต่ยังทำให้แคชใช้งานไม่ได้ตลอดเวลา สิ่งนี้ไม่จำเป็นสำหรับตัวแปรท้องถิ่น ทำไมคุณถึงพูดว่า "เหมือนกัน"?
Val

7
หากคุณกังวลเกี่ยวกับประสิทธิภาพของวัตถุที่มีอายุสั้นเช่นนี้คุณกำลังทำผิด ประสิทธิภาพควรเป็นสิ่งสุดท้ายที่ต้องกังวลเกี่ยวกับ ... ความสามารถในการอ่านครั้งแรก นี่เป็นโครงสร้างที่ดีทีเดียว
Bill K

4
ฉันจะบอกว่าฉันไม่เข้าใจความต้องการที่จะอภิปรายเมื่ออินสแตนซ์ดัชนีสามารถสร้างได้ครั้งเดียวและนำมาใช้ใหม่ / อัปเดตเพียงเพื่อโต้แย้งข้อโต้แย้งและทำให้ทุกคนมีความสุข อย่างไรก็ตามในการวัดของฉันเวอร์ชันที่สร้างดัชนีใหม่ () ทุกครั้งที่ทำเร็วกว่าสองเท่าในเครื่องของฉันประมาณเท่ากับ iterator ดั้งเดิมที่รันการทำซ้ำเดียวกัน
Eric Woodruff

47

ทางออกที่ง่ายที่สุดคือเรียกใช้ตัวนับของคุณเอง:

int i = 0;
for (String s : stringArray) {
    doSomethingWith(s, i);
    i++;
}

เหตุผลนี้เป็นเพราะไม่มีการรับประกันจริง ๆ ว่ารายการในคอลเลกชัน (ซึ่งตัวแปรที่ทำforซ้ำมากกว่า) แม้จะมีดัชนีหรือมีคำสั่งที่กำหนดไว้ (คอลเลกชันบางอย่างอาจเปลี่ยนลำดับเมื่อคุณเพิ่มหรือลบองค์ประกอบ)

ดูตัวอย่างรหัสต่อไปนี้:

import java.util.*;

public class TestApp {
  public static void AddAndDump(AbstractSet<String> set, String str) {
    System.out.println("Adding [" + str + "]");
    set.add(str);
    int i = 0;
    for(String s : set) {
        System.out.println("   " + i + ": " + s);
        i++;
    }
  }

  public static void main(String[] args) {
    AbstractSet<String> coll = new HashSet<String>();
    AddAndDump(coll, "Hello");
    AddAndDump(coll, "My");
    AddAndDump(coll, "Name");
    AddAndDump(coll, "Is");
    AddAndDump(coll, "Pax");
  }
}

เมื่อคุณเรียกใช้คุณจะเห็นสิ่งต่อไปนี้:

Adding [Hello]
   0: Hello
Adding [My]
   0: Hello
   1: My
Adding [Name]
   0: Hello
   1: My
   2: Name
Adding [Is]
   0: Hello
   1: Is
   2: My
   3: Name
Adding [Pax]
   0: Hello
   1: Pax
   2: Is
   3: My
   4: Name

แสดงให้เห็นว่าถูกต้องเพื่อไม่ถือว่าเป็นคุณสมบัติเด่นของชุด

มีวิธีอื่น ๆ ที่จะทำโดยไม่มีเคาน์เตอร์คู่มือ แต่มันเป็นงานที่ค่อนข้างยุติธรรมเพื่อผลประโยชน์ที่น่าสงสัย


2
สิ่งนี้ยังคงเป็นโซลูชั่นที่ชัดเจนที่สุดแม้จะมี List และ Iterable interfaces
Joshua Pinter

27

การใช้lambdasและส่วนต่อประสานการทำงานในJava 8ทำให้การสร้างลูป abstractions เป็นไปได้ ฉันสามารถวนรอบคอลเลกชันด้วยดัชนีและขนาดคอลเลกชัน:

List<String> strings = Arrays.asList("one", "two","three","four");
forEach(strings, (x, i, n) -> System.out.println("" + (i+1) + "/"+n+": " + x));

ผลลัพธ์ใด:

1/4: one
2/4: two
3/4: three
4/4: four

ซึ่งฉันนำมาใช้เป็น:

   @FunctionalInterface
   public interface LoopWithIndexAndSizeConsumer<T> {
       void accept(T t, int i, int n);
   }
   public static <T> void forEach(Collection<T> collection,
                                  LoopWithIndexAndSizeConsumer<T> consumer) {
      int index = 0;
      for (T object : collection){
         consumer.accept(object, index++, collection.size());
      }
   }

ความเป็นไปได้ไม่มีที่สิ้นสุด ตัวอย่างเช่นฉันสร้างนามธรรมที่ใช้ฟังก์ชั่นพิเศษเฉพาะสำหรับองค์ประกอบแรก:

forEachHeadTail(strings, 
                (head) -> System.out.print(head), 
                (tail) -> System.out.print(","+tail));

ซึ่งพิมพ์รายการที่คั่นด้วยเครื่องหมายจุลภาคอย่างถูกต้อง:

one,two,three,four

ซึ่งฉันนำมาใช้เป็น:

public static <T> void forEachHeadTail(Collection<T> collection, 
                                       Consumer<T> headFunc, 
                                       Consumer<T> tailFunc) {
   int index = 0;
   for (T object : collection){
      if (index++ == 0){
         headFunc.accept(object);
      }
      else{
         tailFunc.accept(object);
      }
   }
}

ห้องสมุดจะเริ่มปรากฏขึ้นเพื่อทำสิ่งต่าง ๆ เหล่านี้หรือคุณสามารถม้วนของคุณเอง


3
ไม่ใช่คำวิจารณ์ของโพสต์ (มันเป็น "วิธีที่ยอดเยี่ยมในการทำ" ทุกวันนี้ฉันเดา) แต่ฉันพยายามที่จะดูว่ามันง่ายกว่า / ดีกว่าโรงเรียนเก่าที่เรียบง่ายสำหรับลูป: สำหรับ (int i = 0; i < list.size (); i ++) {} แม้คุณยายจะเข้าใจสิ่งนี้และมันอาจจะง่ายกว่าที่จะพิมพ์โดยไม่มีไวยากรณ์และอักขระผิดปกติที่แลมบ์ดาใช้ อย่าเข้าใจฉันผิดฉันชอบใช้แลมบ์ดาสำหรับบางสิ่ง (รูปแบบการโทรกลับ / จัดการเหตุการณ์) แต่ฉันก็ไม่เข้าใจการใช้งานในกรณีเช่นนี้ เครื่องมือที่ยอดเยี่ยม แต่ใช้งานได้ตลอดเวลาฉันไม่สามารถทำได้
Manius

ฉันคิดว่าการหลีกเลี่ยงบางส่วนของดัชนีแบบปิด / ปิดต่อรายการ แต่มีตัวเลือกที่โพสต์ด้านบน: int i = 0; สำหรับ (String s: stringArray) {doSomethingWith (s, i); ฉัน ++; }
Manius

21

Java 8 แนะนำวิธีการIterable#forEach()/ Map#forEach()ซึ่งมีประสิทธิภาพมากขึ้นสำหรับหลายCollection/ Mapการใช้งานเมื่อเทียบกับ "คลาสสิก" สำหรับแต่ละวง อย่างไรก็ตามในกรณีนี้ยังไม่มีการจัดทำดัชนี เคล็ดลับที่นี่คือการใช้AtomicIntegerนอกแสดงออกแลมบ์ดา หมายเหตุ: ตัวแปรที่ใช้ภายในนิพจน์แลมบ์ดาจะต้องมีผลอย่างสมบูรณ์นั่นคือสาเหตุที่เราไม่สามารถใช้สามัญintได้

final AtomicInteger indexHolder = new AtomicInteger();
map.forEach((k, v) -> {
    final int index = indexHolder.getAndIncrement();
    // use the index
});

16

foreachฉันกลัวนี้ไม่ได้เป็นไปได้ด้วย แต่ฉันขอแนะนำให้คุณใช้ลูปแบบเก่าๆ ที่เรียบง่าย:

    List<String> l = new ArrayList<String>();

    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");

    // the array
    String[] array = new String[l.size()];

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        array[it.nextIndex()] = it.next();
    }

ขอให้สังเกตว่าส่วนต่อประสานรายการให้คุณเข้าถึงit.nextIndex()ได้

(แก้ไข)

ตัวอย่างที่เปลี่ยนแปลงของคุณ:

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        int i = it.nextIndex();
        doSomethingWith(it.next(), i);
    }

9

การเปลี่ยนแปลงอย่างใดอย่างหนึ่งที่SunกำลังพิจารณาJava7คือการให้การเข้าถึงภายในIteratorในวงรอบ foreach ไวยากรณ์จะเป็นดังนี้ (ถ้าเป็นที่ยอมรับ):

for (String str : list : it) {
  if (str.length() > 100) {
    it.remove();
  }
}

นี่คือน้ำตาลเชิงประโยค แต่เห็นได้ชัดว่ามีคำขอจำนวนมากสำหรับคุณลักษณะนี้ Iteratorแต่จนกว่าจะมีการอนุมัติคุณจะต้องนับซ้ำด้วยตัวเองหรือใช้เป็นประจำสำหรับวงที่มี


7

โซลูชันสำนวน:

final Set<Double> doubles; // boilerplate
final Iterator<Double> iterator = doubles.iterator();
for (int ordinal = 0; iterator.hasNext(); ordinal++)
{
    System.out.printf("%d:%f",ordinal,iterator.next());
    System.out.println();
}

นี้เป็นจริงการแก้ปัญหาที่ Google แสดงให้เห็นในการอภิปรายฝรั่งCountingIteratorที่ว่าทำไมพวกเขาไม่ได้ให้


4

แม้ว่าจะมีวิธีอื่น ๆ อีกมากมายที่กล่าวถึงเพื่อบรรลุเป้าหมายเดียวกัน แต่ฉันจะแบ่งปันวิธีของฉันสำหรับผู้ใช้ที่ไม่พอใจ ฉันใช้คุณสมบัติ Java 8 IntStream

1. อาร์เรย์

Object[] obj = {1,2,3,4,5,6,7};
IntStream.range(0, obj.length).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + obj[index]);
});

2. รายการ

List<String> strings = new ArrayList<String>();
Collections.addAll(strings,"A","B","C","D");

IntStream.range(0, strings.size()).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + strings.get(index));
});

2

หากคุณต้องการตัวนับในแต่ละวงคุณต้องนับตัวเอง ไม่มีเคาน์เตอร์ในตัวเท่าที่ฉันรู้


(ผมเคยได้รับการแก้ไขข้อผิดพลาดในคำถามที่.)
ทอม Hawtin - tackline

1

มี "ตัวแปร" สำหรับ pax 'answer ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}

3
อยากรู้อยากเห็นมีประโยชน์จากการใช้นี้มากกว่าวิธีการที่ดูเหมือนชัดเจนมากขึ้นi=0; i++;?
Joshua Pinter

1
ฉันเดาว่าคุณจำเป็นต้องใช้ดัชนี i อีกครั้งหลังจากทำบางสิ่งในขอบเขต
gcedo

1

สำหรับสถานการณ์ที่ฉันต้องการดัชนีเป็นครั้งคราวเช่นเดียวกับในประโยคจับบางครั้งฉันจะใช้ indexOf

for(String s : stringArray) {
  try {
    doSomethingWith(s);
  } catch (Exception e) {
    LOGGER.warn("Had some kind of problem with string " +
      stringArray.indexOf(s) + ": " + s, e);
  }
}

2
โปรดใช้ความระมัดระวังว่าสิ่งนี้จำเป็นต้องมี. equals () - การใช้งานออบเจ็กต์อาร์เรย์ซึ่งสามารถระบุวัตถุบางอย่างที่ไม่ซ้ำกัน นี่ไม่ใช่กรณีของ Strings หากสตริงหนึ่งอยู่ใน Array หลายครั้งคุณจะได้รับดัชนีของการเกิดครั้งแรกเท่านั้นแม้ว่าคุณจะทำการวนซ้ำในไอเท็มภายหลังด้วยสตริงเดียวกันก็ตาม
Kosi2801

-1

ทางออกที่ดีที่สุดและเพิ่มประสิทธิภาพคือการทำสิ่งต่อไปนี้:

int i=0;

for(Type t: types) {
  ......
  i++;
}

โดยที่ Type สามารถเป็นชนิดข้อมูลและชนิดใดก็ได้เป็นตัวแปรที่คุณใช้สำหรับการวนซ้ำ


โปรดระบุคำตอบของคุณในรูปแบบรหัสที่ทำซ้ำได้
Nareman Darwish

นี่เป็นวิธีที่ต้องการโซลูชันทางเลือก คำถามไม่เกี่ยวกับประเภท แต่เกี่ยวกับความเป็นไปได้ในการเข้าถึงตัวนับลูปในวิธีที่แตกต่างกว่าการนับด้วยตนเอง
Kosi2801

-4

ฉันประหลาดใจเล็กน้อยที่ไม่มีใครแนะนำสิ่งต่อไปนี้ (ฉันยอมรับว่ามันเป็นวิธีที่ขี้เกียจ ... ); หาก stringArray เป็นรายการของการเรียงลำดับบางอย่างคุณสามารถใช้สิ่งที่ต้องการ stringArray.indexOf (S) เพื่อส่งกลับค่าสำหรับการนับปัจจุบัน

หมายเหตุ: นี่ถือว่าองค์ประกอบของรายการนั้นมีความเป็นเอกลักษณ์หรือไม่สำคัญว่ามันจะไม่ซ้ำกันหรือไม่ (เพราะในกรณีนี้มันจะส่งคืนดัชนีของสำเนาแรกที่พบ)

มีสถานการณ์ที่น่าจะเพียงพอ ...


9
ด้วยประสิทธิภาพที่เข้าใกล้ O (n²) ตลอดทั้งวงมันเป็นเรื่องยากที่จะจินตนาการถึงแม้กระทั่งบางสถานการณ์ที่สิ่งนี้จะเหนือกว่าสิ่งอื่นใด ขอโทษ
Kosi2801

-5

นี่คือตัวอย่างของวิธีที่ฉันทำสิ่งนี้ นี่จะได้รับดัชนีที่สำหรับแต่ละลูป หวังว่านี่จะช่วยได้

public class CheckForEachLoop {

    public static void main(String[] args) {

        String[] months = new String[] { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST",
                "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" };
        for (String s : months) {
            if (s == months[2]) { // location where you can change
              doSomethingWith(s); // however many times s and months
                                  // doSomethingWith(s) will be completed and 
                                  // added together instead of counter
            }

        }
        System.out.println(s); 


    }
}

1
ขออภัยนี่ไม่ได้ตอบคำถาม มันไม่ได้เกี่ยวกับการเข้าถึงเนื้อหา แต่เกี่ยวกับตัวนับความถี่ที่วนซ้ำแล้วซ้ำอีก
Kosi2801

คำถามคือการหาดัชนีปัจจุบันในวงในแต่ละวงสไตล์
rumman0786
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.