Modifying local variable from inside lambda

后端 未结 11 1208
失恋的感觉
失恋的感觉 2020-11-28 04:30

Modifying a local variable in forEach gives a compile error:

Normal

    int ordinal = 0;
    for (Example s : list) {
          


        
相关标签:
11条回答
  • 2020-11-28 04:55

    An alternative to AtomicInteger (or any other object able to store a value) is to use an array:

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

    But see the Stuart's answer: there might be a better way to deal with your case.

    0 讨论(0)
  • 2020-11-28 04:57

    I know that's an old question, but if you are comfortable with a workaround, considering the fact that the external variable should be final, you can simply do this:

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

    Maybe not the most elegant, or even the most correct, but it will do.

    0 讨论(0)
  • 2020-11-28 05:03

    This is fairly close to an XY problem. That is, the question being asked is essentially how to mutate a captured local variable from a lambda. But the actual task at hand is how to number the elements of a list.

    In my experience, upward of 80% of the time there is a question of how to mutate a captured local from within a lambda, there's a better way to proceed. Usually this involves reduction, but in this case the technique of running a stream over the list indexes applies well:

    IntStream.range(0, list.size())
             .forEach(i -> list.get(i).setOrdinal(i));
    
    0 讨论(0)
  • 2020-11-28 05:05

    Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it. Lambdas have been made for functional style of programming and this means: No side effects. What you want to do is considered bad style. It is also dangerous in case of parallel streams.

    You should either find a solution without side effects or use a traditional for loop.

    0 讨论(0)
  • 2020-11-28 05:08

    I had a slightly different problem. Instead of incrementing a local variable in the forEach, I needed to assign an object to the local variable.

    I solved this by defining a private inner domain class that wraps both the list I want to iterate over (countryList) and the output I hope to get from that list (foundCountry). Then using Java 8 "forEach", I iterate over the list field, and when the object I want is found, I assign that object to the output field. So this assigns a value to a field of the local variable, not changing the local variable itself. I believe that since the local variable itself is not changed, the compiler doesn't complain. I can then use the value that I captured in the output field, outside of the list.

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

    Wrapper object:

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

    Iterate operation:

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

    You could remove the wrapper class method "setCountryList()" and make the field "countryList" final, but I did not get compilation errors leaving these details as-is.

    0 讨论(0)
提交回复
热议问题