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};
{
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.asList
khô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.asList
khô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.asList
khô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.asList
khô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);
Iterator<Integer> example = iterator;
while (iterator.hasNext()) {
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
.