M. Ather Khan
M. Ather Khan

Reputation: 335

Why calling class function from a thread works in Java?

So I got this weird scenario that works fine but doesn't make any sense as to why it works. From my experience in C++, I'm very sure that this will not work at all and will throw an error during compilation.

public class Practice {

    private void testFunction() {
        System.out.println("working fine");
        System.out.println("testFunc: " + this);
    }

    public void start() {
        System.out.println("start: " + this);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("run: " + this);
                testFunction();
            }
        }).start();
    }
}

// Inside main
Practice practice = new Practice();
practice.start()

Output

start: com.company.Practice@5e2de80c
run: com.company.Practice$1@6720b744
working fine
testFunc: com.company.Practice@5e2de80c

WHY!? why did this work? How am I able to call testFunction() from a Runnable? Shouldn't I create a new instance and then call that function like Practice p = new Practice(); p.testFunction()? How does Java know that testFunction() is part of Practice class and not Runnable?

And also, how come the value of this in testFunction() is same as start()? Shouldn't it be same as run()?

Upvotes: 1

Views: 82

Answers (2)

Sweeper
Sweeper

Reputation: 272895

You can access testFunction is because you are using the simple name of testFunction. If instead you did:

this.testFunction();

Then you wouldn't be able to access it.

When you call a method with its simple name, everything that is in scope is considered to resolve the method. (See JLS 6.5.7.1) testFunction is definitely in scope inside the Runnable, because it is inside Practice.

To call testFunction, you still need an instance of Practice, right? Well, the compiler generates some code that passes the instance of Practice on which start is invoked (i.e. what this means in start) to the instance of Runnable that you are creating. Specifically, it creates a non-static inner class

class Practice {
    public void start() {
        ...
        new Thread(new SomeGeneratedRunnable()).start();
    }
    // not the actual name of the class. The actual name is Practice$1
    private class SomeGeneratedRunnable implements Runnable {
        public void run() {
            System.out.println("run: " + this);
            testFunction();
        }
    }
}

You can think of this as:

class Practice {
    public void start() {
        ...
        new Thread(new SomeGeneratedRunnable(this)).start();
    }
}

class SomeGeneratedRunnable implements Runnable {
    private final Practice outer;

    public SomeGeneratedRunnable(Practice outer) {
        this.outer = outer;
    }

    public void run() {
        System.out.println("run: " + this);
        outer.testFunction();
    }
}

Now you should see why this inside testFunction can't possibly be the same as the this inside the Runnable. The this inside Runnable is an instance of SomeGeneratedRunnable (or Practice$1), but this inside testFunction is an instance of Practice.

Upvotes: 1

Thilo
Thilo

Reputation: 262734

You have an "inner class" here.

Instances of the inner class (your anonymous subclass of Runnable) will contain references to the containing outer classes they were created from (here an instance of Practice) and can call methods on those outer instances.

Your testFunction(); gets compiled to something like this.$myOuterInstance.testFunction();

How does Java know that testFunction() is part of Practice class and not Runnable?

The compiler resolves this by first looking at the inner class then going to the outside scope, which not only includes the containing class, but for example also any effectively final local variables you might have in that start method.

This is similar to how variables and fields are resolved as well (with some shadowing rules when you have name conflicts).

And also, how come the value of this in testFunction() is same as start()? Shouldn't it be same as run()?

No. testFunction is invoked on the instance of Practice (as is start).

run is invoked on the instance of your Runnable Practice$1.

Upvotes: 2

Related Questions