Runnable with a Parameter

Runnable with a parameter?

Well it's been almost 9 years since I originally posted this and to be honest, Java has made a couple improvements since then. I'll leave my original answer below, but there's no need for people to do what is in it. 9 years ago, during code review I would have questioned why they did it and maybe approved it, maybe not. With modern lambdas available, it's irresponsible to have such a highly voted answer recommending an antiquated approach (that, in all fairness, was dubious to begin with...) In modern Java, that code review would be immediately rejected, and this would be suggested:

void foo(final String str) {
Thread t = new Thread(() -> someFunc(str));
t.start();
}

As before, details like handling that thread in a meaningful way is left as an exercise to the reader. But to put it bluntly, if you're afraid of using lambdas, you should be even more afraid of multi-threaded systems.

Original answer, just because:

You can declare a class right in the method

void Foo(String str) {
class OneShotTask implements Runnable {
String str;
OneShotTask(String s) { str = s; }
public void run() {
someFunc(str);
}
}
Thread t = new Thread(new OneShotTask(str));
t.start();
}

Is there a way to pass parameters to a Runnable?

Simply a class that implements Runnable with constructor that accepts the parameter can do,

public class MyRunnable implements Runnable {
private Data data;
public MyRunnable(Data _data) {
this.data = _data;
}

@override
public void run() {
...
}
}

You can just create an instance of the Runnable class with parameterized constructor.

MyRunnable obj = new MyRunnable(data);
handler.post(obj);

Parametrise a Runnable object at runtime

I suggest you change doSomething to a Consumer that accepts your parameters:

public void foo(ScheduledExecutorService execService) {
Consumer<YourParams> doSomething = (params) -> {
/*Code that I DON’T want to duplicate*/
/* small piece of code that I need to parametrise */
// use params
};

// after someDelayInSeconds doSomething.run() will be called
YourParams asyncParams = /* parameters for async execution */;
execService.schedule(() -> doSomething.accept(asyncParams), someDelayInSeconds, TimeUnit.SECONDS);

// this might or might not call doSomething.run()
bar(doSomething);

}

private void bar(Consumer<YourParams> doSomething) {
if (/* some conditions are met */) {doSomething.accept(otherParams);}
}

In the scheduled execution you then transform doSomething into a Runnable by passing the default parameters for asynchronous execution, while in bar() you pass the alternative parameters of your choice directly.

How can I pass a parameter to a Java Thread?

You need to pass the parameter in the constructor to the Runnable object:

public class MyRunnable implements Runnable {

public MyRunnable(Object parameter) {
// store parameter for later user
}

public void run() {
}
}

and invoke it thus:

Runnable r = new MyRunnable(param_value);
new Thread(r).start();

Java: Parameterized Runnable

Typically you would implement Runnable or Callable with a class that supports a generic input parameter; e.g.

public class MyRunnable<T> implements Runnable {
private final T t;

public MyRunnable(T t) {
this.t = t;
}

public void run() {
// Reference t.
}
}

Parameterized Runnable with function parameter

You can define an interface called TaskExecutor as follows :

public interface TaskExecutor {
public void execute(String log)
}

You can then modify your ParameterizedTask class as follows :

public static class ParameterizedTask implements Runnable {
private String log;
//other parameters

private TaskExecutor taskExecutor;

public ParameterizedTask(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}

@Override
public void run() {
taskExecutor.execute(log);
}
}

You can then define different behavior to be executed inside the run method as follows :

ParameterizedTask task = new ParameterizedTask(new TaskExecutor() { 
public void execute(String log) {
//doSomething with the log
}
}
);

Passing parameters to public void run() from the parent

Basically, you need to think in terms of passing the parameter to the Runnable object, not to the run() method.

If you make the anonymous class an inner class, it becomes pretty clear:

@Override
public void run() {

String tmpVar;

// ... (Some code)
// ... (Some code)
// ... (Some code)

Platform.runLater(new Updater(tmpVar));

// ... (Some code)
// ... (Some code)
// ... (Some code)
}

// ...

public static class Updater implements Runnable {

private final String var ;

public Updater(String var) {
this.var = var ;
}

@Override
public void run() {

// Access var here

for (int i=0; i<2000;i++){
MyMainClass.leftPaneTextArea.appendText("Goodi!\n");
}

}
}

Now, if tmpVar is final, or "effectively final1", then it will work with your anonymous inner class, and is basically translated to exactly the same thing as the inner class above (in other words, the anonymous inner class gets an implicit field which is populated with the value of the final variable):

@Override
public void run() {

final String tmpVar = ...;

// ... (Some code)
// ... (Some code)
// ... (Some code)

Platform.runLater(new Runnable() {
@Override public void run() {

// access tmpVar here:

for (int i=0; i<2000;i++)
{
MyMainClass.leftPaneTextArea.appendText("Goodi!\n");
}

}
});

// ... (Some code)
// ... (Some code)
// ... (Some code)
}

The language designers could actually have made this work with non-final variables, but decided that the results would have been too confusing. What would happen would be that it would have been translated to the same inner class seen above: in other words the current value of tmpVar would have been implicitly passed to a field in the anonymous inner class. This would be a completely new variable, with a different scope, to the one it appears you are accessing, and its value would be a "snapshot" of the value of tmpVar at the time the anonymous inner class is created. Having what appears to be one variable that is actually referring to two different variables potentially with different values was deemed too confusing and bug-prone.

However, if tmpVar is not final (or effectively final): i.e. you are going to assign it values multiple times, you can explicitly "snapshot" the value of the variable:

@Override
public void run() {

String tmpVar ;

// ... (Some code)
// ... (Some code)
// ... (Some code)

final String varCopy = tmpVar ;

Platform.runLater(new Runnable() {
@Override public void run() {

// access varCopy here:

for (int i=0; i<2000;i++)
{
MyMainClass.leftPaneTextArea.appendText("Goodi!\n");
}

}
});

// ... (Some code)
// ... (Some code)
// ... (Some code)
}

(1) "Effectively final" means that the variable is assigned a value exactly once. Equivalently, it means you could declare it final without creating any compilation errors.

Java Runnable as argument evaluation

I found a working solution for this problem, perhaps trivial for those coming from functional programming.

Accordingly to the example in last edit ([EDIT2])

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class TestEvaluation
{
public static void main(String[] args) throws InterruptedException
{
Middle middle = new Middle();

middle.register(new Consumer<Values>() {
@Override
public void accept(Values values) {
System.out.println("a is: " + values.getA());
System.out.println("b is: " + values.getB());
}
});

Thread.sleep(2000);
}

static class Values
{
private int a = 0;
private int b = 0;

public int getA() {
return a;
}

public void setA(int a) {
this.a = a;
}

public int getB() {
return b;
}

public void setB(int b) {
this.b = b;
}
}

static class Middle
{
private ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);

public void register(Consumer<Values> passed)
{
Consumer<Values> middleConsumer = new Consumer<Values>() {
@Override
public void accept(Values values) {
System.out.println("Middle");
passed.accept(values);
}
};

Leaf leaf = new Leaf(middleConsumer);
pool.schedule(leaf, 1, TimeUnit.SECONDS);
}
}

static class Leaf implements Runnable
{
public Consumer<Values> task;

public Leaf(Consumer<Values> task)
{
this.task = task;
}

@Override
public void run()
{
Values values = new Values();
values.setA(5);
values.setB(5);
System.out.println("Leaf");
task.accept(values);
}
}
}

This code produces the behavior i want.
Hope this will help someone.
Cheers!



Related Topics



Leave a reply



Submit