2

I'm trying to do a stream of updates on a HandlerThread using the class below, but I have a couple of questions about how variable capture works in Java.

[1] Is ret captured from the enclosing scope by reference?

[2] Does this refer to the Runnable, or is it captured from the enclosing scope?

[bonus] StartStream should post a Runnable to the handler thread, and only return when the Runnable has completed. Will the code below work as expected?

public class Stream extends HandlerThread {
    Handler handler = null;

    Stream() {
        super("Stream");
        handler = new Handler(getLooper());
        start();
    }

    private int _startStream() { // Start some repeating update
        return 1;
    }

    public int StartStream() {
        int ret = -1;

        handler.post(new Runnable(){
            @Override public void run() {
                synchronized(this) {
                    ret = _startStream();    // [1]
                    this.notify();           // [2]
                }
            }
        });

        synchronized(this) {
            while(ret == -1) {
                try {
                    this.wait();
                }
                catch (InterruptedException e){}
            }
        }

        return ret;
    }
}
CuriousGeorge
  • 7,120
  • 6
  • 42
  • 74
  • 2
    why are you extending `HandlerThread` ? – pskink Apr 17 '16 at 18:06
  • because `HandlerThread` is designed just to be used directly – pskink Apr 17 '16 at 18:51
  • the guys who wrote it – pskink Apr 17 '16 at 19:37
  • The class is not marked as final, and the source contains comments about what to do if one were to extend the class...So you'll have to forgive me for disregarding your comment. – CuriousGeorge Apr 17 '16 at 20:20
  • sure it is not final, an experienced user can extend it and overwrite `onLooperPrepared` for example, sure they override other methods too, but not in a way you are doing, see: http://androidxref.com/6.0.1_r10/search?q=HandlerThread&defs=&refs=&path=&hist=&project=frameworks, more than 9 / 10 of cases is a direct use of `HandlerThread` – pskink Apr 18 '16 at 06:56
  • Still waiting for a reference to some docs that support what you're saying, or any real practical reason to justify your statement that what I'm doing is bad....I have faith in you! ;) – CuriousGeorge Apr 19 '16 at 00:41
  • i think the problem is that you really dont know what you want to do: 1) your original code doesnt compile 2) you dont know what `this` means in `this.notify()` inside `StartStream` method 3) even if you corrected the above problems your code crashes immediately in `Stream` constructor in `new Handler(getLooper())` since `getLooper()` is null, so what actually is your goal? what do you want to achieve? do you want some code to be run in a `HandlerThread`'s background thread and then be notified in UI thread? – pskink Apr 19 '16 at 05:18
  • @pstink Faith, gone. – CuriousGeorge Apr 19 '16 at 16:17
  • in science the faith is not a good adviser, and i am not a good evangelist to keep your faith, sorry... – pskink Apr 19 '16 at 16:31

2 Answers2

3

Inner classes have implicit references to outer class.

To use ret in anonymous inner class it should be final. The reason local variables cannot reference as non-final is because the local class instance can remain in memory after the method returns. It also depends on java version. Still it should be "effectively final" or move it to a member variable.

this refers to the Runnable, you should use Stream.this for enclosing one.

Maxim G
  • 1,479
  • 1
  • 15
  • 23
  • I don't understand what you mean about 'ret'. I need to set a variable/flag in the Runnable's enclosing scope. How does making 'ret' final/read-only achieve that? – CuriousGeorge Apr 17 '16 at 18:18
  • Do you mean that local variables are automatically captured as final as a safety mechanism since they may go out of scope? – CuriousGeorge Apr 17 '16 at 18:21
  • You should add final to satisfy compiler – Maxim G Apr 17 '16 at 18:22
  • But (final == readonly) right? So how will the while() loop below know when the variable has changed? – CuriousGeorge Apr 17 '16 at 18:23
  • The reason local variables cannot reference as non-final is because the local class instance can remain in memory after the method returns. – Maxim G Apr 17 '16 at 18:24
  • Ok, I see what you're saying. So I guess I just have to make 'ret' a member variable then. Thanks! – CuriousGeorge Apr 17 '16 at 18:26
  • Seems, the question is for interview))) It also depends on [java version](https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html). Still it should be "effectively final" or move it to a member variable. – Maxim G Apr 17 '16 at 18:28
  • Not for interview. I've just removed the details of my actual implementation for simplicity. I've been doing some reading though, and it appears I can just do this: **int[] ret = new int[1]; ret[0] = -1; etc...** and then, since it's now a reference/gc type, the outer scope will see the changes. – CuriousGeorge Apr 17 '16 at 18:36
  • @bitwise This solution is not guaranteed to be thread safe by the Java memory model, see [this answer](http://stackoverflow.com/a/4732586/3888450). The waiting thread might see a cached version of the array. Instead, use an [`AtomicInteger`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html). – Stefan Dollase Apr 17 '16 at 18:43
  • @StefanDollase I was indeed worried about this. I'll use AtomicInteger then. Thanks. – CuriousGeorge Apr 17 '16 at 18:48
  • @MaximG There is no technical reason why Java requires these variables to be effectively final. In fact, other languages and even JVM languages don't have this requirement. It is simply a language design decision to keep the code easier to follow. – Stefan Dollase Apr 17 '16 at 18:54
  • The inner class isn’t using the local variable, but a copy. So there is a possibility of unexpected data synchronization problems. Yeah, we can say it is a language design decision. – Maxim G Apr 17 '16 at 19:02
1

ret is a local variable and thus needs to be effectively final. This means, the compiler complains, if ret is assigned to another value, after it was initialized. Thus [1] results in a compiler error.

This is because the Java language architects want to prevent local variables (variables that are declared in a method) to be changed from somewhere else than the method that declares it.

For more information about variable capture from the enclosing method:

this does indeed refer to the Runnable instance. However, you can use Stream.this to refer to the enclosing Stream instance.

Community
  • 1
  • 1
Stefan Dollase
  • 4,530
  • 3
  • 27
  • 51