12

I'm trying to refactor this code to use a lambda instead of anonymous class. It's a simple list of items in a GUI. I register a different listener to each item, and the last item created does something special when clicked.

class ItemList {
    interface OnClickListener {
        void onClick();
    }
    OnClickListener current;
    OnClickListener newListener(final int n) {
        return current = new OnClickListener() {
            public void onClick() {
                if (this == current)
                    System.out.println("doing something special (item #"+n+")");
                System.out.println("selected item #" + n);
            }
        };
    }
    public static void main(String[] args) {
        ItemList l = new ItemList();
        OnClickListener ocl1 = l.newListener(1);
        OnClickListener ocl2 = l.newListener(2);
        OnClickListener ocl3 = l.newListener(3);
        ocl1.onClick();
        ocl2.onClick();
        ocl3.onClick();
    }
}

It works as expected:

$ javac ItemList.java && java ItemList
selected item #1
selected item #2
doing something special (item #3)
selected item #3

Now I change it to use a lambda instead of anonymous class:

class ItemList {
    interface OnClickListener {
        void onClick();
    }
    OnClickListener current;
    OnClickListener newListener(final int n) {
        return current = () -> {
            if (this == current)
                System.out.println("doing something special (item #"+n+")");
            System.out.println("selected item #" + n);
        };
    }
    public static void main(String[] args) {
        ItemList l = new ItemList();
        OnClickListener ocl1 = l.newListener(1);
        OnClickListener ocl2 = l.newListener(2);
        OnClickListener ocl3 = l.newListener(3);
        ocl1.onClick();
        ocl2.onClick();
        ocl3.onClick();
    }
}

But now it no longer does anything special on the last item? Why? Does == work differently with lambdas? I thought it was a bug in retrolambda at first, but this example is running on plain JDK8 and it still happens.

$ javac A.java && java A
selected item #1
selected item #2
selected item #3
Dog
  • 7,707
  • 8
  • 40
  • 74

1 Answers1

13

this inside lambda doesn't mean the same as this inside an anonymous class instance.

Inside a lambda it refers to the enclosing class.

...the lambda expression does not introduce a new level of scoping. Consequently, you can directly access fields, methods, and local variables of the enclosing scope. ... To access variables in the enclosing class, use the keyword this. ...

Inside an instance of an anonymous class, it refers to the current object

Within an instance method or a constructor, this is a reference to the current object — the object whose method or constructor is being called

That's why in the lambda expression, this == current is never true, since it compares an instance of class ItemList with a lambda expression of type OnClickListener.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Still it's not clear why current==this is never true. Since enclosing class is the same, we should see three "something" in the output. – Oleg Gryb Jul 15 '14 at 18:32
  • @OlegGryb The enclosing class is ItemList, so it's never equal to current (which is the lambda expression of type OnClickListener). – Eran Jul 15 '14 at 18:37
  • You're right, didn't think it through, so current.onClick() should still work if I want to use it this way. – Oleg Gryb Jul 15 '14 at 18:42
  • @OlegGryb I'm not sure what you are trying to do with current.onClick(). – Eran Jul 15 '14 at 18:52
  • I was saying that even though 'this' doesn't reference the class created by lambda, I can still reference this class outside and call a method from this class if I want to. It's not an argument - I was just thinking loudly. – Oleg Gryb Jul 15 '14 at 19:06
  • If the `==` is really on an `ItemList` and `OnClickListener`, shouldn't that be a compile error? – Dog Jul 15 '14 at 21:52
  • 3
    @Dog and Eran, no, you can't compare any two objects with `==`; try comparing two instances of unrelated concrete classes for example. But you **can** generally compare two references if at least one of them is an interface, even if one of them is `this`. The reason is that the class reference might be an instance of a subclass that happens to implement the interface type of the reference being compared. – Stuart Marks Jul 15 '14 at 23:23
  • @Stuart Marks Thanks for the clarification. I stand corrected – Eran Jul 15 '14 at 23:27
  • 1
    @Eran I should say that I was also initially confused by why this wasn't a compile-time error, and it took some digging and some thinking to understand why it wasn't. It's quite subtle. – Stuart Marks Jul 16 '14 at 00:23