带参数的Runnable?

211

我需要一个“接受参数的可运行体”,尽管我知道这样的可运行体并不存在。

这可能指向我的应用程序设计上的根本性缺陷和/或我疲惫的大脑中的心理障碍,因此我希望在这里找到一些关于如何实现以下内容的建议,而不违反基本面向对象原则:

  private Runnable mOneShotTask = new Runnable(String str) {
    public void run(String str) {
       someFunc(str);
    }
  };  

有什么想法可以实现类似上面的功能吗?


21
现在您可以使用Consumer<T> - Alex78191
2
我已经阅读了这个问题的各种答案。奇怪的是,没有人告诉你可以通过添加适当的接口将所需的Runnables(带有一个、两个、三个或更多参数)添加到项目中。我在这里创建了一个有注释的Gist,供有兴趣的人参考:https://gist.github.com/jsfan3/3a66e711fd0fd233c5e4c467184adb7a - Francesco Galgani
这不是“如何向Java线程传递参数”的重复。现代的答案就像@Alex78191所说:使用Consumer<T> - Epaga
我通常创建一个实现run()方法的类,并将所需的任何数据传递到构造函数或访问器/设置器中。 - musterjunk
1
@Alex78191,你能进一步阐述一下使用Consumer<T>的意思吗? - experiment unit 1998X
@experimentunit1998X https://dev59.com/0G025IYBdhLWcg3wsIIU#59660705 - Alex78191
10个回答

257

距离我最初发布这篇文章已经将近9年了,老实说,Java自那以后已经有了一些改进。我会保留我的原始答案,但是现在没有必要再按照它的方式去做了。9年前,在代码审查期间,我可能会质疑他们为什么这样做,也许会批准,也许不会。随着现代lambda的出现,推荐一种过时的方法(公正地说,一开始就很可疑...)的高票答案是不负责任的。在现代Java中,那个代码审查将被立即拒绝,并建议使用以下方法:

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

与之前一样,如何有意义地处理该线程的细节留给读者自己去练习。但是坦白说,如果你害怕使用lambda表达式,那么你应该更加害怕多线程系统。

以下是原始答案:

您可以在方法中声明一个类。

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();
}

谢谢大家!所有建议的解决方案都指向了同一种方法,但我只能接受一个。我一定很累,无法自己想出这个方法。对所有人点赞。 - uTubeFan
21
实际上,大多数人并不知道你可以在方法内声明一个类。有些人会认为这是一种不好的编码风格,但我想这只是一个品味问题 :) - corsiKa
3
公共接口 ResultRunnable<T> { public void run(T result); } 该接口定义了一个名为 "run" 的方法,该方法有一个参数类型为 T 的参数 "result",可以在实现此接口的类中使用。 - Roman M
我不太理解更现代的方法。好吧,现在没有Runnable了。有什么改变可以在Scheduler中使用它吗?例如:Scheduling scheduling = new Scheduling(1); scheduling.startScheduler(new MyRunnable(), TimeUnit.DAYS, 1); - BairDev

52

你可以将它放在一个函数中。

String paramStr = "a parameter";
Runnable myRunnable = createRunnable(paramStr);

private Runnable createRunnable(final String paramStr){

    Runnable aRunnable = new Runnable(){
        public void run(){
            someFunc(paramStr);
        }
    };

    return aRunnable;

}

当我使用它时,我的参数是一个整数ID,我用它来创建一个ID--> myRunnables的哈希映射表。这样,我可以使用哈希映射表在处理程序中发布/删除不同的myRunnable对象。


4
谢谢分享代码-我很喜欢人们这么做而不是只是空谈。有一个问题-在涉及内存泄漏时,上述方法是否可行?您传递的所有引用都会被正确处理吗? - nikib3ro
2
@kape123 答案是“这取决于情况”。只要方法返回的 Runnable 对象存在于任何地方,paramStr 可能就不会被垃圾回收。如果对象存在但无法再次运行,JIT(甚至 javac)可能会决定将其从作用域中删除,但我们不应该依赖这样的优化,因为它们可能会在未来发生变化。 - corsiKa

35
theView.post(new Runnable() {
    String str;
    @Override                            
    public void run() {
        par.Log(str);                              
    }
    public Runnable init(String pstr) {
        this.str=pstr;
        return(this);
    }
}.init(str));
创建一个初始化函数,该函数返回对象本身并用它来初始化参数。

3
很遗憾,您将无法将其分配给一个变量并调用init()。 - Andrew Glukhoff

23

如果仍在Java 8中使用带有lambda的Runnable,我们可以按如下方式操作: void doSomething(String param) { CompletableFuture.runAsync(() -> howToDo(param));} private void howToDo(String param) { System.out.println(param);} - Lampard

12

我使用下面的类来实现Runnable接口。 使用这个类,你可以很容易地创建带参数的新线程。

public abstract class RunnableArg implements Runnable {

    Object[] m_args;

    public RunnableArg() {
    }

    public void run(Object... args) {
        setArgs(args);
        run();
    }

    public void setArgs(Object... args) {
        m_args = args;
    }

    public int getArgCount() {
        return m_args == null ? 0 : m_args.length;
    }

    public Object[] getArgs() {
        return m_args;
    }
}

2
你会如何使用这个抽象类来运行一个带参数的可运行对象? - EZDsIt
我更喜欢使用带参数的构造函数:public RunnableArg(Object... args) { setArgs(args); }然后描述本地类:class ActionArg extends RunnableArg { public ActionArg(Object... arg) { super(arg); } @Override public void run() { /* getArgs() and process them */ } }并使用它:Thread t = new Thread(new ActionArg( %param_object(s)% )); t.start(); - GSD.Aaz
如果您正在为API < 24进行开发,则这是完美的解决方案!我选择了这个解决方案而不是Consumer<T>解决方案,因为后者仅适用于Java 8(API> = 24)。谢谢! - ONE

10

你有两个选择:

  1. 定义一个命名类。将你的参数传递给该命名类的构造函数。

  2. 让你的匿名类封闭你的“参数”。确保将其标记为final


@Massa:请参考https://dev59.com/dXVC5IYBdhLWcg3whRgw - setzamora

5

我首先想知道你在这里试图达成什么目标,需要传递参数给new Runnable()或run()方法。 通常的做法应该是创建一个Runnable对象,在启动之前通过设置成员变量来向其线程传递数据(str)。然后run()方法使用这些成员变量的值来执行someFunc()。


1
/**
 * @author AbdelWadoud Rasmi
 * <p>
 * The goal of this class is to pass some parameters to a runnable instance, a good example is
 * after caching a file you need to pass the new path to user to do some work on it.
 */
public abstract class ParameterizedRunnable implements Runnable {
    private Object[] params;

    /**
     * @param params: parameters you want to pass the the runnable.
     */
    public ParameterizedRunnable(Object... params) {
        this.params = params;
    }

    /**
     * Code you want to run
     *
     * @param params:parameters you want to pass the the runnable.
     */
    protected abstract void run(Object... params);

    @Override
    public final void run() {
        run(params);
    }

    /**
     * setting params
     */
    public void setParams(Object... params) {
        this.params = params;
    }

    /**
     * getting params
     */
    public Object[] getParams() {
        return params;
    }
}

这是最实用和最简单的方法,您只需要创建一个继承Runnable的类,该类具有参数(getter/setter)和另一个run方法,原始重写的run方法调用您定义的方法(保存您的参数的方法)。 - Abdel Wadoud
干净利落。 - Igor Vuković
不用谢,希望我能够帮到你。 - Abdel Wadoud

1

目前为止最好的方法:

Consumer<String> oneShot = str -> {
    
   somefunc(str);
    
};

oneShot.accept("myString");

0
创建一个实现了Runnable接口的新类。在这个类中,你可以通过定义自己选择的抽象方法来扩展run()方法。
public abstract class CCallback implements Runnable {
@Override
public void run() {}

public abstract void run(Object any);

}

现在你可以像这样使用这个类作为回调函数:

public void whatever(CCallback callback) {
    String any = "any type of param";
    if (callback != null)
        callback.run(any);
}

"

无论什么"方法现在可以在我们的可运行程序运行之前完成其任务:

"
whatever(new CCallback() {
    @Override
    public void run(Object any) {
        // Here you can reach "any"
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接