2015-05-04 14 views
21

Sửa đổi một biến địa phương trong forEach đưa ra một lỗi biên dịch:Sửa đổi biến địa phương từ bên lambda

Bình thường

int ordinal = 0; 
    for (Example s : list) { 
     s.setOrdinal(ordinal); 
     ordinal++; 
    } 

Với Lambda

int ordinal = 0; 
    list.forEach(s -> { 
     s.setOrdinal(ordinal); 
     ordinal++; 
    }); 

Bất kỳ ý tưởng làm thế nào để giải quyết điều này?

+0

lỗi biên dịch là gì? Vui lòng bao gồm điều đó trong câu hỏi của bạn và xem liệu bạn có thể thu hẹp mã ví dụ của mình xuống để bạn có thể bao gồm định nghĩa lớp hoàn chỉnh hay ít nhất là định nghĩa phương thức hoàn chỉnh, sao chép lỗi đó. –

+3

Xem xét lambdas về cơ bản là cú pháp đường cho một lớp bên trong vô danh, trực giác của tôi là không thể nắm bắt một biến cục bộ không phải là cuối cùng. Tôi rất muốn được chứng minh là sai. – Sinkingpoint

+2

Một biến được sử dụng trong một biểu thức lambda phải có hiệu quả cuối cùng. Bạn có thể sử dụng một số nguyên nguyên tử mặc dù nó quá mức cần thiết, do đó, một biểu thức lambda không thực sự cần thiết ở đây. Chỉ cần gắn bó với vòng lặp. –

Trả lời

38

Sử dụng một wrapper

Với Java 8 +:

AtomicInteger ordinal = new AtomicInteger(0); 
list.forEach(s -> { 
    s.setOrdinal(ordinal.getAndIncrement()); 
}); 

Với Java 10+:

var wrapper = new Object(){ int ordinal = 0; }; 
list.forEach(s -> { 
    s.setOrdinal(wrapper.ordinal++); 
}); 
+0

java 10+? Điều đó có tồn tại không? – Eduardo

+0

@Eduardo [yes] (http://openjdk.java.net/jeps/286), ở dạng xem trước. –

5

này khá gần với một XY problem. Đó là, câu hỏi được hỏi về cơ bản là làm thế nào để biến đổi một biến địa phương đã capture từ một lambda. Nhưng nhiệm vụ thực tế trong tay là làm thế nào để đánh số các yếu tố của một danh sách.

Theo kinh nghiệm của tôi, có tới 80% thời gian có câu hỏi làm thế nào để biến đổi một địa phương bị bắt từ bên trong một lambda, có một cách tốt hơn để tiếp tục. Thường này liên quan đến giảm, nhưng trong trường hợp này các kỹ thuật chạy một dòng suối trên các chỉ số danh sách áp dụng tốt:

IntStream.range(0, list.size()) 
     .forEach(i -> list.get(i).setOrdinal(i)); 
+0

Giải pháp tốt nhưng chỉ khi 'danh sách' là danh sách 'RandomAccess' – ZhekaKozlov

5

Bạn có thể làm điều đó với một lớp ẩn danh thông thường thay vì một lambda:

list.forEach(new Consumer<Example>() { 
    int ordinal = 0; 
    public void accept(Example s) { 
     s.setOrdinal(ordinal); 
     ordinal++; 
    } 
}); 
0

một thay thế cho AtomicInteger (hoặc bất kỳ vật thể nào có thể lưu trữ một giá trị) là sử dụng một mảng:

final int ordinal[] = new int[ 1 ]; 
list.forEach (s -> s.setOrdinal (ordinal[ 0 ]++)); 

Nhưng thấy the Stuart's answer: có thể có một wa tốt hơn y để đối phó với trường hợp của bạn.

0

Bạn có thể kết hợp nó để giải quyết trình biên dịch nhưng hãy nhớ rằng các tác dụng phụ trong lambdas không được khuyến khích.

Để trích dẫn javadoc

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care

0

Tôi đã có một vấn đề hơi khác nhau. Thay vì tăng một biến cục bộ trong forEach, tôi cần gán một đối tượng cho biến cục bộ.

Tôi đã giải quyết điều này bằng cách xác định một lớp miền bên trong riêng tư bao bọc cả danh sách mà tôi muốn lặp lại (countryList) và kết quả đầu ra mà tôi hy vọng nhận được từ danh sách đó (foundCountry). Sau đó, sử dụng Java 8 "forEach", tôi lặp qua trường danh sách và khi đối tượng tôi muốn được tìm thấy, tôi gán đối tượng đó cho trường đầu ra.Vì vậy, điều này gán một giá trị cho một trường của biến cục bộ, không thay đổi biến cục bộ. Tôi tin rằng vì biến cục bộ không bị thay đổi, trình biên dịch không phàn nàn. Sau đó tôi có thể sử dụng giá trị mà tôi đã chụp trong trường đầu ra, bên ngoài danh sách.

miền Object:

public class Country { 

    private int id; 
    private String countryName; 

    public Country(int id, String countryName){ 
     this.id = id; 
     this.countryName = countryName; 
    } 

    public int getId() { 
     return id; 
    } 

    public void setId(int id) { 
     this.id = id; 
    } 

    public String getCountryName() { 
     return countryName; 
    } 

    public void setCountryName(String countryName) { 
     this.countryName = countryName; 
    } 
} 

đối tượng Wrapper:

private class CountryFound{ 
    private final List<Country> countryList; 
    private Country foundCountry; 
    public CountryFound(List<Country> countryList, Country foundCountry){ 
     this.countryList = countryList; 
     this.foundCountry = foundCountry; 
    } 
    public List<Country> getCountryList() { 
     return countryList; 
    } 
    public void setCountryList(List<Country> countryList) { 
     this.countryList = countryList; 
    } 
    public Country getFoundCountry() { 
     return foundCountry; 
    } 
    public void setFoundCountry(Country foundCountry) { 
     this.foundCountry = foundCountry; 
    } 
} 

Lặp hoạt động:

int id = 5; 
CountryFound countryFound = new CountryFound(countryList, null); 
countryFound.getCountryList().forEach(c -> { 
    if(c.getId() == id){ 
     countryFound.setFoundCountry(c); 
    } 
}); 
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName()); 

Bạn có thể loại bỏ các phương pháp lớp wrapper "setCountryList()" và làm cho lĩnh vực này "countryList" cuối cùng, nhưng tôi đã không nhận được lỗi biên dịch để lại những chi tiết này.

1

Nếu bạn đang ở trên Java 10, bạn có thể sử dụng var cho rằng:

var ordinal = new Object() { int value; }; 
list.forEach(s -> { 
    s.setOrdinal(ordinal.value); 
    ordinal.value++; 
}); 
Các vấn đề liên quan