final修饰函数在性能上提高的测试。

无意间看到一篇文章其中说到:final 修饰函数可以提升性能

于是我找到这种提升性能的2种解释:

其一:
final方法:
当一个方法用“final”来修饰时,表示该方法不可以被子类重写。

使用final方法的原因有二:   

一、限制了子类中对其改写;
二、提高了执行的效率,因为这种情况属于静态绑定,Java虚拟机(JVM)的即时编译器就不会去检索该方法在其父类、子类或爷爷类、孙子类等有内联关系的类中是否存在重写,省去了动态判断究竟要执行哪一个类(具体内联关系的类)的这个名称的方法。

 

其二:
final方法   如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。   

使用final方法的原因有二:   

第一、把方法锁定,防止任何继承类修改它的意义和实现。   

第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。 按照我的理解:相当于C++ 中 inline 函数

 

于是,编写我的测试实例:

测试一:非继承情况下

package cn.vicky.chapt12;

/**
 *
 * @author Vicky.H
 */
public class FinalTest {
    
    static long temp;

    /**   
     * 测试方法一   
     */
    public void Method1(int i) {
        temp = i + 1;
        temp = i + 2;
        temp = i + 3;
        temp = i + 4;
        temp = i + 5;
        temp = i + 6;
        temp = i + 7;
        temp = i + 8;
        temp = i + 9;
        temp = i + 10;
    }

    /**   
     * 测试方法二   
     */
    public final void Method2(int i) {
        temp = i + 1;
        temp = i + 2;
        temp = i + 3;
        temp = i + 4;
        temp = i + 5;
        temp = i + 6;
        temp = i + 7;
        temp = i + 8;
        temp = i + 9;
        temp = i + 10;
    }
    

    public static void main(String args[]) {
        FinalTest finalTest = new FinalTest(); // 2个函数在执行时间上几乎相同
        long start, now;
        start = System.currentTimeMillis();
        for (int i = 0; i < 999999999; i++) {
            finalTest.Method1(i);
        }
        now = System.currentTimeMillis() - start;
        System.err.println("调用不带final关键字的Method1方法耗时(ms毫秒):" + now);
        System.out.println("---------------------------------------------------");
        start = System.currentTimeMillis();
        for (int i = 0; i < 999999999; i++) {
            finalTest.Method2(i);
        }
        now = System.currentTimeMillis() - start;
        System.err.println("调用带final关键字的Method2方法耗时(ms毫秒):" + now);
    }
}


这种情况下,final与非final几乎没有差异。

 

测试二:在继承情况下

package cn.vicky.chapt12;

/**
 *
 * @author Vicky.H
 */
public class FinalParent {

    static long temp;
    
    /**   
     * 测试方法一   
     */
    public void Method1(int i) {
        temp = i + 1;
        temp = i + 2;
        temp = i + 3;
        temp = i + 4;
        temp = i + 5;
        temp = i + 6;
        temp = i + 7;
        temp = i + 8;
        temp = i + 9;
        temp = i + 10;
    }

    /**   
     * 测试方法二   
     */
    public final void Method2(int i) {
        temp = i + 1;
        temp = i + 2;
        temp = i + 3;
        temp = i + 4;
        temp = i + 5;
        temp = i + 6;
        temp = i + 7;
        temp = i + 8;
        temp = i + 9;
        temp = i + 10;
    }
}


 

package cn.vicky.chapt12;

/**
 * 注意,该测试
 * @author Vicky.H
 */
public class FinalChild extends FinalParent {

    
    /**   
     * 测试程序入口方法   
     * @param args 入口参数列表   
     */
    public static void main(String[] args) {
        // FinalChild finalTest = new FinalChild();
        FinalParent finalTest = new FinalChild();
        long start, now;
        start = System.currentTimeMillis();
        for (int i = 0; i < 999999999; i++) {
            finalTest.Method1(i);
        }
        now = System.currentTimeMillis() - start;
        System.err.println("调用不带final关键字的Method1方法耗时(ms毫秒):" + now);
        System.out.println("---------------------------------------------------");
        start = System.currentTimeMillis();
        for (int i = 0; i < 999999999; i++) {
            finalTest.Method2(i);
        }
        now = System.currentTimeMillis() - start;
        System.err.println("调用带final关键字的Method2方法耗时(ms毫秒):" + now);

    }
}


但任然同样的,函数在执行时间上几乎相同,那么我可以认为是因为我的代码快达不到final对性能影响的度么?(待续。。。)

<think>我们面对的问题是如何在Java单元测试中mock被final修饰的Logger对象。由于Logger对象通常被声明为final,并且使用静态工厂方法(如LoggerFactory.getLogger())获取,这给mock带来了挑战。 ###问题分析1.**Logger的典型用法**:在Java中,我们通常这样声明Logger:```javaprivate staticfinal LoggerLOGGER =LoggerFactory.getLogger(MyClass.class);```2.**难点**:Logger被`final`修饰,且其创建方式是通过静态工厂方法。传统的Mockito无法mockfinal类或方法(除非使用特定配置),也无法直接mock静态方法。 ###解决方案我们可以使用PowerMockito来解决这个问题。PowerMockito扩展了Mockito,能够mock静态方法、构造方法、final类和方法等。 ####步骤1. **添加依赖**:确保项目中包含PowerMockito的依赖(例如:powermock-api-mockito2和powermock-module-junit4)。2.**配置测试类**:使用PowerMockRunner,并准备需要mock的类(这里包括LoggerFactory和被测类)。3.**mock静态方法**:使用PowerMockito.mockStatic来mockLoggerFactory的getLogger方法,使其返回一个mock的Logger对象。 4. **设置期望行为**:对mock的Logger对象设置期望的行为(例如:当调用error方法时,不做任何事或验证是否被调用)。 ###示例代码假设我们有一个类`MyService`,其中使用了final修饰的Logger: ```javaimport org.slf4j.Logger; importorg.slf4j.LoggerFactory; publicclass MyService {privatestatic finalLogger LOGGER =LoggerFactory.getLogger(MyService.class);publicvoid doSomething(){try {//业务逻辑} catch(Exceptione){LOGGER.error("An erroroccurred",e);}}} ```测试类如下: ```javaimport org.junit.Test; importorg.junit.runner.RunWith; importorg.powermock.api.mockito.PowerMockito;import org.powermock.core.classloader.annotations.PrepareForTest; importorg.powermock.modules.junit4.PowerMockRunner; importorg.slf4j.Logger;import org.slf4j.LoggerFactory;@RunWith(PowerMockRunner.class)@PrepareForTest({LoggerFactory.class, MyService.class}) //注意:需要准备LoggerFactory和被测类(因为Logger是在被测类中静态初始化的) publicclass MyServiceTest{@Testpublic voidtestDoSomethingWhenExceptionOccurs() {//mock静态的LoggerFactory.getLogger方法LoggermockLogger= PowerMockito.mock(Logger.class);PowerMockito.mockStatic(LoggerFactory.class);PowerMockito.when(LoggerFactory.getLogger(MyService.class)).thenReturn(mockLogger);//创建被测对象MyService service= newMyService();//执行被测方法service.doSomething();//验证:当出现异常时,是否调用了LOGGER.error//注意:这里假设doSomething方法内部会触发异常情况,实际测试可能需要构造异常场景PowerMockito.verifyStatic(LoggerFactory.class);//验证静态方法调用LoggerFactory.getLogger(MyService.class);Mockito.verify(mockLogger).error(Mockito.anyString(), Mockito.any(Throwable.class));}} ```###关键点说明-`@RunWith(PowerMockRunner.class)`:使用PowerMock的测试运行器。- `@PrepareForTest({LoggerFactory.class, MyService.class})`:告诉PowerMock准备这两个类。`MyService.class`需要被准备是因为它包含Logger的静态初始化(调用LoggerFactory.getLogger)。- **mock静态方法**:使用`PowerMockito.mockStatic`来mock`LoggerFactory`的`getLogger`方法,并让它返回我们mock的Logger对象。 -**验证行为**:使用`PowerMockito.verifyStatic`来验证静态方法的调用,然后使用`Mockito.verify`来验证mock的Logger对象上的方法调用。###注意事项1. **测试场景**:上述示例中,我们假设在`doSomething`方法中会触发异常,从而调用`LOGGER.error`。在实际测试中,可能需要通过模拟依赖、抛出异常等方式来构造异常场景。2.**性能**:使用PowerMock会减慢测试速度,因为它需要修改字节码。因此,应谨慎使用,尽量考虑设计上的改进(如将Logger作为依赖注入,但这通常不现实)。 3. **版本兼容**:确保PowerMockito版本与Mockito和JUnit版本兼容。 ###相关问题1.如何在不使用PowerMock的情况下mock final类或方法?(提示:Mockito从2.1.0版本开始支持mock final类和方法,但需要配置。然而,对于静态方法,仍然需要PowerMock或Mockito3.4.0+的InlineMockMaker,但后者对静态方法的支持仍有限制) 2.除了PowerMock,还有哪些框架可以用于mock静态方法和构造方法?(例如:JMockit,但注意JMockit的使用方式和兼容性)3.如何设计可测试的代码以避免使用PowerMock?(例如:通过依赖注入、将Logger作为非静态成员、使用门面模式等)[^1]:引用[1]中提到了使用PowerMockito来mock构造方法,这里我们扩展到了mock静态方法。[^2]:引用[2]中提到了异常测试,虽然本文未涉及,但也是单元测试的重要部分。[^3]:引用[3]中展示了测试生命周期注解的使用,在编写测试类时应注意这些注解的执行顺序。</think>### 如何在Java单元测试中mock被final修饰的Logger对象 在Java单元测试中,mock被`final`修饰的Logger对象需要特殊处理,因为标准Mockito无法直接mock final类/方法。以下是三种解决方案: #### 1. **使用PowerMockito(最常用)** ```java // 测试类配置 @RunWith(PowerMockRunner.class) @PrepareForTest({LoggerFactory.class, YourClass.class}) // 包含Logger工厂和被测类 public class YourTest { @Test public void testLogging() throws Exception { // 1. Mock静态工厂方法 PowerMockito.mockStatic(LoggerFactory.class); // 2. 创建Logger的mock对象 Logger mockLogger = Mockito.mock(Logger.class); // 3. 指定静态方法返回值 PowerMockito.when(LoggerFactory.getLogger(YourClass.class)) .thenReturn(mockLogger); // 4. 设置期望行为 Mockito.doNothing().when(mockLogger).error(Mockito.anyString()); // 5. 执行测试并验证 new YourClass().methodWithLogging(); Mockito.verify(mockLogger).error(Mockito.contains("expected error")); } } ``` **关键点**: - 需添加依赖:`powermock-api-mockito2` + `powermock-module-junit4` - `@PrepareForTest`必须包含`LoggerFactory`和被测类 - 通过`mockStatic()`拦截静态工厂方法[^1] #### 2. **配置Mockito支持final类(Mockito 2.1+)** ```java // 在src/test/resources/mockito-extensions中创建文件: // org.mockito.plugins.MockMaker // 文件内容: mock-maker-inline ``` 测试代码与普通mock相同: ```java @Test public void testInlineMock() { Logger mockLogger = Mockito.mock(Logger.class); try (MockedStatic<LoggerFactory> mocked = Mockito.mockStatic(LoggerFactory.class)) { mocked.when(() -> LoggerFactory.getLogger(YourClass.class)) .thenReturn(mockLogger); // 执行测试并验证 new YourClass().methodWithLogging(); Mockito.verify(mockLogger).warn(Mockito.anyString()); } } ``` **优势**:无需PowerMock,简化依赖 **限制**:需Mockito 2.1+,对静态方法需配合`mockStatic`[^2] #### 3. **重构代码(推荐长期方案)** ```java // 原始代码 private static final Logger LOG = LoggerFactory.getLogger(Class.class); // 重构为可注入的形式 class YourClass { private final Logger log; // 通过构造函数注入 public YourClass(Logger logger) { this.log = logger; } // 提供默认构造器保持兼容性 public YourClass() { this(LoggerFactory.getLogger(YourClass.class)); } } ``` 测试时直接注入mock: ```java @Test public void testInjectedLogger() { Logger mockLogger = Mockito.mock(Logger.class); YourClass instance = new YourClass(mockLogger); instance.methodWithLogging(); Mockito.verify(mockLogger).info("expected message"); } ``` **最佳实践**: - 优先选择方案3(依赖注入)提高测试性 - 短期方案选方案2(Mockito inline) - 遗留项目用方案1(PowerMockito) [^1]: PowerMock通过字节码操作突破final限制,需特殊测试运行器 [^2]: Mockito的inline mockmaker利用Java Instrumentation API实现final类mock [^3]: 重构方案符合SOLID原则,是长期最健壮的解决方案 ### 相关问题 1. 如何验证SLF4J Logger的日志内容是否包含特定字符串? 2. 使用PowerMockito时`@PrepareForTest`的工作原理是什么? 3. 除了Logger,还有哪些常见场景需要mock final类? 4. 如何测试Logback/MDC等高级日志功能? 5. 重构代码注入Logger时如何避免破坏现有代码?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值