Câu lệnh nâng cao for hoạt động như thế nào đối với mảng và cách lấy một trình lặp cho một mảng?


76

Cho đoạn mã sau:

int[] arr = {1, 2, 3};
for (int i : arr)
    System.out.println(i);

Tôi có những câu hỏi sau:

  1. Vòng lặp for-each ở trên hoạt động như thế nào?
  2. Làm cách nào để lấy một trình lặp cho một mảng trong Java?
  3. Mảng có được chuyển đổi thành danh sách để lấy trình lặp không?


Câu hỏi trên về hiệu suất đã được đóng lại. Nó được trả lời ở đây: stackoverflow.com/questions/256859/…
Orangle

Tôi không tìm thấy câu trả lời dứt khoát cho điểm 1. Vòng lặp for each có kiểm tra loại mục đang được lặp lại và sử dụng vòng lặp for thông thường dưới các bìa nếu đó là một mảng không? Hay nó tạo một Lặp lại khi đang di chuyển? Hay nó chuyển đổi thành Danh sách? Tôi đã không tìm thấy một câu trả lời dứt khoát cho điều này.
Elisabeth

Câu trả lời:


55

Nếu bạn muốn một Iteratormảng trên một mảng, bạn có thể sử dụng một trong các triển khai trực tiếp ở đó thay vì gói mảng trong một List. Ví dụ:

Bộ sưu tập Apache Commons ArrayIterator

Hoặc, cái này, nếu bạn muốn sử dụng thuốc chung:

com.Ostermiller.util.ArrayIterator

Lưu ý rằng nếu bạn muốn có một Iteratorkiểu nguyên thủy, bạn không thể, vì kiểu nguyên thủy không thể là một tham số chung. Ví dụ: nếu bạn muốn một Iterator<int>, bạn phải sử dụng một Iterator<Integer>thay thế, điều này sẽ dẫn đến rất nhiều autoboxing và -unboxing nếu nó được hỗ trợ bởi một int[].


2
Đồng ý. Hoặc xây dựng ArrayIterator của riêng bạn, nếu bạn muốn tiếp tục sử dụng kiểu dữ liệu nguyên thủy
Khaled.K

Hoàn thành mã không hiển thị bất kỳ đề xuất nào cho ArrayIt; giả sử tất cả các thư viện của bên thứ ba.
Mark Jeronimus

50

Không, không có chuyển đổi. JVM chỉ lặp qua mảng bằng cách sử dụng một chỉ mục trong nền.

Trích dẫn từ Phiên bản Java thứ 2 hiệu quả, Mục 46:

Lưu ý rằng không có hình phạt về hiệu suất khi sử dụng vòng lặp for-each, ngay cả đối với các mảng. Trên thực tế, nó có thể cung cấp một chút lợi thế về hiệu suất so với vòng lặp for thông thường trong một số trường hợp, vì nó chỉ tính giới hạn của chỉ mục mảng một lần.

Vì vậy, bạn không thể nhận được một Iteratorcho một mảng (tất nhiên trừ khi bằng cách chuyển nó thành một mảng Listđầu tiên).


2
Có, +1 Nó chỉ là một cuộc bình thường for. @Emil, bạn không thể.
st0le

Java hiệu quả là sai nếu đó là một Bộ sưu tập trống. Trong trường hợp đó, nó sẽ tạo một lặp lại không cần thiết. Điều này dẫn đến Object churn. Cách khắc phục là bọc nó trong một kiểm tra isEmpty (). Điều này đặc biệt quan trọng trên các thiết bị hạn chế bộ nhớ như Android.
keyboardr

Chúng ta có chắc là nó không tạo một Lặp lại cho mảng hoặc chuyển đổi mảng thành một Danh sách? Nói cách khác, bên trong, Java đang nói "nếu kiểu là một mảng thì hãy sử dụng vòng lặp for thông thường với chỉ mục ẩn, nếu không, hãy sử dụng Iterable." ???
Elisabeth

33

Arrays.asList (arr) .iterator ();

Hoặc viết của riêng bạn, triển khai giao diện ListIterator ..


23
Arrays.asList()không thể lấy an int[], only Object[]và subclasses.
ILMTitan

3
cái này sai. điều này trả về a List<int[]>, và do đó là một Iterator<int[]>, không phải a List<Integer>!
njzk2

Bạn đang chuyển đổi mảng thành một danh sách chỉ để cung cấp một trình lặp ?!
Khaled.K

1
@KhaledKhnifer Thực sự không có nhiều chuyển đổi đang diễn ra. Arrays.asList()sử dụng mảng thực tế và bao bọc nó trong một lớp điều chỉnh nó với giao diện danh sách. Vì vậy, ngoại trừ vấn đề "không hỗ trợ nguyên thủy" (và đối với những người đến đây với cùng một vấn đề trong một tình huống không phải nguyên thủy) thì đây thực sự là một giải pháp tốt.
Jasper

@ Khaled.K, hãy xem câu trả lời mới của Zenexer ( stackoverflow.com/users/1188377/zenexer ).
Orangle

31

Bộ sưu tập của Google Guava Librarie cung cấp chức năng như sau:

Iterator<String> it = Iterators.forArray(array);

Người ta nên thích Guava trên Bộ sưu tập Apache (có vẻ như đã bị bỏ rơi).


1
Đây thường là câu trả lời tốt nhất ngoại trừ khi chúng tôi đang xử lý mảng các nguyên thủy (chẳng hạn như int[]) thì điều này không còn hoạt động nữa :( vì chúng tôi không thể chỉ định Iterator<int>hoặcIterator<Integer>
chakrit


10
public class ArrayIterator<T> implements Iterator<T> {
  private T array[];
  private int pos = 0;

  public ArrayIterator(T anArray[]) {
    array = anArray;
  }

  public boolean hasNext() {
    return pos < array.length;
  }

  public T next() throws NoSuchElementException {
    if (hasNext())
      return array[pos++];
    else
      throw new NoSuchElementException();
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}

9

Nói một cách chính xác, bạn không thể lấy một trình lặp của mảng nguyên thủy, bởi vì Iterator.next () chỉ có thể trả về một Đối tượng. Nhưng thông qua sự kỳ diệu của autoboxing, bạn có thể lấy trình vòng lặp bằng phương thức Arrays.asList () .

Iterator<Integer> it = Arrays.asList(arr).iterator();

Câu trả lời trên là sai, bạn không thể sử dụng Arrays.asList()trên một mảng nguyên thủy, nó sẽ trả về a List<int[]>. Sử dụng Guava 's Ints.asList()thay thế.


5

Bạn không thể trực tiếp lấy một trình vòng lặp cho một mảng.

Nhưng bạn có thể sử dụng một Danh sách, được hỗ trợ bởi mảng của bạn và có được một trình điều khiển trong danh sách này. Đối với điều đó, mảng của bạn phải là mảng Số nguyên (thay vì mảng int):

Integer[] arr={1,2,3};
List<Integer> arrAsList = Arrays.asList(arr);
Iterator<Integer> iter = arrAsList.iterator();

Lưu ý: nó chỉ là lý thuyết. Bạn có thể nhận được một trình lặp như thế này, nhưng tôi không khuyến khích bạn làm như vậy. Hiệu suất không tốt so với lặp trực tiếp trên mảng với "cú pháp mở rộng cho".

Lưu ý 2: cấu trúc danh sách với phương thức này không hỗ trợ tất cả các phương thức (vì danh sách được hỗ trợ bởi mảng có kích thước cố định). Ví dụ: phương thức "remove" của trình vòng lặp của bạn sẽ dẫn đến một ngoại lệ.


Tôi không nghĩ nó sẽ hoạt động.Arrays.asList (arr) sẽ trả về Danh sách kiểu int []. Vì nó là mảng nguyên thủy.
Emil

@Emil: Java generics không hoạt động với các kiểu nguyên thủy. Arrays.asListhoàn toàn xử lý điều này bằng cách đóng gói các giá trị trong mảng. Do đó, kết quả thực sự a List<Integer>.
Konrad Rudolph

@emil nó hoạt động vì Arrays.asList mất một varargs tham số
Sean Patrick Floyd

@seanizer: nhưng tôi đã thử mã trên trong IDE của mình và nó hiển thị lỗi.
Emil

nó biên dịch trong nhật thực của tôi. bạn đã đặt tuân thủ trình biên dịch ít nhất 1,5 chưa?
Sean Patrick Floyd

4

Vòng lặp for-each ở trên hoạt động như thế nào?

Giống như nhiều tính năng mảng khác, JSL đề cập đến mảng một cách rõ ràng và cung cấp cho chúng các thuộc tính kỳ diệu. JLS 7 14.14.2 :

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

Nếu loại Biểu thức là một loại phụ của Iterable , thì bản dịch như sau

[...]

Nếu không, Biểu thức nhất thiết phải có kiểu mảng, T[] ,. [[ MA THUẬT! ]]

Để cho L1 ... Lm là chuỗi nhãn (có thể trống) ngay trước câu lệnh for nâng cao.

Câu lệnh for nâng cao tương đương với câu lệnh for cơ bản có dạng:

T[] #a = Expression;
L1: L2: ... Lm:
for (int #i = 0; #i < #a.length; #i++) {
    VariableModifiersopt TargetType Identifier = #a[#i];
    Statement
}

#a#ilà các số nhận dạng được tạo tự động khác với bất kỳ số nhận dạng nào khác (được tạo tự động hoặc theo cách khác) nằm trong phạm vi tại điểm xảy ra câu lệnh nâng cao for.

Mảng có được chuyển đổi thành danh sách để lấy trình lặp không?

Hãy javapbắt đầu:

public class ArrayForLoop {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i : arr)
            System.out.println(i);
    }
}

sau đó:

javac ArrayForLoop.java
javap -v ArrayForLoop

main phương pháp có chỉnh sửa một chút để dễ đọc hơn:

 0: iconst_3
 1: newarray       int
 3: dup
 4: iconst_0
 5: iconst_1
 6: iastore
 7: dup
 8: iconst_1
 9: iconst_2
10: iastore
11: dup
12: iconst_2
13: iconst_3
14: iastore

15: astore_1
16: aload_1
17: astore_2
18: aload_2
19: arraylength
20: istore_3
21: iconst_0
22: istore        4

24: iload         4
26: iload_3
27: if_icmpge     50
30: aload_2
31: iload         4
33: iaload
34: istore        5
36: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
39: iload         5
41: invokevirtual #3    // Method java/io/PrintStream.println:(I)V
44: iinc          4, 1
47: goto          24
50: return

Phá vỡ:

  • 0to 14: tạo mảng
  • 15to 22: chuẩn bị cho vòng lặp for. Tại 22 , lưu trữ số nguyên 0từ ngăn xếp vào vị trí cục bộ 4. ĐÓ là biến vòng lặp.
  • 24to 47: vòng lặp. Biến vòng lặp được truy xuất tại 31và tăng lên tại 44. Khi nó bằng với độ dài mảng được lưu trữ trong biến cục bộ 3 khi kiểm tra tại 27, vòng lặp kết thúc.

Kết luận : nó giống như thực hiện một vòng lặp for rõ ràng với một biến chỉ mục, không có trình lặp nào tham gia.


4

Tôi hơi muộn với trò chơi, nhưng tôi nhận thấy một số điểm chính bị bỏ qua, đặc biệt là về Java 8 và hiệu quả của Arrays.asList.

1. Vòng lặp for-each hoạt động như thế nào?

Như Ciro Santilli六四事件法轮功包卓轩nhọn ra, sẽ có một tiện ích tiện dụng để kiểm tra bytecode mà tàu với JDK: javap. Sử dụng điều đó, chúng tôi có thể xác định rằng hai đoạn mã sau tạo ra bytecode giống hệt nhau kể từ Java 8u74:

Đối với mỗi vòng lặp:

int[] arr = {1, 2, 3};
for (int n : arr) {
    System.out.println(n);
}

Vòng lặp for:

int[] arr = {1, 2, 3};

{  // These extra braces are to limit scope; they do not affect the bytecode
    int[] iter = arr;
    int length = iter.length;
    for (int i = 0; i < length; i++) {
        int n = iter[i];
        System.out.println(n);
    }
}

2. Làm cách nào để lấy một trình lặp cho một mảng trong Java?

Mặc dù điều này không hoạt động đối với các nguyên bản, nhưng cần lưu ý rằng việc chuyển đổi một mảng thành Danh sách với Arrays.asListkhông ảnh hưởng đến hiệu suất theo bất kỳ cách nào đáng kể. Tác động đến cả bộ nhớ và hiệu suất là gần như không thể đo lường được.

Arrays.asListkhông sử dụng triển khai Danh sách bình thường có thể truy cập dễ dàng dưới dạng một lớp. Nó sử dụng java.util.Arrays.ArrayList, không giống như java.util.ArrayList. Nó là một lớp bao bọc rất mỏng xung quanh một mảng và không thể thay đổi kích thước. Nhìn vào mã nguồn của java.util.Arrays.ArrayList, chúng ta có thể thấy rằng nó được thiết kế để có chức năng tương đương với một mảng. Hầu như không có chi phí cao. Lưu ý rằng tôi đã bỏ qua tất cả trừ mã có liên quan nhất và thêm nhận xét của riêng tôi.

public class Arrays {
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }
    }
}

Trình lặp đang ở java.util.AbstractList.Itr. Đối với các trình vòng lặp, nó rất đơn giản; nó chỉ gọi get()cho đến khi size()được kết nối, giống như một vòng lặp hướng dẫn sử dụng. Đây là cách triển khai đơn giản nhất và thường hiệu quả nhất của mộtIterator một mảng.

Một lần nữa, Arrays.asListkhông tạo ra mộtjava.util.ArrayList . Nó nhẹ hơn nhiều và phù hợp để có được một trình lặp với chi phí không đáng kể.

Mảng nguyên thủy

Như những người khác đã lưu ý, Arrays.asListkhông thể được sử dụng trên các mảng nguyên thủy. Java 8 giới thiệu một số công nghệ mới để xử lý các tập hợp dữ liệu, một số công nghệ trong số đó có thể được sử dụng để trích xuất các trình vòng lặp đơn giản và tương đối hiệu quả từ các mảng. Lưu ý rằng nếu bạn sử dụng generic, bạn sẽ luôn gặp vấn đề khi mở hộp quyền anh: bạn sẽ cần chuyển đổi từ int sang Integer và sau đó quay lại int. Mặc dù quyền anh / unboxing thường không đáng kể, nhưng nó có tác động đến hiệu suất O (1) trong trường hợp này và có thể dẫn đến sự cố với các mảng rất lớn hoặc trên máy tính có tài nguyên rất hạn chế (ví dụ: SoC ).

Yêu thích cá nhân của tôi cho bất kỳ loại hoạt động truyền / quyền anh mảng nào trong Java 8 là API luồng mới. Ví dụ:

int[] arr = {1, 2, 3};
Iterator<Integer> iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator();

API luồng cũng cung cấp các cấu trúc để tránh vấn đề quyền anh ngay từ đầu, nhưng điều này yêu cầu bỏ qua các trình lặp để có lợi cho luồng. Có các loại luồng dành riêng cho int, long và double (IntStream, LongStream và DoubleStream, tương ứng).

int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr);
stream.forEach(System.out::println);

Điều thú vị là Java 8 cũng cho biết thêm java.util.PrimitiveIterator. Điều này cung cấp những gì tốt nhất của cả hai thế giới: khả năng tương thích với Iterator<T>thông qua quyền anh cùng với các phương pháp tránh đấm bốc. PrimitiveIterator có ba giao diện tích hợp để mở rộng nó: OfInt, OfLong và OfDouble. Cả ba sẽ đóng hộp nếu next()được gọi nhưng cũng có thể trả về các nguyên thủy thông qua các phương thức như nextInt(). Mã mới hơn được thiết kế cho Java 8 nên tránh sử dụng next()trừ khi thực sự cần thiết.

int[] arr = {1, 2, 3};
PrimitiveIterator.OfInt iterator = Arrays.stream(arr);

// You can use it as an Iterator<Integer> without casting:
Iterator<Integer> example = iterator;

// You can obtain primitives while iterating without ever boxing/unboxing:
while (iterator.hasNext()) {
    // Would result in boxing + unboxing:
    //int n = iterator.next();

    // No boxing/unboxing:
    int n = iterator.nextInt();

    System.out.println(n);
}

Nếu bạn chưa sử dụng Java 8, thật đáng buồn là tùy chọn đơn giản nhất của bạn kém ngắn gọn hơn rất nhiều và gần như chắc chắn sẽ liên quan đến quyền anh:

final int[] arr = {1, 2, 3};
Iterator<Integer> iterator = new Iterator<Integer>() {
    int i = 0;

    @Override
    public boolean hasNext() {
        return i < arr.length;
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        return arr[i++];
    }
};

Hoặc nếu bạn muốn tạo thứ gì đó có thể tái sử dụng nhiều hơn:

public final class IntIterator implements Iterator<Integer> {
    private final int[] arr;
    private int i = 0;

    public IntIterator(int[] arr) {
        this.arr = arr;
    }

    @Override
    public boolean hasNext() {
        return i < arr.length;
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        return arr[i++];
    }
}

Bạn có thể giải quyết vấn đề quyền anh ở đây bằng cách thêm các phương pháp của riêng bạn để lấy nguyên mẫu, nhưng nó sẽ chỉ hoạt động với mã nội bộ của riêng bạn.

3. Mảng có được chuyển đổi thành danh sách để lấy trình lặp không?

Không có nó không phải là. Tuy nhiên, điều đó không có nghĩa là gói nó trong một danh sách sẽ mang lại cho bạn hiệu suất kém hơn, miễn là bạn sử dụng thứ gì đó nhẹ chẳng hạn Arrays.asList.


1
Câu trả lời đầy đủ nhất theo ý kiến của tôi, nhưng tôi nghĩ rằng bạn nên thay thế mapToObj(Integer::new)vớimapToObj(Integer::valueOf)
Michael Schmeißer

Có vẻ như bạn nói đúng , @ MichaelSchmeißer. Cảm ơn vì tiền hỗ trợ. Tôi đã cập nhật câu trả lời cho phù hợp.
Zenexer

3

Đối với (2), Guava cung cấp chính xác những gì bạn muốn dưới dạng Int.asList () . Có một tương đương cho mỗi kiểu nguyên thủy trong lớp được liên kết, ví dụ: Booleanscho boolean, v.v.

    int[] arr={1,2,3};
    for(Integer i : Ints.asList(arr)) {
      System.out.println(i);
    }

2

Tôi là một sinh viên gần đây nhưng tôi TIN rằng ví dụ ban đầu với int [] đang lặp lại trên mảng nguyên thủy, nhưng không phải bằng cách sử dụng một đối tượng Iterator. Nó chỉ có cùng một cú pháp (tương tự) với các nội dung khác nhau,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays. nó như một danh sách với một phần tử. Chúng ta có thể xem mã nguồn cho cái này không? Bạn sẽ không muốn một ngoại lệ? Đừng bận tâm. Tôi đoán (đó là GUESS) rằng nó giống như (hoặc nó LÀ) một Bộ sưu tập singleton. Vì vậy, ở đây asList () không liên quan đến trường hợp với một mảng nguyên thủy, nhưng khó hiểu. Tôi không BIẾT mình đúng, nhưng tôi đã viết một chương trình nói rằng tôi đúng.

Vì vậy, ví dụ này (về cơ bản asList () không làm những gì bạn nghĩ và do đó không phải là thứ mà bạn thực sự sử dụng theo cách này) - Tôi hy vọng mã hoạt động tốt hơn mã đánh dấu là mã của tôi và, này, hãy nhìn vào dòng cuối cùng:

// Java(TM) SE Runtime Environment (build 1.6.0_19-b04)

import java.util.*;

public class Page0434Ex00Ver07 {
public static void main(String[] args) {
    int[] ii = new int[4];
    ii[0] = 2;
    ii[1] = 3;
    ii[2] = 5;
    ii[3] = 7;

    Arrays.asList(ii);

    Iterator ai = Arrays.asList(ii).iterator();

    int[] i2 = (int[]) ai.next();

    for (int i : i2) {
        System.out.println(i);
    }

    System.out.println(Arrays.asList(12345678).iterator().next());
}
}

1

Tôi thích câu trả lời từ thứ 30 bằng cách sử dụng IteratorsGuava. Tuy nhiên, từ một số khung công tác, tôi nhận được null thay vì một mảng trống và Iterators.forArray(array)không xử lý tốt điều đó. Vì vậy, tôi đã nghĩ ra phương pháp trợ giúp này, bạn có thể gọi nó bằngIterator<String> it = emptyIfNull(array);

public static <F> UnmodifiableIterator<F> emptyIfNull(F[] array) {
    if (array != null) {
        return Iterators.forArray(array);
    }
    return new UnmodifiableIterator<F>() {
        public boolean hasNext() {
            return false;
        }

        public F next() {
            return null;
        }
    };
}
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.