如何使用lambda表达式与TimerTask?

26

希望您已经知道在Java 8中可以使用lambda表达式,例如用它来替代匿名方法。

这里可以看到Java 7和Java 8的一个例子:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        checkDirectory();
    }
};

在Java 8中可以用以下两种方式表达:

Runnable runnable = () -> checkDirectory();
或者
Runnable runnable = this::checkDirectory;

这是因为Runnable是一个函数式接口,只有一个(抽象的)公共非默认方法。

然而...... 对于TimerTask,我们有以下内容:

TimerTask timerTask = new TimerTask() {
    @Override
    public void run() {
        checkDirectory();
    }
};

看起来很熟悉,对吗?
然而,使用lambda表达式是行不通的,因为TimerTask是一个抽象类,尽管它只有一个抽象的公共非默认方法,但它不是一个接口,因此也不是函数式接口。
另外,它也无法被重构为带有默认实现的接口,因为它带有状态,那样做是不可行的。

所以我的问题: 是否有任何方法在构建TimerTask时使用lambda表达式?

我想要的是以下内容:

Timer timer = new Timer();
timer.schedule(this::checkDirectory, 0, 1 * 1000);

有没有什么方法可以让它更美观,而不是使用一些丑陋的匿名内部类呢?


10
既然你使用了现代功能,为什么不一路走到底,使用 ScheduledExecutorService 而不是 TimerTask? ;) - fge
2
@fge 嗯,我之前不知道它的存在...现在想想,当旧特性尚未弃用时,Java的API是否没有提到有一种新的类似功能可用呢? - skiwi
Timer的情况下,Josh Bloch的《Effective Java第二版》建议如下。尽管如此,这并不是JDK API的官方立场。 - Marko Topolnik
1
嗯,这确实是他们文档中的一个很大缺陷。同样,在“文件”文档中,他们没有提到“文件”。 - fge
5个回答

14

首先需要注意的是Timer实际上是一个过时的API,但是为了回答您的问题,您可以编写一个小包装器来适应schedule方法接受一个Runnable,然后在内部将该Runnable转换为一个TimerTask。然后您将有一个接受lambda表达式的schedule方法。

public class MyTimer {
  private final Timer t = new Timer();

  public TimerTask schedule(final Runnable r, long delay) {
     final TimerTask task = new TimerTask() { public void run() { r.run(); }};
     t.schedule(task, delay);
     return task;
  }
}

9
从技术角度看,你的答案是正确的,但我个人更喜欢使用ScheduledExecutorService而不是TimerTask - skiwi
2
当前的API是什么? - krivar

1

虽然Marko的回答完全正确,但我更喜欢我的实现方式:

public class FunctionalTimerTask extends TimerTask {

    Runnable task;

    public FunctionalTimerTask(Runnable task) {
        this.task = task;
    }

    @Override
    public void run() {
        task.run();
    }
}

 public static class Task {
    public static TimerTask set(Runnable run) {
        return new FunctionalTimerTask(() -> System.err.println("task"));
    }
}

 Timer timer = new Timer(false);
 timer.schedule(Task.set(() -> doStuff()), TimeUnit.SECONDS.toMillis(1));

这使您对计时器有更多的控制,并且您拥有一个静态实用类。理想情况下,给它一个不会与其他常见线程类冲突的名称,例如不要使用Task、Job或Timer。

1
要完善Marko Topolnik关于Timer的答案,您只需要用lambda调用schedule方法。
schedule(() -> {
    System.out.println("Task #1 is running");
}, 500);

Runnable 是一个接口,你可以使用 lambda 实现它。因此,你可以使用 lambda 调用 schedule 方法。这里有一个可工作的代码片段 https://gist.github.com/fsamin/ec5aa79bc23965eca277 - François SAMIN
1
误解了答案;你当然是完全正确的。 - gerardw

1

我知道这是一篇旧帖子,但为了完整起见,我想包括Flown发布的包装器解决方案:

private static TimerTask wrap(Runnable r) {
  return new TimerTask() {

    @Override
    public void run() {
      r.run();
    }
  };
}

然后你的调用可以变成:
timer.schedule(wrap(this::checkDirectory), delay);

-1
如果你想要的话,你也可以使用Swing API中的lambda轻松创建一个计时器(但不能使用TimerTask): new javax.swing.Timer(1000, (ae) -> this::checkDirectory).start();

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