简体   繁体   中英

Java comparator uses non final variables

I want to sort a list, using a map which contains the values for each item.

Map<Integer, Float> map = new HashMap<>();
List<Integer> list = new ArrayList<>();

map.put(0, 0.0f);
map.put(1, 5.0f);
map.put(2, 2.0f);

list = new ArrayList<>(map.keySet());

Collections.sort(list, new Comparator<Integer>() {
    public int compare(Integer left, Integer right) {
        Float leftCost = map.get(left);
        Float rightCost = map.get(right);
        return leftCost.compareTo(rightCost);
    }
})

I want the order to be 0,2,1 because the value of 1 is higher than 2 . But java dont let me do this. I get the following error: Cannot refer to a non-final variable map inside an inner class defined in a different method

How can I do this a way like that?

Just make it final:

final Map<Integer, Float> map = new HashMap<Integer, Float>();
List<Integer> list = new ArrayList<Integer>(); // this assignment is unncessary [1]

map.put(0, 0.0f);
map.put(1, 5.0f);
map.put(2, 2.0f);

list = new ArrayList<Integer>(map.keySet()); // 1. assignment is replaced here

Collections.sort(list, new Comparator<Integer>() {
    public int compare(Integer left, Integer right) {
        Float leftCost = map.get(left);
        Float rightCost = map.get(right);
        return leftCost.compareTo(rightCost);
    }
})

As your map is mutable, you can still modify it.

An annonymous inner class (your comparator is one) can only refer local variable that are declared final , in order to access your map - you must declare it as final .

Note that declaring it as final does not prevent you from modifying the map object , you just cannot assign a new object to the variable map .

Your Comparator is an anonymous inner class. Inside it, you're trying to access a local variable, map , declared in the method that contains the anonymous inner class.

There's a restriction in Java that you can only do this if the local variable is final . So, make your variable map final :

final Map<Integer, Float> map = new HashMap<>();

In your case the solution is trivial: mark your map as final :

final Map<Integer, Float> map = new HashMap<>();

You are probably confused with word final . It does not limit you in manipulations on your map. It just does not allow you to change reference to the map that is OK in your case.

The reason for this requirement is that your comparator is anonymous inner class. All outer method variables are copied to the of anonymous class, so if changing them in outer method will create conflicts. This is the reason that compiler requires marking the variable accessed from anonymous class as final.

Other solution is to extract your comparator to separate class and send map to it as an argument constructor.

You can definitely do this. Just make a named class in place of your anonymous class extending Comparator and pass in the Map variable as a parameter. Like so:

  @Test
  public void test() {
    Map<Integer, Float> map = new HashMap<Integer, Float>();
    map.put(0, 0.0f);
    map.put(1, 5.0f);
    map.put(2, 2.0f);

    List<Integer> list = new ArrayList<Integer>(map.keySet());
    Collections.sort(list, new FloatComparator(map));

    System.out.println(list);
  }

  class FloatComparator implements Comparator<Integer> {
    private Map<Integer, Float> mapRef;
    public FloatComparator(Map<Integer, Float> newMap) {
      mapRef = newMap;
    }

    @Override
    public int compare(Integer left, Integer right) {
      Float leftCost = mapRef.get(left);
      Float rightCost = mapRef.get(right);
      return leftCost.compareTo(rightCost);
    }
  }

This prints out:

[0, 2, 1]

And also makes your code cleaner to read, IMO.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM