Elegant Java Code Timing: From Simple Diff to AutoCloseable TraceWatch
This article explores various Java techniques for measuring code execution time—from basic start‑end time differences and StopWatch utilities to functional wrappers and AutoCloseable‑based TraceWatch—offering cleaner, less intrusive alternatives for performance profiling.
Introduction
Code timing statistics is a common requirement in daily development, especially when identifying performance bottlenecks. Java's language characteristics can make timing code feel intrusive and clutter business logic, so this article examines more elegant ways to perform code timing.
Conventional Methods
2.1 Time Difference
The simplest approach records the start time, then the end time, and computes the difference.
public class TimeDiffTest {
public static void main(String[] args) throws InterruptedException {
final long startMs = TimeUtils.nowMs();
TimeUnit.SECONDS.sleep(5); // simulate business code
System.out.println("timeCost: " + TimeUtils.diffMs(startMs));
}
}
/* output:
timeCost: 5005
*/
public class TimeUtils {
/**
* @return current milliseconds
*/
public static long nowMs() {
return System.currentTimeMillis();
}
/**
* Difference between current milliseconds and start milliseconds
* @param startMillis start nanoseconds
* @return time difference
*/
public static long diffMs(long startMillis) {
return diffMs(startMillis, nowMs());
}
}This method is easy to implement and understand, but it is highly invasive and makes the code look clumsy.
2.2 StopWatch
Many frameworks and common libraries provide their own StopWatch implementations. The following example mimics org.springframework.util.StopWatch with a custom TraceWatch class.
public class TraceWatchTest {
public static void main(String[] args) throws InterruptedException {
TraceWatch traceWatch = new TraceWatch();
traceWatch.start("function1");
TimeUnit.SECONDS.sleep(1); // simulate business code
traceWatch.stop();
traceWatch.start("function2");
TimeUnit.SECONDS.sleep(1); // simulate business code
traceWatch.stop();
traceWatch.record("function1", 1); // directly record time cost
System.out.println(JSON.toJSONString(traceWatch.getTaskMap()));
}
}
/* output:
{"function2":[{"data":1000,"taskName":"function2"}],"function1":[{"data":1000,"taskName":"function1"},{"data":1,"taskName":"function1"}]}
*/
public class TraceWatch {
/** Start time of the current task. */
private long startMs;
/** Name of the current task. */
@Nullable
private String currentTaskName;
@Getter
private final Map<String, List<TaskInfo>> taskMap = new HashMap<>();
/**
* Start a timing metric; call {@link #stop()} to end.
* @param taskName metric name
*/
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start TraceWatch: it's already running");
}
this.currentTaskName = taskName;
this.startMs = TimeUtils.nowMs();
}
/** Stop the current timing metric. */
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop TraceWatch: it's not running");
}
long lastTime = TimeUtils.nowMs() - this.startMs;
TaskInfo info = new TaskInfo(this.currentTaskName, lastTime);
this.taskMap.computeIfAbsent(this.currentTaskName, e -> new LinkedList<>()).add(info);
this.currentTaskName = null;
}
/** Directly record metric data, not limited to time differences. */
public void record(String taskName, Object data) {
TaskInfo info = new TaskInfo(taskName, data);
this.taskMap.computeIfAbsent(taskName, e -> new LinkedList<>()).add(info);
}
@Getter
@AllArgsConstructor
public static final class TaskInfo {
private final String taskName;
private final Object data;
}
}Advanced Methods
3.1 Function
Since JDK 1.8, the java.util.function package provides functional interfaces that allow executing extra code around a specific code block.
public class TraceHolderTest {
public static void main(String[] args) {
TraceWatch traceWatch = new TraceWatch();
TraceHolder.run(traceWatch, "function1", i -> {
try {
TimeUnit.SECONDS.sleep(1); // simulate business code
} catch (InterruptedException e) {
e.printStackTrace();
}
});
String result = TraceHolder.run(traceWatch, "function2", () -> {
try {
TimeUnit.SECONDS.sleep(1); // simulate business code
return "YES";
} catch (InterruptedException e) {
e.printStackTrace();
return "NO";
}
});
TraceHolder.run(traceWatch, "function1", i -> {
try {
TimeUnit.SECONDS.sleep(1); // simulate business code
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(JSON.toJSONString(traceWatch.getTaskMap()));
}
}
/* output:
{"function2":[{"data":1004,"taskName":"function2"}],"function1":[{"data":1001,"taskName":"function1"},{"data":1002,"taskName":"function1"}]}
*/
public class TraceHolder {
/** Call with a return value */
public static <T> T run(TraceWatch traceWatch, String taskName, Supplier<T> supplier) {
try {
traceWatch.start(taskName);
return supplier.get();
} finally {
traceWatch.stop();
}
}
/** Call without a return value */
public static void run(TraceWatch traceWatch, String taskName, IntConsumer function) {
try {
traceWatch.start(taskName);
function.accept(0);
} finally {
traceWatch.stop();
}
}
}3.2 AutoCloseable
Java 7's try‑with‑resources can automatically close objects that implement AutoCloseable. By making TraceWatch implement this interface and returning the instance from start(), the timing can be managed automatically.
// Without AutoCloseable
public static String readFirstLineFromFile(String path) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path));
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
br.close();
}
}
return null;
}
// Using AutoCloseable
public static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}Modifying TraceWatch.start() to return this enables usage like:
public static void main(String[] args) throws Exception {
TraceWatch traceWatch = new TraceWatch();
try (TraceWatch ignored = traceWatch.start("function1")) {
TimeUnit.SECONDS.sleep(1); // simulate business code
}
try (TraceWatch ignored = traceWatch.start("function2")) {
TimeUnit.SECONDS.sleep(1); // simulate business code
}
System.out.println(JSON.toJSONString(traceWatch.getTaskMap()));
}
/* output:
{"function2":[{"data":1001,"taskName":"function2"}],"function1":[{"data":1002,"taskName":"function1"},{"data":1002,"taskName":"function1"}]}
*/Conclusion
The article presents four methods for code timing: simple time‑difference calculation, StopWatch‑style utilities, functional wrappers using Supplier and IntConsumer, and AutoCloseable‑based tracing. Each approach balances simplicity, invasiveness, and elegance, offering developers options to choose based on their needs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
