Java 设计问题:强制执行方法调用顺序
Java Design Issue: Enforce method call sequence
最近在面试中有人问我一个问题。
问题: 有一个class 用来分析代码的执行时间。 class 就像:
Class StopWatch {
long startTime;
long stopTime;
void start() {// set startTime}
void stop() { // set stopTime}
long getTime() {// return difference}
}
客户端应创建 StopWatch 的实例并相应地调用方法。用户代码可能会混淆方法的使用,从而导致意外结果。例如,start()、stop() 和 getTime() 调用应该按顺序进行。
这个class必须是"reconfigured",这样可以防止用户弄乱顺序。
如果在 start() 之前调用 stop() 或进行一些 if/else 检查,我建议使用自定义异常,但面试官不满意。
是否有设计模式来处理这些情况?
编辑: class成员和方法实现可以修改。
方法调用顺序不正确时抛出异常是很常见的。例如,Thread
的 start
如果调用两次将抛出 IllegalThreadStateException
。
您可能应该更好地解释实例如何知道方法的调用顺序是否正确。这可以通过引入状态变量并在每个方法开始时检查状态(并在必要时更新它)来完成。
也许他预料到了这个 'reconfiguration' 而问题根本不是关于方法序列的:
class StopWatch {
public static long runWithProfiling(Runnable action) {
startTime = now;
action.run();
return now - startTime;
}
}
我们通常使用来自 Apache Commons StopWatch 的 StopWatch 检查他们提供的模式。
秒表状态错误时抛出IllegalStateException
public void stop()
Stop the stopwatch.
This method ends a new timing session, allowing the time to be retrieved.
Throws:
IllegalStateException - if the StopWatch is not running.
直截了当。
首先,实现一个自己的 Java 分析器是浪费时间,因为好的分析器是可用的(也许这就是问题背后的意图)。
如果你想在编译时强制执行正确的方法顺序,你必须 return 链中的每个方法:
start()
必须使用停止方法 return 一个 WatchStopper
。
- 然后
WatchStopper.stop()
必须 return 使用 getResult()
方法 WatchResult
。
这些助手的外部构造 类 以及访问其方法的其他方式当然必须被阻止。
通过对接口进行微小的更改,您可以使方法序列成为唯一可以调用的方法 - 即使在编译时也是如此!
public class Stopwatch {
public static RunningStopwatch createRunning() {
return new RunningStopwatch();
}
}
public class RunningStopwatch {
private final long startTime;
RunningStopwatch() {
startTime = System.nanoTime();
}
public FinishedStopwatch stop() {
return new FinishedStopwatch(startTime);
}
}
public class FinishedStopwatch {
private final long elapsedTime;
FinishedStopwatch(long startTime) {
elapsedTime = System.nanoTime() - startTime;
}
public long getElapsedNanos() {
return elapsedTime;
}
}
用法很简单 - 每个方法 returns 都是不同的 class,只有当前适用的方法。基本上,秒表的状态被封装在类型系统中。
评论中指出,即使是上面的设计,也可以调用stop()
两次。虽然我认为这是附加值,但理论上可以把自己搞砸。那么,我能想到的唯一方法就是这样:
class Stopwatch {
public static Stopwatch createRunning() {
return new Stopwatch();
}
private final long startTime;
private Stopwatch() {
startTime = System.nanoTime();
}
public long getElapsedNanos() {
return System.nanoTime() - startTime;
}
}
这与省略 stop()
方法的赋值不同,但这也可能是一个不错的设计。然后一切都将取决于精确的要求...
我建议如下:
interface WatchFactory {
Watch startTimer();
}
interface Watch {
long stopTimer();
}
会这样使用
Watch watch = watchFactory.startTimer();
// Do something you want to measure
long timeSpentInMillis = watch.stopTimer();
你不能以错误的顺序调用任何东西。如果你调用 stopTimer
两次你都会得到有意义的结果(也许最好将它重命名为 measure
和 return 每次调用的实际时间)
这也可以在 Java 8 中使用 Lambdas 完成。在这种情况下,您将函数传递给 StopWatch
class,然后告诉 StopWatch
执行那个代码。
Class StopWatch {
long startTime;
long stopTime;
private void start() {// set startTime}
private void stop() { // set stopTime}
void execute(Runnable r){
start();
r.run();
stop();
}
long getTime() {// return difference}
}
再想一想
事后看来,他们似乎在寻找 execute around pattern。它们通常用于执行诸如强制关闭流之类的事情。由于这一行,这也更相关:
Is there a design pattern to handle these kind of situations?
我们的想法是,你给 "executing around" 一些 class 做某事的东西。您可能会使用 Runnable
但这不是必需的。 (Runnable
最有意义,你很快就会明白为什么。) 在你的 StopWatch
class 中添加一些像这样的方法
public long measureAction(Runnable r) {
start();
r.run();
stop();
return getTime();
}
你可以这样称呼它
StopWatch stopWatch = new StopWatch();
Runnable r = new Runnable() {
@Override
public void run() {
// Put some tasks here you want to measure.
}
};
long time = stopWatch.measureAction(r);
这使它变得万无一失。你不必担心在开始之前处理停止或者人们忘记调用一个而不是另一个等等。Runnable
很好的原因是因为
- 标准 java class,不是您自己的或第三方的
- 最终用户可以将他们需要的任何内容放入
Runnable
待完成。
(如果你用它来强制关闭流,那么你可以把需要用数据库连接完成的操作放在里面,这样最终用户就不需要担心如何打开和关闭它,你同时强制他们正确关闭它。)
如果您愿意,可以制作一些 StopWatchWrapper
而不是保留 StopWatch
不变。您也可以将 measureAction(Runnable)
设为 return 而不是 getTime()
public。
Java8的调用方式更简单
StopWatch stopWatch = new StopWatch();
long time = stopWatch.measureAction(() - > {/* Measure stuff here */});
A third (hopefully final) thought: it seems what the interviewer was looking for and what is being upvoted the most is throwing exceptions based on state (e.g., if stop()
is called before start()
or start()
after stop()
). This is a fine practice and in fact, depending on the methods in StopWatch
having a visibility other than private/protected, it's probably better to have than not have. My one issue with this is that throwing exceptions alone will not enforce a method call sequence.
For example, consider this:
class StopWatch {
boolean started = false;
boolean stopped = false;
// ...
public void start() {
if (started) {
throw new IllegalStateException("Already started!");
}
started = true;
// ...
}
public void stop() {
if (!started) {
throw new IllegalStateException("Not yet started!");
}
if (stopped) {
throw new IllegalStateException("Already stopped!");
}
stopped = true;
// ...
}
public long getTime() {
if (!started) {
throw new IllegalStateException("Not yet started!");
}
if (!stopped) {
throw new IllegalStateException("Not yet stopped!");
}
stopped = true;
// ...
}
}
Just because it's throwing IllegalStateException
doesn't mean that the proper sequence is enforced, it just means improper sequences are denied (and I think we can all agree exceptions are annoying, luckily this is not a checked exception).
The only way I know to truly enforce that the methods are called correctly is to do it yourself with the execute around pattern or the other suggestions that do things like return RunningStopWatch
and StoppedStopWatch
that I presume have only one method, but this seems overly complex (and OP mentioned that the interface couldn't be changed, admittedly the non-wrapper suggestion I made does this though). So to the best of my knowledge there's no way to enforce the proper order without modifying the interface or adding more classes.
I guess it really depends on what people define "enforce a method call sequence" to mean. If only the exceptions are thrown then the below compiles
StopWatch stopWatch = new StopWatch();
stopWatch.getTime();
stopWatch.stop();
stopWatch.start();
True it won't run, but it just seems so much simpler to hand in a Runnable
and make those methods private, let the other one relax and handle the pesky details yourself. Then there's no guess work. With this class it's obvious the order, but if there were more methods or the names weren't so obvious it can begin to be a headache.
原回答
More hindsight edit: OP mentions in a comment,
"The three methods should remain intact and are only interface to the programmer. The class members and method implementation can change."
所以下面是错误的,因为它从界面中删除了一些东西。 (从技术上讲,您可以将它实现为一个空方法,但这似乎是一件愚蠢的事情,而且太令人困惑了。)如果没有限制,我有点喜欢这个答案,它似乎是另一个 "fool proof" 这样做的方式,所以我会离开它。
对我来说这样的东西似乎不错。
class StopWatch {
private final long startTime;
public StopWatch() {
startTime = ...
}
public long stop() {
currentTime = ...
return currentTime - startTime;
}
}
我认为这很好的原因是记录是在对象创建期间进行的,因此不会忘记或乱序完成(如果不存在则无法调用 stop()
方法) .
一个缺陷可能是 stop()
的命名。起初我想也许 lap()
但这通常意味着重新启动或某种形式(或至少自上次 lap/start 以来的记录)。也许 read()
会更好?这模仿了看秒表时间的动作。我选择了 stop()
以使其与原始 class 相似。
我唯一不能 100% 确定的是如何获得时间。老实说,这似乎是一个更次要的细节。只要上面代码中的两个 ...
以相同的方式获取当前时间就可以了。
大概使用秒表的原因是对时间感兴趣的实体不同于负责启动和停止计时间隔的实体。如果不是这种情况,使用不可变对象并允许代码随时查询秒表以查看到目前为止已经过了多少时间的模式可能比使用可变秒表对象的模式更好。
如果您的目的是捕获关于花多少时间做各种事情的数据,我建议您最好使用 class 来构建与时间相关的事件列表。这样的 class 可能会提供一种方法来生成和添加一个新的与时间相关的事件,该事件将记录其创建时间的快照并提供一种方法来指示其完成。外部 class 还将提供一种方法来检索迄今为止注册的所有计时事件的列表。
如果创建新计时事件的代码提供了一个参数来指示其目的,则检查列表末尾的代码可以确定是否所有启动的事件都已正确完成,并识别出任何未完成的事件;它还可以确定是否有任何事件完全包含在其他事件中或与其他事件重叠但不包含在其他事件中。因为每个事件都有自己独立的状态,所以未能关闭一个事件不需要干扰任何后续事件或导致与它们相关的计时数据的任何丢失或损坏(例如,如果秒表被意外遗忘,可能会发生运行 应该停止的时候)。
虽然使用 start
和 stop
方法的可变秒表 class 当然是可能的,但如果意图是每个 "stop" 动作都与一个特定的 "start" 动作,具有 "start" 动作 return 必须是 "stopped" 的对象不仅可以确保这种关联,而且即使动作开始并放弃。
按照面试题,好像是这样
Class StopWatch {
long startTime;
long stopTime;
public StopWatch() {
start();
}
void start() {// set startTime}
void stop() { // set stopTime}
long getTime() {
stop();
// return difference
}
}
所以现在所有用户都需要在开始时创建 StopWatch class 的对象,并且 getTime() 需要在结束时调用
例如
StopWatch stopWatch=new StopWatch();
//do Some stuff
stopWatch.getTime()
我知道这已经得到解答,但找不到通过 接口 调用构建器来控制流程的答案,所以这是我的解决方案:
(以比我更好的方式命名接口:p)
public interface StartingStopWatch {
StoppingStopWatch start();
}
public interface StoppingStopWatch {
ResultStopWatch stop();
}
public interface ResultStopWatch {
long getTime();
}
public class StopWatch implements StartingStopWatch, StoppingStopWatch, ResultStopWatch {
long startTime;
long stopTime;
private StopWatch() {
//No instanciation this way
}
public static StoppingStopWatch createAndStart() {
return new StopWatch().start();
}
public static StartingStopWatch create() {
return new StopWatch();
}
@Override
public StoppingStopWatch start() {
startTime = System.currentTimeMillis();
return this;
}
@Override
public ResultStopWatch stop() {
stopTime = System.currentTimeMillis();
return this;
}
@Override
public long getTime() {
return stopTime - startTime;
}
}
用法:
StoppingStopWatch sw = StopWatch.createAndStart();
//Do stuff
long time = sw.stop().getTime();
我建议强制执行方法调用顺序解决了错误的问题;真正的问题是一个不友好的界面,用户必须知道秒表的状态。解决方案是取消了解秒表状态的任何要求。
public class StopWatch {
private Logger log = Logger.getLogger(StopWatch.class);
private boolean firstMark = true;
private long lastMarkTime;
private long thisMarkTime;
private String lastMarkMsg;
private String thisMarkMsg;
public TimingResult mark(String msg) {
lastMarkTime = thisMarkTime;
thisMarkTime = System.currentTimeMillis();
lastMarkMsg = thisMarkMsg;
thisMarkMsg = msg;
String timingMsg;
long elapsed;
if (firstMark) {
elapsed = 0;
timingMsg = "First mark: [" + thisMarkMsg + "] at time " + thisMarkTime;
} else {
elapsed = thisMarkTime - lastMarkTime;
timingMsg = "Mark: [" + thisMarkMsg + "] " + elapsed + "ms since mark [" + lastMarkMsg + "]";
}
TimingResult result = new TimingResult(timingMsg, elapsed);
log.debug(result.msg);
firstMark = false;
return result;
}
}
这允许简单地使用 mark
方法,返回结果并包含日志记录。
StopWatch stopWatch = new StopWatch();
TimingResult r;
r = stopWatch.mark("before loop 1");
System.out.println(r);
for (int i=0; i<100; i++) {
slowThing();
}
r = stopWatch.mark("after loop 1");
System.out.println(r);
for (int i=0; i<100; i++) {
reallySlowThing();
}
r = stopWatch.mark("after loop 2");
System.out.println(r);
这给出了不错的结果;
First mark: [before loop 1] at time 1436537674704
Mark: [after loop 1] 1037ms since mark [before loop 1]
Mark: [after loop 2] 2008ms since mark [after loop 1]
最近在面试中有人问我一个问题。
问题: 有一个class 用来分析代码的执行时间。 class 就像:
Class StopWatch {
long startTime;
long stopTime;
void start() {// set startTime}
void stop() { // set stopTime}
long getTime() {// return difference}
}
客户端应创建 StopWatch 的实例并相应地调用方法。用户代码可能会混淆方法的使用,从而导致意外结果。例如,start()、stop() 和 getTime() 调用应该按顺序进行。
这个class必须是"reconfigured",这样可以防止用户弄乱顺序。
如果在 start() 之前调用 stop() 或进行一些 if/else 检查,我建议使用自定义异常,但面试官不满意。
是否有设计模式来处理这些情况?
编辑: class成员和方法实现可以修改。
方法调用顺序不正确时抛出异常是很常见的。例如,Thread
的 start
如果调用两次将抛出 IllegalThreadStateException
。
您可能应该更好地解释实例如何知道方法的调用顺序是否正确。这可以通过引入状态变量并在每个方法开始时检查状态(并在必要时更新它)来完成。
也许他预料到了这个 'reconfiguration' 而问题根本不是关于方法序列的:
class StopWatch {
public static long runWithProfiling(Runnable action) {
startTime = now;
action.run();
return now - startTime;
}
}
我们通常使用来自 Apache Commons StopWatch 的 StopWatch 检查他们提供的模式。
秒表状态错误时抛出IllegalStateException
public void stop()
Stop the stopwatch.
This method ends a new timing session, allowing the time to be retrieved.
Throws:
IllegalStateException - if the StopWatch is not running.
直截了当。
首先,实现一个自己的 Java 分析器是浪费时间,因为好的分析器是可用的(也许这就是问题背后的意图)。
如果你想在编译时强制执行正确的方法顺序,你必须 return 链中的每个方法:
start()
必须使用停止方法 return 一个WatchStopper
。- 然后
WatchStopper.stop()
必须 return 使用getResult()
方法WatchResult
。
这些助手的外部构造 类 以及访问其方法的其他方式当然必须被阻止。
通过对接口进行微小的更改,您可以使方法序列成为唯一可以调用的方法 - 即使在编译时也是如此!
public class Stopwatch {
public static RunningStopwatch createRunning() {
return new RunningStopwatch();
}
}
public class RunningStopwatch {
private final long startTime;
RunningStopwatch() {
startTime = System.nanoTime();
}
public FinishedStopwatch stop() {
return new FinishedStopwatch(startTime);
}
}
public class FinishedStopwatch {
private final long elapsedTime;
FinishedStopwatch(long startTime) {
elapsedTime = System.nanoTime() - startTime;
}
public long getElapsedNanos() {
return elapsedTime;
}
}
用法很简单 - 每个方法 returns 都是不同的 class,只有当前适用的方法。基本上,秒表的状态被封装在类型系统中。
评论中指出,即使是上面的设计,也可以调用stop()
两次。虽然我认为这是附加值,但理论上可以把自己搞砸。那么,我能想到的唯一方法就是这样:
class Stopwatch {
public static Stopwatch createRunning() {
return new Stopwatch();
}
private final long startTime;
private Stopwatch() {
startTime = System.nanoTime();
}
public long getElapsedNanos() {
return System.nanoTime() - startTime;
}
}
这与省略 stop()
方法的赋值不同,但这也可能是一个不错的设计。然后一切都将取决于精确的要求...
我建议如下:
interface WatchFactory {
Watch startTimer();
}
interface Watch {
long stopTimer();
}
会这样使用
Watch watch = watchFactory.startTimer();
// Do something you want to measure
long timeSpentInMillis = watch.stopTimer();
你不能以错误的顺序调用任何东西。如果你调用 stopTimer
两次你都会得到有意义的结果(也许最好将它重命名为 measure
和 return 每次调用的实际时间)
这也可以在 Java 8 中使用 Lambdas 完成。在这种情况下,您将函数传递给 StopWatch
class,然后告诉 StopWatch
执行那个代码。
Class StopWatch {
long startTime;
long stopTime;
private void start() {// set startTime}
private void stop() { // set stopTime}
void execute(Runnable r){
start();
r.run();
stop();
}
long getTime() {// return difference}
}
再想一想
事后看来,他们似乎在寻找 execute around pattern。它们通常用于执行诸如强制关闭流之类的事情。由于这一行,这也更相关:
Is there a design pattern to handle these kind of situations?
我们的想法是,你给 "executing around" 一些 class 做某事的东西。您可能会使用 Runnable
但这不是必需的。 (Runnable
最有意义,你很快就会明白为什么。) 在你的 StopWatch
class 中添加一些像这样的方法
public long measureAction(Runnable r) {
start();
r.run();
stop();
return getTime();
}
你可以这样称呼它
StopWatch stopWatch = new StopWatch();
Runnable r = new Runnable() {
@Override
public void run() {
// Put some tasks here you want to measure.
}
};
long time = stopWatch.measureAction(r);
这使它变得万无一失。你不必担心在开始之前处理停止或者人们忘记调用一个而不是另一个等等。Runnable
很好的原因是因为
- 标准 java class,不是您自己的或第三方的
- 最终用户可以将他们需要的任何内容放入
Runnable
待完成。
(如果你用它来强制关闭流,那么你可以把需要用数据库连接完成的操作放在里面,这样最终用户就不需要担心如何打开和关闭它,你同时强制他们正确关闭它。)
如果您愿意,可以制作一些 StopWatchWrapper
而不是保留 StopWatch
不变。您也可以将 measureAction(Runnable)
设为 return 而不是 getTime()
public。
Java8的调用方式更简单
StopWatch stopWatch = new StopWatch();
long time = stopWatch.measureAction(() - > {/* Measure stuff here */});
A third (hopefully final) thought: it seems what the interviewer was looking for and what is being upvoted the most is throwing exceptions based on state (e.g., if
stop()
is called beforestart()
orstart()
afterstop()
). This is a fine practice and in fact, depending on the methods inStopWatch
having a visibility other than private/protected, it's probably better to have than not have. My one issue with this is that throwing exceptions alone will not enforce a method call sequence.For example, consider this:
class StopWatch { boolean started = false; boolean stopped = false; // ... public void start() { if (started) { throw new IllegalStateException("Already started!"); } started = true; // ... } public void stop() { if (!started) { throw new IllegalStateException("Not yet started!"); } if (stopped) { throw new IllegalStateException("Already stopped!"); } stopped = true; // ... } public long getTime() { if (!started) { throw new IllegalStateException("Not yet started!"); } if (!stopped) { throw new IllegalStateException("Not yet stopped!"); } stopped = true; // ... } }
Just because it's throwing
IllegalStateException
doesn't mean that the proper sequence is enforced, it just means improper sequences are denied (and I think we can all agree exceptions are annoying, luckily this is not a checked exception).The only way I know to truly enforce that the methods are called correctly is to do it yourself with the execute around pattern or the other suggestions that do things like return
RunningStopWatch
andStoppedStopWatch
that I presume have only one method, but this seems overly complex (and OP mentioned that the interface couldn't be changed, admittedly the non-wrapper suggestion I made does this though). So to the best of my knowledge there's no way to enforce the proper order without modifying the interface or adding more classes.I guess it really depends on what people define "enforce a method call sequence" to mean. If only the exceptions are thrown then the below compiles
StopWatch stopWatch = new StopWatch(); stopWatch.getTime(); stopWatch.stop(); stopWatch.start();
True it won't run, but it just seems so much simpler to hand in a
Runnable
and make those methods private, let the other one relax and handle the pesky details yourself. Then there's no guess work. With this class it's obvious the order, but if there were more methods or the names weren't so obvious it can begin to be a headache.
原回答
More hindsight edit: OP mentions in a comment,
"The three methods should remain intact and are only interface to the programmer. The class members and method implementation can change."
所以下面是错误的,因为它从界面中删除了一些东西。 (从技术上讲,您可以将它实现为一个空方法,但这似乎是一件愚蠢的事情,而且太令人困惑了。)如果没有限制,我有点喜欢这个答案,它似乎是另一个 "fool proof" 这样做的方式,所以我会离开它。
对我来说这样的东西似乎不错。
class StopWatch {
private final long startTime;
public StopWatch() {
startTime = ...
}
public long stop() {
currentTime = ...
return currentTime - startTime;
}
}
我认为这很好的原因是记录是在对象创建期间进行的,因此不会忘记或乱序完成(如果不存在则无法调用 stop()
方法) .
一个缺陷可能是 stop()
的命名。起初我想也许 lap()
但这通常意味着重新启动或某种形式(或至少自上次 lap/start 以来的记录)。也许 read()
会更好?这模仿了看秒表时间的动作。我选择了 stop()
以使其与原始 class 相似。
我唯一不能 100% 确定的是如何获得时间。老实说,这似乎是一个更次要的细节。只要上面代码中的两个 ...
以相同的方式获取当前时间就可以了。
大概使用秒表的原因是对时间感兴趣的实体不同于负责启动和停止计时间隔的实体。如果不是这种情况,使用不可变对象并允许代码随时查询秒表以查看到目前为止已经过了多少时间的模式可能比使用可变秒表对象的模式更好。
如果您的目的是捕获关于花多少时间做各种事情的数据,我建议您最好使用 class 来构建与时间相关的事件列表。这样的 class 可能会提供一种方法来生成和添加一个新的与时间相关的事件,该事件将记录其创建时间的快照并提供一种方法来指示其完成。外部 class 还将提供一种方法来检索迄今为止注册的所有计时事件的列表。
如果创建新计时事件的代码提供了一个参数来指示其目的,则检查列表末尾的代码可以确定是否所有启动的事件都已正确完成,并识别出任何未完成的事件;它还可以确定是否有任何事件完全包含在其他事件中或与其他事件重叠但不包含在其他事件中。因为每个事件都有自己独立的状态,所以未能关闭一个事件不需要干扰任何后续事件或导致与它们相关的计时数据的任何丢失或损坏(例如,如果秒表被意外遗忘,可能会发生运行 应该停止的时候)。
虽然使用 start
和 stop
方法的可变秒表 class 当然是可能的,但如果意图是每个 "stop" 动作都与一个特定的 "start" 动作,具有 "start" 动作 return 必须是 "stopped" 的对象不仅可以确保这种关联,而且即使动作开始并放弃。
按照面试题,好像是这样
Class StopWatch {
long startTime;
long stopTime;
public StopWatch() {
start();
}
void start() {// set startTime}
void stop() { // set stopTime}
long getTime() {
stop();
// return difference
}
}
所以现在所有用户都需要在开始时创建 StopWatch class 的对象,并且 getTime() 需要在结束时调用
例如
StopWatch stopWatch=new StopWatch();
//do Some stuff
stopWatch.getTime()
我知道这已经得到解答,但找不到通过 接口 调用构建器来控制流程的答案,所以这是我的解决方案: (以比我更好的方式命名接口:p)
public interface StartingStopWatch {
StoppingStopWatch start();
}
public interface StoppingStopWatch {
ResultStopWatch stop();
}
public interface ResultStopWatch {
long getTime();
}
public class StopWatch implements StartingStopWatch, StoppingStopWatch, ResultStopWatch {
long startTime;
long stopTime;
private StopWatch() {
//No instanciation this way
}
public static StoppingStopWatch createAndStart() {
return new StopWatch().start();
}
public static StartingStopWatch create() {
return new StopWatch();
}
@Override
public StoppingStopWatch start() {
startTime = System.currentTimeMillis();
return this;
}
@Override
public ResultStopWatch stop() {
stopTime = System.currentTimeMillis();
return this;
}
@Override
public long getTime() {
return stopTime - startTime;
}
}
用法:
StoppingStopWatch sw = StopWatch.createAndStart();
//Do stuff
long time = sw.stop().getTime();
我建议强制执行方法调用顺序解决了错误的问题;真正的问题是一个不友好的界面,用户必须知道秒表的状态。解决方案是取消了解秒表状态的任何要求。
public class StopWatch {
private Logger log = Logger.getLogger(StopWatch.class);
private boolean firstMark = true;
private long lastMarkTime;
private long thisMarkTime;
private String lastMarkMsg;
private String thisMarkMsg;
public TimingResult mark(String msg) {
lastMarkTime = thisMarkTime;
thisMarkTime = System.currentTimeMillis();
lastMarkMsg = thisMarkMsg;
thisMarkMsg = msg;
String timingMsg;
long elapsed;
if (firstMark) {
elapsed = 0;
timingMsg = "First mark: [" + thisMarkMsg + "] at time " + thisMarkTime;
} else {
elapsed = thisMarkTime - lastMarkTime;
timingMsg = "Mark: [" + thisMarkMsg + "] " + elapsed + "ms since mark [" + lastMarkMsg + "]";
}
TimingResult result = new TimingResult(timingMsg, elapsed);
log.debug(result.msg);
firstMark = false;
return result;
}
}
这允许简单地使用 mark
方法,返回结果并包含日志记录。
StopWatch stopWatch = new StopWatch();
TimingResult r;
r = stopWatch.mark("before loop 1");
System.out.println(r);
for (int i=0; i<100; i++) {
slowThing();
}
r = stopWatch.mark("after loop 1");
System.out.println(r);
for (int i=0; i<100; i++) {
reallySlowThing();
}
r = stopWatch.mark("after loop 2");
System.out.println(r);
这给出了不错的结果;
First mark: [before loop 1] at time 1436537674704
Mark: [after loop 1] 1037ms since mark [before loop 1]
Mark: [after loop 2] 2008ms since mark [after loop 1]