避免Java应用中空指针异常的技巧和最佳实践


原文链接:http://javarevisited.blogspot.com/2013/05/ava-tips-and-best-practices-to-avoid-nullpointerexception-program-application.html


Java应用中抛出的NullPointerException是解决空指针的最好方式,也是写出能顺利工作的健壮程序的关键。熟话说“预防胜于治疗”,对于这么令人讨厌的空指针异常,这句话也是成立的。值得庆幸的是运用一些防御性的编码技巧,跟踪应用中多个部分之间的联系,你可以将Java中的空指针异常控制在一个很好的水平上。顺便说一句,这是Javarevisited上的第二个NullPointerException的帖子。在上个帖子中我们讨论了Java中导致空指针异常的常见原因,而在本教程中我们将会学习一些Java的编程技巧和最佳实践。这些技巧可以帮助你避免Java中的NullPointerException。遵从这些技巧同样可以减少Java代码中到处都有的非空检查的数量。作为一个有经验的Java程序员,你可能已经知道其中的一部分技巧并且应用在你的项目中。但对于新手和中级开发人员来说,这将是很值得学习的。顺便说一句,如果你知道其它的避免空指针异常和减少空指针检查的Java技巧,请和我们分享。


1) 从已知的String对象中调用equals()和equalsIgnoreCase()方法,而非未知对象。

总是从已知的非空String对象中调用equals()方法。因为equals()方法是对称的,调用a.equals(b)和调用b.equals(a)是完全相同的,这也是为什么程序员对于对象a和b这么不上心。如果调用者是空指针,这种调用可能导致一个空指针异常

Object unknownObject = null;

//错误方式 – 可能导致 NullPointerException
if(unknownObject.equals("knownObject")){
   System.err.println("This may result in NullPointerException if unknownObject is null");
}

//正确方式 - 即便 unknownObject是null也能避免NullPointerException
if("knownObject".equals(unknownObject)){
    System.err.println("better coding avoided NullPointerException");
}

这是避免空指针异常最简单的Java技巧,但能够导致巨大的改进,因为equals()是一个常见方法。


2) 当valueOf()和toString()返回相同的结果时,宁愿使用前者。

因为调用null对象的toString()会抛出空指针异常,如果我们能够使用valueOf()获得相同的值,那宁愿使用valueOf(),传递一个null给valueOf()将会返回“null”,尤其是在那些包装类,像Integer、Float、Double和BigDecimal。

BigDecimal bd = getPrice();
System.out.println(String.valueOf(bd)); //不会抛出空指针异常
System.out.println(bd.toString()); //抛出 "Exception in thread "main" java.lang.NullPointerException"

如果你不知道这对象是不是null,请使用这个Java技巧。

 

3) 使用null安全的方法和库

有很多开源库已经为您做了繁重的空指针检查工作。其中最常用的一个的是Apache commons 中的StringUtils。你可以使用StringUtils.isBlank(),isNumeric(),isWhiteSpace()以及其他的工具方法而不用担心空指针异常。


//StringUtils方法是空指针安全的,他们不会抛出空指针异常
System.out.println(StringUtils.isEmpty(null));
System.out.println(StringUtils.isBlank(null));
System.out.println(StringUtils.isNumeric(null));
System.out.println(StringUtils.isAllUpperCase(null));

Output:
true
true
false
false

4) 避免从方法中返回空指针,而是返回空collection或者空数组。

这个Java最佳实践或技巧由Joshua Bloch在他的书Effective Java中提到。这是另外一个可以更好的使用Java编程的技巧。通过返回一个空collection或者空数组,你可以确保在调用如size(),length()的时候不会因为空指针异常崩溃。Collections类提供了方便的空List,Set和Map: Collections.EMPTY_LIST,Collections.EMPTY_SET,Collections.EMPTY_MAP。这里是实例:

public List getOrders(Customer customer){
    List result = Collections.EMPTY_LIST;
    return result;
}

5) 使用annotation @NotNull 和 @Nullable

在写程序的时候你可以定义是否可为空指针。通过使用像@NotNull和@Nullable之类的annotation来声明一个方法是否是空指针安全的。现代的编译器、IDE或者工具可以读此annotation并帮你添加忘记的空指针检查,或者向你提示出不必要的乱七八糟的空指针检查。IntelliJ和findbugs已经支持了这些annotation。这些annotation同样是JSR 305的一部分,但即便IDE或工具中没有,这个annotation本身可以作为文档。看到@NotNull和@Nullable,程序员自己可以决定是否做空指针检查。顺便说一句,这个技巧对Java程序员来说相对比较新,要采用需要一段时间。

 

6) 避免你的代码中不必要的自动包装和自动解包。

且不管其他如创建临时对象的缺点,如果wrapper类对象是null,自动包装同样容易导致空指针异常。例如如果person对象没有电话号码的话会返回null,如下代码会因为空指针异常崩溃。

Person ram = new Person("ram");
int phone = ram.getPhone();

当使用自动包装和自动解包的时候,不仅仅是等号,< > 同样会抛出空指针异常。你可以通过这篇文章来学习更多的Java中的自动包装和拆包的陷阱

 

7) 遵从Contract并定义合理的默认值。

在Java中避免空指针异常的一个最好的方法是简单的定义contract并遵从它们。大部分空指针异常的出现是因为使用不完整的信息创建对象或者未提供所有的依赖项。如果你不允许创建不完整的对象并优雅地拒绝这些请求,你可以在接下来的工作者预防大量的空指针异常。类似的,如果对象允许创建,你需要给他们定义一个合理的默认值。例如一个Employee对象不能在创建的时候没有id和name,但是是否有电话号码是可选的。现在如果Employee没有电话号码,你可以返回一个默认值(例如0)来代替返回null。但是必须谨慎选择,哟有时候检查空指针比调用无效号码要方便。同样的,通过定义什么可以是null,什么不能为null,调用者可以作出明智的决定。failing fast或接受null同样是一个你需要进行选择并贯彻的,重要的设计决策。


8) 定义数据库中的字段是否可为空

如果你在使用数据库来保存你的域名对象,如Customers,Orders 等,你需要在数据库本身定义是否为空的约束。因为数据库会从很多代码中获取数据,数据库中有是否为空的检查可以确保你的数据健全。在数据空中维护null约束同样可以帮助你减少Java代码中的空指针检查。当从数据库中加载一个对象是你会明确,哪些字段是可以为null的,而哪些不能,这可以使你代码中不必要的!= null检查最少化。

 

9) 使用空对象模式(Null Object Pattern)

还有一种方法来避免Java中的空指针异常。如果一个方法返回对象,在调用者中执行一些操作,例如Collection.iterator()方法返回迭代器,其调用者执行遍历。假设如果一个调用者并没有任何迭代器,其可以返回空对象(Null object)而非null。空对象是一个特殊的对象,其在不同的上下文中有不同的意义。例如一个空的迭代器调用hasNext()返回false时,可以是一个空对象。同样的在返回Container和Collection类型方法的例子中,空对象可以被用来代替null作为返回值。我打算另写一篇文章来讲空对象模式,分享几个Java空对象的例子。


这就是全部了,这是几个易于遵从的避免空指针异常的Java技巧和最佳实践。你可以欣赏到这些技巧将非常有用,且不太难实现。如果你有其他比秒这个异常的技巧,而又没包含在这里,请通过评论来和我们分享,我将收录在这里。


<think>好的,我现在需要帮助用户解决关于C++中空指针异常处理的问题。首,用户提到在Java中可以捕获空指针异常,但C++的情况不同。用户已经给出了一段测试代码,并询问在C++中如何处理类似的问题。我需要理解C++的异常处理机制,以及为什么这段代码在C++中可能无法捕获空指针异常。 首,回顾用户提供的代码示例。在C++中,解引用空指针(如*p)是未定义行为(UB),这意味着编译器不保证任何特定的行为,包括抛出异常。这与Java不同,Java在遇到空引用会明确抛出NullPointerException。因此,在C++中,尝试捕获这样的错误可能需要不同的方法。 接下来,我需要整理C++处理空指针问题的常见解决方案。用户可能想知道如何预防空指针解引用,或者在发生此类错误如何检测处理。可能的解决方法包括: 1. **防御性编程**:在解引用指针检查是否为nullptr。这是最直接方法,但需要程序员在每次使用指针都进行检查可能繁琐但有效。 2. **智能指针**:使用unique_ptr或shared_ptr等智能指针,它们可以自动管理内存,减少裸指针的使用,从而降低空指针的风险。但智能指针本身也可能持有nullptr,所以仍然需要检查。 3. **断言**:在调试阶段使用assert宏检查指针有效性,帮助在开发阶段发现问题。但发布版本中通常禁用断言,因此不作为运行处理手段。 4. **自定义异常**:主动检查指针并在发现nullptr抛出自定义异常,这样可以在上层通过try-catch块捕获。这需要显式地添加检查代码,但提供了结构化的错误处理方式。 5. **系统级信号处理**:例如,在Linux中使用信号处理函数捕获SIGSEGV信号(段错误)。但信号处理是全局的,且不可移植,不同系统可能有不同处理方式,通常不建议作为主要解决方案。 另外,需要指出C++标准中空指针解引用不会抛出异常,因此无法像Java那样通过catch(...)捕获。用户提供的示例代码中,解引用空指针导致未定义行为,程序可能直接崩溃而不是进入catch块。 在参考资料中,用户引用了关于JavaC++空指针异常的内容。需要强调C++与Java异常处理机制上的差异,避免用户混淆两者。 最后,生成相关问题要围绕用户的问题扩展,可能涉及其他类型的指针错误、智能指针的使用细节、调试技巧等。</think>### C++ 空指针异常处理解决方案 在 C++ 中,**解引用空指针是未定义行为**(Undefined Behavior),不会自动触发异常,因此无法像 Java 那样直接捕获空指针异常[^1]。但可以通过以下方法预防或处理空指针问题: --- #### 1. **防御性编程(显式检查指针)** 在解引用指针前强制检查是否为 `nullptr`: ```cpp void safe_access(int* p) { if (p == nullptr) { throw std::invalid_argument("Pointer is null!"); } int value = *p; // 安全解引用 } ``` --- #### 2. **使用智能指针(自动管理资源)** 通过 `std::unique_ptr` 或 `std::shared_ptr` 减少裸指针的使用: ```cpp #include <memory> void smart_pointer_demo() { auto ptr = std::make_unique<int>(42); if (ptr) { // 检查是否有效 int value = *ptr; // 安全访问 } } ``` --- #### 3. **断言调试(开发阶段检测)** 使用 `assert` 在调试阶段捕获空指针: ```cpp #include <cassert> void assert_demo(int* p) { assert(p != nullptr && "Pointer cannot be null!"); int value = *p; } ``` --- #### 4. **自定义异常(结构化错误处理)** 手动抛出异常并捕获: ```cpp #include <stdexcept> void custom_exception(int* p) { try { if (p == nullptr) { throw std::runtime_error("Null pointer encountered"); } int x = 100 + *p; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } } ``` --- #### 5. **系统级信号处理(不推荐)** 通过信号处理捕获段错误(不可移植): ```cpp #include <csignal> #include <iostream> void signal_handler(int signal) { std::cerr << "Segmentation fault detected!" << std::endl; exit(1); } int main() { signal(SIGSEGV, signal_handler); int* p = nullptr; int x = *p; // 触发信号处理 } ``` --- ### 关键区别:C++ 与 Java空指针处理 | 特性 | C++ | Java | |--------------------|------------------------------|-----------------------------| | 空指针解引用 | 未定义行为(程序可能崩溃) | 抛出 `NullPointerException` | | 异常捕获 | 需手动检查抛出异常 | 自动抛出并支持捕获 | | 推荐解决方案 | 智能指针 + 防御性检查 | 异常处理 + 代码规范 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值