工作中使用==埋下的坑

本文通过实例详细解析了Java中不同类型变量间的比较方法,包括基本数据类型与封装类型的==及equals方法的区别,帮助开发者理解并避免常见错误。

前言:按理说这种错误,工作多年之后就不应该再犯的,不过实际工作中我遇到过两次,工作n年的同事还是犯了,所以,在此记上一笔以作警醒。还是先尝栗子,再下结论。

1:使用BigDecimal类,实际中也是经常用的,不过相对来说使用此类时犯的错误会少一点

package com.jd.test.integer;

import java.math.BigDecimal;

public class TestBigDecimalMain {
    public static void main(String args[]){
        BigDecimal _BigDecimal1 = new BigDecimal(99);
        BigDecimal _BigDecimal2 = new BigDecimal(99);

        System.out.println("_BigDecimal1 hashCode is :"+_BigDecimal1.hashCode()+"\n_BigDecimal2 hashCode is :"+_BigDecimal2.hashCode());
        System.out.println("_BigDecimal1 identityHashCode is :"+System.identityHashCode(_BigDecimal1)+"\n_BigDecimal2 identityHashCode is :"+System.identityHashCode(_BigDecimal2));

        //包装类型的==比较,赋值时采用的是创建对应的对象的方式
        System.out.println("_BigDecimal1==_BigDecimal2 is : "+(_BigDecimal1==_BigDecimal2));//false,注意:这里是false,也就是说不能使用这种比较方式
        //包装类型的equals比较
        System.out.println("_BigDecimal1.equals(_BigDecimal2) is : "+(_BigDecimal1.equals(_BigDecimal2)));//true
    }
}

2:使用Double类,特别是_Double1==_Double2相比较时,更容易出错

package com.jd.test.integer;

public class TestDoubleMain {
    public static void main(String args[]){
        double double1 = 99;
        double double2 = 99;
        Double _Double1 = 99D;
        Double _Double2 = 99D;
        Double _Double3 = new Double(99);
        Double _Double4 = new Double(99);

        System.out.println("double1 identityHashCode is :"+System.identityHashCode(double1)+"\ndouble2 identityHashCode is :"+System.identityHashCode(double2));

        System.out.println("_Double1 hashCode is :"+_Double1.hashCode()+"\n_Double2 hashCode is :"+_Double2.hashCode());
        System.out.println("_Double1 identityHashCode is :"+System.identityHashCode(_Double1)+"\n_Double2 identityHashCode is :"+System.identityHashCode(_Double2));

        System.out.println("_Double3 hashCode is :"+_Double3.hashCode()+"\n_Double4 hashCode is :"+_Double4.hashCode());
        System.out.println("_Double3 identityHashCode is :"+System.identityHashCode(_Double3)+"\n_Double4 identityHashCode is :"+System.identityHashCode(_Double4));


        //基本类型的==比较
        System.out.println("double1==double2 is : "+(double1==double2));//true
        //基本类型和包装类型的==比较
        System.out.println("double1==_Double1 is : "+(double1==_Double1));//true
//        System.out.println("_Double1==double1 is : "+(_Double1==double1));//true
        System.out.println("double1==_Double3 is : "+(double1==_Double3));//true
//        System.out.println("_Double3==double1 is : "+(_Double3==double1));//true
        //包装类型的==比较,赋值时采用的是直接赋值的方式
        System.out.println("_Double1==_Double2 is : "+(_Double1==_Double2));//false,注意:这里是false,也就是说不能使用这种比较方式
        //包装类型的==比较,赋值时采用的是创建对应的对象的方式
        System.out.println("_Double1==_Double3 is : "+(_Double1==_Double3));//false,注意:这里是false,也就是说不能使用这种比较方式
        System.out.println("_Double3==_Double4 is : "+(_Double3==_Double4));//false,注意:这里是false,也就是说不能使用这种比较方式
        //基本类型和包装类型的equals比较
        System.out.println("_Double1.equals(double1) is : "+(_Double1.equals(double1)));//true
        //包装类型的equals比较
        System.out.println("_Double1.equals(_Double2) is : "+(_Double1.equals(_Double2)));//true
        System.out.println("_Double3.equals(_Double1) is : "+(_Double3.equals(_Double1)));//true
        System.out.println("_Double3.equals(_Double4) is : "+(_Double3.equals(_Double4)));//true

        System.out.println(System.identityHashCode(_Double1)+"   "+System.identityHashCode(_Double2));//true
    }
}

3:使用Float类,特别是_Float1==_Float2相比较时,更容易出错

package com.jd.test.integer;

public class TestFloatMain {
    public static void main(String args[]){
        float float1 = 99;
        float float2 = 99;
        Float _Float1 = 99F;
        Float _Float2 = 99F;
        Float _Float3 = new Float(99);
        Float _Float4 = new Float(99);

        System.out.println("float1 identityHashCode is :"+System.identityHashCode(float1)+"\nfloat2 identityHashCode is :"+System.identityHashCode(float2));

        System.out.println("_Float1 hashCode is :"+_Float1.hashCode()+"\n_Float2 hashCode is :"+_Float2.hashCode());
        System.out.println("_Float1 identityHashCode is :"+System.identityHashCode(_Float1)+"\n_Float2 identityHashCode is :"+System.identityHashCode(_Float2));

        System.out.println("_Float3 hashCode is :"+_Float3.hashCode()+"\n_Float4 hashCode is :"+_Float4.hashCode());
        System.out.println("_Float3 identityHashCode is :"+System.identityHashCode(_Float3)+"\n_Float4 identityHashCode is :"+System.identityHashCode(_Float4));


        //基本类型的==比较
        System.out.println("float1==float2 is : "+(float1==float2));//true
        //基本类型和包装类型的==比较
        System.out.println("float1==_Float1 is : "+(float1==_Float1));//true
//        System.out.println("_Float1==float1 is : "+(_Float1==float1));//true
        System.out.println("float1==_Float3 is : "+(float1==_Float3));//true
//        System.out.println("_Float3==float1 is : "+(_Float3==float1));//true
        //包装类型的==比较,赋值时采用的是直接赋值的方式
        System.out.println("_Float1==_Float2 is : "+(_Float1==_Float2));//false,注意:这里是false,也就说不能使用这种比较方式
        //包装类型的==比较,赋值时采用的是创建对应的对象的方式
        System.out.println("_Float1==_Float3 is : "+(_Float1==_Float3));//false,注意:这里是false,也就说不能使用这种比较方式
        System.out.println("_Float3==_Float4 is : "+(_Float3==_Float4));//false,注意:这里是false,也就说不能使用这种比较方式
        //基本类型和包装类型的equals比较
        System.out.println("_Float1.equals(float1) is : "+(_Float1.equals(float1)));//true
        //包装类型的equals比较
        System.out.println("_Float1.equals(_Float2) is : "+(_Float1.equals(_Float2)));//true
        System.out.println("_Float3.equals(_Float1) is : "+(_Float3.equals(_Float1)));//true
        System.out.println("_Float3.equals(_Float4) is : "+(_Float3.equals(_Float4)));//true
    }
}

4:使用Integer类,特别是integer1==integer3相比较时,更容易出错

package com.jd.test.integer;

public class TestIntegerMain {
    public static void main(String args[]){
        int int1 = 99;
        int int2 = 99;
        Integer _Integer1 = 99;
        Integer _Integer2 = 99;
        Integer _Integer3 = new Integer(99);
        Integer _Integer4 = new Integer(99);

        System.out.println("int1 identityHashCode is :"+System.identityHashCode(int1)+"\nint2 identityHashCode is :"+System.identityHashCode(int2));

        System.out.println("_Integer1 hashCode is :"+_Integer1.hashCode()+"\n_Integer2 hashCode is :"+_Integer2.hashCode());
        System.out.println("_Integer1 identityHashCode is :"+System.identityHashCode(_Integer1)+"\n_Integer2 identityHashCode is :"+System.identityHashCode(_Integer2));

        System.out.println("_Integer3 hashCode is :"+_Integer3.hashCode()+"\n_Integer4 hashCode is :"+_Integer4.hashCode());
        System.out.println("_Integer3 identityHashCode is :"+System.identityHashCode(_Integer3)+"\n_Integer4 identityHashCode is :"+System.identityHashCode(_Integer4));

        //基本类型的==比较
        System.out.println("int1==int2 is : "+(int1==int2));//true
        //基本类型和包装类型的==比较
        System.out.println("int1==_Integer1 is : "+(int1==_Integer1));//true
//        System.out.println("_Integer1==int1 is : "+(_Integer1==int1));//true
        System.out.println("int1==_Integer3 is : "+(int1==_Integer3));//true
//        System.out.println("_Integer3==int1 is : "+(_Integer3==int1));//true
        //包装类型的==比较,赋值时采用的是直接赋值的方式
        System.out.println("_Integer1==_Integer2 is : "+(_Integer1==_Integer2));//true
        //包装类型的==比较,赋值时采用的是创建对应的对象的方式
        System.out.println("_Integer1==_Integer3 is : "+(_Integer1==_Integer3));//false,注意:这里是false,也就说不能使用这种比较方式
        System.out.println("_Integer3==_Integer4 is : "+(_Integer3==_Integer4));//false,注意:这里是false,也就说不能使用这种比较方式
        //基本类型和包装类型的equals比较
        System.out.println("_Integer1.equals(int1) is : "+(_Integer1.equals(int1)));//true
        //包装类型的equals比较
        System.out.println("_Integer1.equals(_Integer2) is : "+(_Integer1.equals(_Integer2)));//true
        System.out.println("_Integer3.equals(_Integer1) is : "+(_Integer3.equals(_Integer1)));//true
        System.out.println("_Integer3.equals(int1) is : "+(_Integer3.equals(int1)));//true
        System.out.println("_Integer3.equals(_Integer4) is : "+(_Integer3.equals(_Integer4)));//true
    }
}

5:使用Long类,特别是_Long1==_Long3相比较时,更容易出错

package com.jd.test.integer;

public class TestLongMain {
    public static void main(String args[]){
        long long1 = 99;
        long long2 = 99;;
        Long _Long1 = 99L;
        Long _Long2 = 99L;
        Long _Long3 = new Long(99);
        Long _Long4 = new Long(99);

        System.out.println("long1 identityHashCode is :"+System.identityHashCode(long1)+"\nlong2 identityHashCode is :"+System.identityHashCode(long2));

        System.out.println("_Long1 hashCode is :"+_Long1.hashCode()+"\n_Long2 hashCode is :"+_Long2.hashCode());
        System.out.println("_Long1 identityHashCode is :"+System.identityHashCode(_Long1)+"\n_Long2 identityHashCode is :"+System.identityHashCode(_Long2));

        System.out.println("_Long3 hashCode is :"+_Long3.hashCode()+"\n_Long4 hashCode is :"+_Long4.hashCode());
        System.out.println("_Long3 identityHashCode is :"+System.identityHashCode(_Long3)+"\n_Long4 identityHashCode is :"+System.identityHashCode(_Long4));

        //基本类型的==比较
        System.out.println("long1==long2 is : "+(long1==long2));//true
        //基本类型和包装类型的==比较
        System.out.println("long1==_Long1 is : "+(long1==_Long1));//true
//        System.out.println("_Long1==long1 is : "+(_Long1==long1));//true
        System.out.println("long1==_Long3 is : "+(long1==_Long3));//true
//        System.out.println("_Long3==long1 is : "+(_Long3==long1));//true
        //包装类型的==比较,赋值时采用的是直接赋值的方式
        System.out.println("_Long1==_Long2 is : "+(_Long1==_Long2));//true
        //包装类型的==比较,赋值时采用的是创建对应的对象的方式
        System.out.println("_Long1==_Long3 is : "+(_Long1==_Long3));//false,注意:这里是false,也就是说不能使用这种比较方式
        System.out.println("_Long3==_Long4 is : "+(_Long3==_Long4));//false,注意:这里是false,也就是说不能使用这种比较方式
        //基本类型和包装类型的equals比较
        System.out.println("_Long1.equals(long1) is : "+(_Long1.equals(long1)));//true
        //包装类型的equals比较
        System.out.println("_Long1.equals(_Long2) is : "+(_Long1.equals(_Long2)));//true
        System.out.println("_Long3.equals(_Long1) is : "+(_Long3.equals(_Long1)));//true
        System.out.println("_Long3.equals(_Long4) is : "+(_Long3.equals(_Long4)));//true
    }
}

 6:结论

     6-1:当变量声明时使用的全部都是基本数据类型,使用==比较是没问题的,当然,也只能使用==看看他们是否相等,此时比较的是变量的值是否相等

     6-2:当变量声明时全部或者部分使用了封装类型,使用==比较可能得不到我们预期的效果,建议一定使用equals方法,基本类型的包装类型类,都重写了equals方法,比较的是对象的内容是否相等,对于数据类型而言,就是他们的值是否相等

     如果想问为什么这样呢?请参看==和equals的简单比较吧!

     如果你将代码执行起来,观察了一下的话,还是有些问题没解释清楚的,比如:为什么 int int1=99;int int2=99;int1和int2的identityHashCode是一样的哪?为什么float float1=99;float float2=99;float1和float2的identityHashCode是不一样的哪?那就需要了解identityHashCode的生成规则了,需要了解一下java的内存地址分配规则了。为什么double1==_Double1 is : true,但是他们的identityHashCode却不相等哪?这需要了解一下基本数据类型和封装类型,自动拆箱和自动装箱的相关知识点了。

### 关于 `@SneakyThrows` 注解的常见问题及解决方法 #### 1. **隐藏受检异常的风险** 使用 `@SneakyThrows` 注解可以自动将受检异常转换为未受检异常(Unchecked Exception),从而绕过 Java 编译器对异常声明的要求。这种方式虽然简化了代码编写,但也带来了潜在风险——开发人员可能会忽略某些重要的异常处理逻辑[^3]。 如果某个方法中的异常被隐匿起来而没有得到妥善处理,则可能导致程序崩溃或其他不可预期的行为。例如,在文件读取操作中发生的 `IOException` 如果不被捕获并适当处理,就可能中断整个业务流程。 ```java import lombok.SneakyThrows; public class Example { @SneakyThrows public void readFile(String path) { FileReader reader = new FileReader(path); // ... other operations ... } } ``` 上述例子中,假如路径参数有误或目标文件不存在时产生的 `FileNotFoundException` 将不会显式提示给调用方知道如何应对这种情况。 **解决办法**: 即使采用此注解也应保持良好的编程习惯,对于那些确实需要关注的重要异常仍需单独捕获与记录日志以便后续排查问题所在之处;另外还可以通过自定义异常来增强可维护性和清晰度。 --- #### 2. **降低代码可读性** 当频繁运用此类技术手段规避标准语法结构时,会使原本直观易懂的部分变得晦涩难明。因为正常情况下应该由函数签名体现出来的信息现在完全消失了,增加了理解成本以及新人接手项目的难度[^4]。 考虑下面这段代码: ```java @Service public class MyService { private final SomeRepository repository; @Autowired public MyService(SomeRepository repository){ this.repository = repository; } @Transactional @SneakyThrows public void processTransaction(){ Object data = someOperationThatMightThrow(); repository.save(data); } private Object someOperationThatMightThrow() throws CustomException{ // Implementation details... } } ``` 这里尽管我们知道内部存在抛出异常的可能性但由于缺少明确说明使得阅读者难以第一时间意识到这一点进而影响整体认知效率。 **改善措施**: 仅限于真正必要场合才应用该特性,并且务必保证团队成员之间达成共识认可这种风格的选择理由及其局限性。 --- #### 3. **违反面向对象原则** Java 的异常机制本质上属于一种控制流管理工具,它鼓励开发者遵循开放封闭原则 (OCP),即软件实体应当对扩展开放,对修改关闭。然而滥用 `@SneakyThrows` 可能违背这一理念,因为它改变了原有接口契约却没有任何外部可见的变化发生[^2]。 假设有一个公共 API 定义如下所示: ```java public interface DataProcessor { void processData(List<String> dataList)throws ProcessingFailureException ; } // 实现类A 使用传统方式处理异常 class ProcessorImpl implements DataProcessor { @Override public void processData(List<String> dataList)throws ProcessingFailureException { try{ performCriticalTask(dataList); } catch(Exception ex){ throw new ProcessingFailureException(ex.getMessage(),ex); } } private void performCriticalTask(List<String> dataList){} } // 实现类B 利用了 Lombok 提供的功能偷懒省事 @RequiredArgsConstructor(onConstructor_={@SneakyThrows}) class LazyProcessor implements DataProcessor { private final ExternalSystemClient client; @Override public void processData(List<String> dataList) { client.sendDataToExternalSystem(dataList); } } ``` 从表面上看两个版本都能完成相同功能但是后者隐瞒了实际依赖关系并且破坏了原有的约定形式不利于长期维护升级工作开展下去。 **建议做法**: 始终尊重原始设计意图除非特殊情形下经过充分讨论评估后再决定是否引入这类快捷技巧。 --- ### 结论 综上所述,虽然 `@SneakyThrows` 能够有效减少冗余代码量提升生产力水平,但在具体实践中还需谨慎权衡利弊以免引发更多复杂状况甚至埋下隐患种子等待爆发时刻到来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值