在项目开发中,我们经常需要统计某个方法或某段代码的执行耗时。传统的做法是在方法前后分别记录时间戳,然后相减,但这样会让计时代码侵入业务逻辑,不仅冗余,还难以统一管理。本文将介绍一种利用Java匿名内部类封装计时逻辑的方式,将需要测量的代码块作为参数传入,实现关注点分离,并深入探讨这种模式的应用场景。


一、传统做法的痛点

假设我们需要测量doSomething()方法的执行时间,通常这样写:

long start=System.currentTimeMillis();
doSomething();
long end=System.currentTimeMillis();
double seconds=(end-start)/1000.0;
System.out.println("执行耗时:"+seconds+"秒");

如果只有一个方法还好,但当多个地方都需要计时,代码中就会充斥大量重复的start/end,而且一旦方法抛出异常,计时逻辑可能失效。计时代码与业务代码高度耦合,违反单一职责原则

二、使用匿名内部类封装计时逻辑

我们可以定义一个任务接口,利用匿名内部类将待测量的代码作为参数传递,由工具类统一执行计时,这其实是模板方法模式,和回调机制的体现。

1.定义任务接口

public interface TimeTask{
	void execute();
}

2.编写计时工具类

import java.util.concurrent.TimeUnit;
public class TimeUtil{
	/**
	 *测量TimeTask的执行时间(毫秒)
	 */
	 public class long measureMillis(TimeTask task){
			long start=System.nanoTime();
			task.excute();
			long end=System.naoTime();
			return TimeUnit.NANOSECONDS.toMillis(end-start); 
	}
 /**
  *测量TimeTask的执行时间(秒),保留小数
  0	*/
 	public static double measure Seconds(TimeTask task){
		return measureMillis(task)/1000.0;
	}
}

3.调用示例

在需要计时的位置,通过匿名内部类创建TimeTask实例,将业务逻辑写在execute()方法中:

double seconds=TimeUtil.measureSeconds(new TimeTask(){
	@Override
	public void excute(){
		//需要测量耗时的业务代码
		doSomething();
		doAnotherThing();
	}
});
System.out.println("执行耗时:"+seconds+"秒");

这样一来,计时逻辑完全由TimeUtil负责,业务代码只需关注自身实现。如果需要测量多段不同的逻辑,只需继续使用匿名内部类即可,避免了计时代码的重复。

小贴士:使用‘System.nanoTime()’代替‘System.currentTimeMillis()’可以获得更多的精度,且不受系统时间调整的影响。

三、应用场景分析

这种基于匿名内部类的计时模式在实际开发中由非常丰富的应用场景:

1.性能调优与快速Debug

在开发阶段,当怀疑某段代码可能存在性能瓶颈时,可以临时用匿名内部类包裹该段代码,快速输出耗时,无需定义额外的变量或日志框架。调试完毕后删除即可,不会影响原有代码结构。

2.接口慢操作日志

在服务层方法中,可以记录每次核心操作的耗时,当超过阈值(如1秒)是打印慢日志,帮助运维监控:

double time =TimeUtil.measureSeconds(new TimeTask(){
@Override
	public void execute(){
		orderService.createOrder(order);
	}	
));
if(time>1.0){
	log.warn("创建订单耗时过长:{}秒",time);
}

3.单元测试中的性能断言

在JUnit测试中,可以断言某个方法的执行时间是否在可接受范围内,保证代码效率不退化:

@Test
public void tesrPreformance(){
	double time=TimeUtil.measureSeconds(new TimeTask(){
	@Override
	public void execute(){
		algoritm.sort(largeData);
	}
});
assertTure("排序耗时超出预期",time<0.5);
}

4.对比不同算法或策略的效率

例如在选择缓存淘汰策略时,通过匿名内部类快速比较LRU和LFU的访问耗时:

double lruTime=TimeUtil.measureSeconds(new TimeTask(){
	public void execute(){ cache.get(key,Strategy,LRU);}
});
double lfuTime=TimeUtil.measureSeconds(new TimeTask(){
	public void execute(){cache.get(key.Steategy.LFU);}
});
System.out.println("LRU:"+lruTime+"s,LFU:"+lfuTime+"s");

5.简易AOP(面向切面编程)实现

在没有使用Spring AOP等框架的场景下,可以利用该模式手动为方法调用添加“横切关注点”——比如为所有DAO操作统一记录执行时间,只需在调用处包裹匿名内部类,而不必修改DAO内部代码。

四、进阶:支持返回值的方法计时

上面的TimeTask无法处理有返回值的方法。我们可以扩展一个泛型接口TimeCallable,并让工具类返回执行结果+耗时的复合对象:

public interface TimeCallable<V>{
	V call() throws Exception;
}
public class TimeResult<V>{
	private final V result;
	private final long millis;
}
public class TImeUtil{
	public static <V>TimeResult<V> measure(TimeCallable<V>callable){
	long start=System.nanoTime();
	try{
			V result =callable.call();
			long end=System.nanoTime();
			long millis=TimeUnit.NANOSECONDS.toMillis(end-start);
			return new TimeResult<>(result,millis);
		}catch(Exception  e){
			throw new RuntimeException(e);
		}
	}
}

使用匿名内部类调用:

 TimeResult<String> tr=TimeUtil.measure(new TImeCallable<String>(){
	@Override
	public String call() throws Exception{
			return remoteService.getData();
	}
});
System.out.println("结果:"+tr.getResult()+",耗时:"+tr.getMillis()/1000.0+"秒")

这样,既拿到了业务结果,也获得了精准的耗时(秒),一举两得。


五、匿名内部类vsLambda表达式

Java8引入的Lambda表达式让这种模式更加简洁:

double seconds=TimeUtil.measureSeconds(()->doSomething());

但匿名内部类的应用场景依然大量存在

  • Android开发(较低API等级限制使用Lambda)
  • 需要显示声明异常时,Lambda表达式受限于函数接口
  • 需要访问外部非final变量时(匿名内部类也有限制但可结合数组等技巧)
  • 代码可读性:某些复杂逻辑用匿名内部类写得更清晰

因此,理解匿名内部类作为”方法指针“替代品的机制,仍然非常重要。

六、总结

通过匿名内部类将计时代码模板化,我们可以轻松地将方法执行耗时统计从业务逻辑中抽离出来,实现:

  • 减少重复代码
  • 业务与横切关注点分离
  • 更灵活的计时控制(毫秒、秒、纳秒)
  • 支持无返回值与有返回值两种场景

无论是日常调试、性能优化,还是搭建简单的监控体系,这种模式都能发挥很大作用。希望本文能给你带来启发,在项目中大胆使用匿名内部类来简化你的计时代码!

更多推荐