Có cách nào để truy cập vào bộ đếm lặp trong mỗi vòng lặp của Java không?


273

Có cách nào trong vòng lặp for cho mỗi vòng lặp của Java không

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

để tìm hiểu mức độ thường xuyên của vòng lặp đã được xử lý?

Bên cạnh việc sử dụng for(int i=0; i < boundary; i++)vòng lặp cũ và nổi tiếng -, là cấu trúc

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

cách duy nhất để có một bộ đếm như vậy có sẵn trong một vòng lặp for-mỗi?


2
Một điều đáng tiếc khác là bạn không thể sử dụng biến vòng lặp bên ngoài vòng lặp,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val

@Val trừ khi tham chiếu có hiệu lực cuối cùng. Xem câu trả lời của tôi để biết cách sử dụng tính năng này
rmuller

Câu trả lời:


205

Không, nhưng bạn có thể cung cấp truy cập của riêng bạn.

Lý do cho điều này là vòng lặp for-every bên trong không bộ đếm; nó dựa trên giao diện Iterable , tức là nó sử dụng một Iteratorvòng lặp thông qua "bộ sưu tập" - có thể hoàn toàn không phải là một bộ sưu tập, và trên thực tế có thể không phải là thứ gì đó dựa trên các chỉ mục (như danh sách được liên kết).


5
Ruby có một cấu trúc cho điều này và Java cũng sẽ có được nó ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiP Quảng cáo

9
Câu trả lời nên bắt đầu bằng "Không, nhưng bạn có thể cung cấp bộ đếm của riêng bạn."
dùng1094206

1
FYI @NicholasDiP Square có một each_with_indexphương thức lặp cho EnumerableRuby. Xem apidock.com/ruby/Enumerable/each_with_index
saywhatnow

@saywhatnow vâng đó là điều tôi muốn nói xin lỗi - không chắc là tôi đã hút thuốc gì khi tôi đưa ra nhận xét trước đó
Nicholas DiP quảng cáo

2
"Lý do cho điều này là vòng lặp for-every bên trong không có bộ đếm". Nên được đọc là "Các nhà phát triển Java không quan tâm đến việc lập trình viên chỉ định biến chỉ số với giá trị ban đầu bên trong forvòng lặp", đại loại nhưfor (int i = 0; String s: stringArray; ++i)
izogfif

64

Có một cách khác.

Cho rằng bạn viết Indexlớp của riêng bạn và một phương thức tĩnh trả về một Iterablethể hiện của lớp này, bạn có thể

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

Trường hợp thực hiện With.indexlà một cái gì đó như

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
Ý tưởng gọn gàng. Tôi đã được nâng cấp nếu nó không được tạo ra đối tượng tồn tại trong lớp Index.
mR_fr0g

3
@ mR_fr0g đừng lo lắng, tôi đã điểm chuẩn điều này và việc tạo tất cả các đối tượng này không chậm hơn việc sử dụng lại cùng một đối tượng cho mỗi lần lặp. Lý do là tất cả các đối tượng này chỉ được phân bổ trong không gian eden và không bao giờ đủ sống để đạt được đống. Vì vậy, phân bổ chúng cũng nhanh như phân bổ các biến cục bộ.
akuhn

1
@akuhn Chờ đã. Không gian địa đàng không có nghĩa là không có GC được lấy lại. Hoàn toàn ngược lại, bạn cần gọi hàm tạo, quét sớm hơn với GC và hoàn thiện. Điều này không chỉ tải CPU mà còn vô hiệu hóa bộ nhớ cache mọi lúc. Không có gì như thế này là cần thiết cho các biến cục bộ. Tại sao bạn nói rằng điều này là "giống nhau"?
Val

7
Nếu bạn lo lắng về hiệu suất của các đối tượng sống ngắn như thế này, bạn đang làm sai. Hiệu suất nên là điều quan trọng nhất để lo lắng về ... khả năng đọc đầu tiên. Đây thực sự là một công trình khá tốt.
Bill K

4
Tôi sẽ nói rằng tôi không thực sự hiểu nhu cầu tranh luận khi thể hiện Index có thể được tạo một lần và sử dụng lại / cập nhật, chỉ để tranh luận và làm mọi người hài lòng. Tuy nhiên, trong các phép đo của tôi, phiên bản tạo ra một Chỉ số () mới mỗi lần thực hiện nhanh hơn hai lần trên máy của tôi, tương đương với một trình vòng lặp gốc chạy cùng các lần lặp.
Eric Woodruff

47

Giải pháp đơn giản nhất chỉ chạy bộ đếm của riêng bạn, do đó:

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

Lý do cho điều này là do không có đảm bảo thực tế rằng các mục trong bộ sưu tập (biến thể forlặp lại đó) thậm chí một chỉ mục hoặc thậm chí có một thứ tự được xác định (một số bộ sưu tập có thể thay đổi thứ tự khi bạn thêm hoặc xóa phần tử).

Xem ví dụ, mã sau đây:

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");
  }
}

Khi bạn chạy nó, bạn có thể thấy một cái gì đó như:

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

chỉ ra rằng, đúng như vậy, trật tự không được coi là một tính năng nổi bật của một bộ.

Có nhiều cách khác để làm điều đó mà không cần bộ đếm thủ công nhưng đó là một công việc hợp lý vì lợi ích đáng ngờ.


2
Đây dường như vẫn là giải pháp rõ ràng nhất, ngay cả với giao diện List và Iterable.
Joshua Pinter

27

Sử dụng lambdascác giao diện chức năng trong Java 8 giúp tạo ra sự trừu tượng hóa vòng lặp mới. Tôi có thể lặp qua một bộ sưu tập với chỉ mục và kích thước bộ sưu tập:

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

Đầu ra nào:

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

Mà tôi đã thực hiện như:

   @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());
      }
   }

Các khả năng là vô tận. Ví dụ: tôi tạo một bản tóm tắt sử dụng hàm đặc biệt chỉ cho phần tử đầu tiên:

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

Mà in một danh sách được phân tách bằng dấu phẩy chính xác:

one,two,three,four

Mà tôi đã thực hiện như:

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);
      }
   }
}

Các thư viện sẽ bắt đầu bật lên để làm những việc này, hoặc bạn có thể tự cuộn.


3
Tôi không chỉ trích bài đăng này (đó là "cách tuyệt vời để làm điều đó" ngày nay tôi đoán vậy), nhưng tôi đấu tranh để xem cách này dễ hơn / tốt hơn so với một trường học cũ đơn giản cho vòng lặp: for (int i = 0; i < list.size (); i ++) {} Ngay cả bà cũng có thể hiểu điều này và có lẽ dễ gõ hơn nếu không có cú pháp và các ký tự không phổ biến mà lambda sử dụng. Đừng hiểu sai ý tôi, tôi thích sử dụng lambdas cho một số thứ nhất định (kiểu mẫu xử lý sự kiện gọi lại / sự kiện) nhưng tôi không thể hiểu được cách sử dụng trong các trường hợp như thế này. Công cụ tuyệt vời, nhưng sử dụng nó mọi lúc , tôi không thể làm được.
Manius

Tôi cho rằng, một số tránh chỉ số off-by-one trên / dưới so với chính danh sách. Nhưng có tùy chọn được đăng ở trên: int i = 0; for (String s: stringArray) {doS SomethingWith (s, i); i ++; }
Manius

21

Java 8 đã giới thiệu phương thức Iterable#forEach()/ Map#forEach(), hiệu quả hơn cho nhiều Collection/ Maptriển khai so với vòng lặp "cổ điển" cho mỗi vòng lặp. Tuy nhiên, cũng trong trường hợp này, một chỉ số không được cung cấp. Mẹo ở đây là sử dụng AtomicIntegerbên ngoài biểu thức lambda. Lưu ý: các biến được sử dụng trong biểu thức lambda phải có hiệu quả cuối cùng, đó là lý do tại sao chúng ta không thể sử dụng một biến thông thường int.

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

16

Tôi sợ điều này là không thể với foreach. Nhưng tôi có thể gợi ý cho bạn một vòng lặp kiểu cũ đơn giản :

    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();
    }

Lưu ý rằng, giao diện Danh sách cho phép bạn truy cập it.nextIndex().

(biên tập)

Để ví dụ thay đổi của bạn:

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

9

Một trong những thay đổi Sunđang được xem xét Java7là cung cấp quyền truy cập vào bên Iteratortrong các vòng lặp foreach. cú pháp sẽ giống như thế này (nếu điều này được chấp nhận):

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

Đây là đường cú pháp, nhưng rõ ràng rất nhiều yêu cầu đã được đưa ra cho tính năng này. Nhưng cho đến khi được phê duyệt, bạn sẽ phải tự mình đếm các lần lặp hoặc sử dụng vòng lặp thông thường cho một vòng lặp Iterator.


7

Giải pháp thành ngữ:

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();
}

đây thực sự là giải pháp mà Google gợi ý trong cuộc thảo luận về ổi về lý do tại sao họ không cung cấp CountingIterator.


4

Mặc dù có rất nhiều cách khác được đề cập để đạt được điều tương tự, tôi sẽ chia sẻ cách của mình cho một số người dùng không hài lòng. Tôi đang sử dụng tính năng Java 8 IntStream.

1. Mảng

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. Danh sách

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

Nếu bạn cần một bộ đếm trong một vòng lặp cho mỗi vòng, bạn phải tự đếm. Không có xây dựng trong quầy theo như tôi biết.


(Tôi đã sửa lỗi đó trong câu hỏi.)
Tom Hawtin - tackline

1

Có một "biến thể" để trả lời câu trả lời ... ;-)

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

3
Tò mò, có bất kỳ lợi ích của việc sử dụng này trên i=0; i++;phương pháp dường như rõ ràng hơn ?
Joshua Pinter

1
Tôi đoán nếu bạn cần sử dụng lại chỉ mục i sau doS Something trong phạm vi for.
gcedo

1

Đối với các tình huống đôi khi tôi chỉ cần chỉ mục, như trong mệnh đề bắt, đôi khi tôi sẽ sử dụng 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
Hãy cẩn thận rằng điều này cần một .equals () - thực hiện các đối tượng Mảng có thể xác định duy nhất một đối tượng nhất định. Đây không phải là trường hợp của Chuỗi, nếu một chuỗi nhất định nằm trong Mảng nhiều lần, bạn sẽ chỉ nhận được chỉ mục của lần xuất hiện đầu tiên, ngay cả khi bạn đã lặp lại trên một mục sau đó với cùng một chuỗi.
Kosi2801

-1

Giải pháp tốt nhất và tối ưu hóa là làm những việc sau:

int i=0;

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

Trong đó Kiểu có thể là bất kỳ kiểu dữ liệu và kiểu nào là biến mà bạn đang áp dụng cho một vòng lặp.


Vui lòng cung cấp câu trả lời của bạn ở định dạng mã có thể lặp lại.
Nareman Darwish

Đây chính xác là cách mà một giải pháp thay thế được mong muốn. Câu hỏi không phải là về các loại mà là về khả năng truy cập vào bộ đếm vòng lặp theo cách KHÁC BIỆT hơn là đếm thủ công.
Kosi2801

-4

Tôi hơi ngạc nhiên khi không ai đề xuất những điều sau đây (tôi thừa nhận đó là một cách tiếp cận lười biếng ...); Nếu stringArray là một Danh sách sắp xếp nào đó, bạn có thể sử dụng một cái gì đó như stringArray.indexOf (S) để trả về một giá trị cho số hiện tại.

Lưu ý: điều này giả định rằng các thành phần của Danh sách là duy nhất hoặc không thành vấn đề nếu chúng không phải là duy nhất (vì trong trường hợp đó, nó sẽ trả về chỉ mục của bản sao đầu tiên được tìm thấy).

Có những tình huống sẽ đủ ...


9
Với hiệu suất tiếp cận O (n²) cho toàn bộ vòng lặp, thật khó để tưởng tượng ngay cả một vài tình huống trong đó điều này sẽ vượt trội hơn bất cứ điều gì khác. Lấy làm tiếc.
Kosi2801

-5

Đây là một ví dụ về cách tôi đã làm điều này. Điều này nhận được chỉ số tại mỗi vòng lặp. Hi vọng điêu nay co ich.

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
Xin lỗi, đây không phải là trả lời câu hỏi. Đó không phải là về việc truy cập nội dung mà là về một bộ đếm tần suất lặp đi lặp lại.
Kosi2801

Câu hỏi là tìm ra chỉ mục hiện tại trong vòng lặp trong một vòng lặp cho mỗi kiểu.
rumman0786
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.