原文:>
你这个问题的解决
问题定位:
在堆栈异常信息的第一行就可以定位到是哪里出了空指针,倘若这里不是你写的类,可以往下翻一下,找到你写的类,就是这里出现的空指针。
问题解决:
对一个空对象调用里面的方法或者属性的时候会报空指针,检查这个对象为什么是空即可。
Java 空指针异常的若干解决方案
Java 中任何对象都有可能为空,当我们调用空对象的方法时就会抛出 NullPointerException 空指针异常,这是一种非常常见的错误类型。我们可以使用若干种方法来避免产生这类异常,使得我们的代码更为健壮。本文将列举这些解决方案,包括传统的空值检测、编程规范、以及使用现代 Java 语言引入的各类工具来作为辅助。
运行时检测
最显而易见的方法就是使用 if (obj == null) 来对所有需要用到的对象来进行检测,包括函数参数、返回值、以及类实例的成员变量。当你检测到 null 值时,可以选择抛出更具针对性的异常类型,如 IllegalArgumentException,并添加消息内容。我们可以使用一些库函数来简化代码,如 Java 7 开始提供的 Objects#requireNonNull 方法:
public void testObjects(Object arg) {
Object checked = ObjectsrequireNonNull(arg, "arg must not be null");
checkedtoString();}
Guava 的 Preconditions 类中也提供了一系列用于检测参数合法性的工具函数,其中就包含空值检测:
public void testGuava(Object arg) {
Object checked = PreconditionscheckNotNull(arg, "%s must not be null", "arg");
checkedtoString();
}
我们还可以使用 Lombok 来生成空值检测代码,并抛出带有提示信息的空指针异常:
public void testLombok(@NonNull Object arg) {
argtoString();
生成的代码如下:
public void testLombokGenerated(Object arg) {
if (arg == null) {
throw new NullPointerException("arg is marked @NonNull but is null");
}
argtoString();
}
这个注解还可以用在类实例的成员变量上,所有的赋值 *** 作会自动进行空值检测。
编程规范
·通过遵守某些编程规范,也可以从一定程度上减少空指针异常的发生。
使用那些已经对 null 值做过判断的方法,如 String#equals、String#valueOf、以及三方库中用来判断字符串和集合是否为空的函数:
if (str != null && strequals("text")) {}
if ("text"equals(str)) {}
if (obj != null) { objtoString(); }
StringvalueOf(obj); // "null"
// from spring-core
StringUtilsisEmpty(str);
CollectionUtilsisEmpty(col);
// from guava
StringsisNullOrEmpty(str);
// from commons-collections4
CollectionUtilsisEmpty(col);
·如果函数的某个参数可以接收 null 值,考虑改写成两个函数,使用不同的函数签名,这样就可以强制要求每个参数都不为空了:
public void methodA(Object arg1) {
methodB(arg1, new Object[0]);
}
public void methodB(Object arg1, Object[] arg2) {
for (Object obj : arg2) {} // no null check
}
·如果函数的返回值是集合类型,当结果为空时,不要返回 null 值,而是返回一个空的集合;如果返回值类型是对象,则可以选择抛出异常。Spring JdbcTemplate 正是使用了这种处理方式:
// 当查询结果为空时,返回 new ArrayList<>()
jdbcTemplatequeryForList("SELECT FROM person");
// 若找不到该条记录,则抛出 EmptyResultDataAccessException
jdbcTemplatequeryForObject("SELECT age FROM person WHERE id = 1", Integerclass);
// 支持泛型集合
public <T> List<T> testReturnCollection() {
return CollectionsemptyList();
}
静态代码分析
Java 语言有许多静态代码分析工具,如 Eclipse IDE、SpotBugs、Checker Framework 等,它们可以帮助程序员检测出编译期的错误。结合 @Nullable 和 @Nonnull 等注解,我们就可以在程序运行之前发现可能抛出空指针异常的代码。
但是,空值检测注解还没有得到标准化。虽然 2006 年 9 月社区提出了 JSR 305 规范,但它长期处于搁置状态。很多第三方库提供了类似的注解,且得到了不同工具的支持,其中使用较多的有:
javaxannotationNonnull:由 JSR 305 提出,其参考实现为 comgooglecodefindbugsjsr305;
orgeclipsejdtannotationNonNull:Eclipse IDE 原生支持的空值检测注解;
eduumdcsfindbugsannotationsNonNull:SpotBugs 使用的注解,基于 findbugsjsr305;
orgspringframeworklangNonNull:Spring Framework 50 开始提供;
orgcheckerframeworkcheckernullnessqualNonNull:Checker Framework 使用;
androidsupportannotationNonNull:集成在安卓开发工具中;
我建议使用一种跨 IDE 的解决方案,如 SpotBugs 或 Checker Framework,它们都能和 Maven 结合得很好。
SpotBugs 与 @NonNull、@CheckForNull
SpotBugs 是 FindBugs 的后继者。通过在方法的参数和返回值上添加 @NonNull 和 @CheckForNull 注解,SpotBugs 可以帮助我们进行编译期的空值检测。需要注意的是,SpotBugs 不支持 @Nullable 注解,必须用 @CheckForNull 代替。如官方文档中所说,仅当需要覆盖 @ParametersAreNonnullByDefault 时才会用到 @Nullable。
官方文档 中说明了如何将 SpotBugs 应用到 Maven 和 Eclipse 中去。我们还需要将 spotbugs-annotations 加入到项目依赖中,以便使用对应的注解。
<dependency><groupId>comgithubspotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>317</version>
</dependency>
以下是对不同使用场景的说明:
@NonNullprivate Object returnNonNull() {
// 错误:returnNonNull() 可能返回空值,但其已声明为 @Nonnull
return null;
}
@CheckForNull
private Object returnNullable() {
return null;
}
public void testReturnNullable() {
Object obj = returnNullable();
// 错误:方法的返回值可能为空
Systemoutprintln(objtoString());
}
private void argumentNonNull(@NonNull Object arg) {
Systemoutprintln(argtoString());
}
public void testArgumentNonNull() {
// 错误:不能将 null 传递给非空参数
argumentNonNull(null);
}
public void testNullableArgument(@CheckForNull Object arg) {
// 错误:参数可能为空
Systemoutprintln(argtoString());
}
对于 Eclipse 用户,还可以使用 IDE 内置的空值检测工具,只需将默认的注解 orgeclipsejdtannotationNullable 替换为 SpotBugs 的注解即可:
Checker Framework 与 @NonNull、@Nullable
Checker Framework 能够作为 javac 编译器的插件运行,对代码中的数据类型进行检测,预防各类问题。我们可以参照 官方文档,将 Checker Framework 与 maven-compiler-plugin 结合,之后每次执行 mvn compile 时就会进行检查。Checker Framework 的空值检测程序支持几乎所有的注解,包括 JSR 305、Eclipse、甚至 lombokNonNull。
import orgcheckerframeworkcheckernullnessqualNullable;@Nullable
private Object returnNullable() {
return null;
}
public void testReturnNullable() {
Object obj = returnNullable();
// 错误:obj 可能为空
Systemoutprintln(objtoString());
}
Checker Framework 默认会将 @NonNull 应用到所有的函数参数和返回值上,因此,即使不添加这个注解,以下程序也是无法编译通过的:
private Object returnNonNull() {// 错误:方法声明为 @NonNull,但返回的是 null。
return null;
}
private void argumentNonNull(Object arg) {
Systemoutprintln(argtoString());
}
public void testArgumentNonNull() {
// 错误:参数声明为 @NonNull,但传入的是 null。
argumentNonNull(null);
}
Checker Framework 对使用 Spring Framework 50 以上的用户非常有用,因为 Spring 提供了内置的空值检测注解,且能够被 Checker Framework 支持。一方面我们无需再引入额外的 Jar 包,更重要的是 Spring Framework 代码本身就使用了这些注解,这样我们在调用它的 API 时就能有效地处理空值了。举例来说,StringUtils 类里可以传入空值的函数、以及会返回空值的函数都添加了 @Nullable 注解,而未添加的方法则继承了整个框架的 @NonNull 注解,因此,下列代码中的空指针异常就可以被 Checker Framework 检测到了:
// 这是 spring-core 中定义的类和方法public abstract class StringUtils {
// str 参数继承了全局的 @NonNull 注解
public static String capitalize(String str) {}
@Nullable
public static String getFilename(@Nullable String path) {}
}
// 错误:参数声明为 @NonNull,但传入的是 null。
StringUtilscapitalize(null);
String filename = StringUtilsgetFilename("/path/to/file");
// 错误:filename 可能为空。
Systemoutprintln(filenamelength());
Optional 类型
Java 8 引入了 Optional<T> 类型,我们可以用它来对函数的返回值进行包装。这种方式的优点是可以明确定义该方法是有可能返回空值的,因此调用方必须做好相应处理,这样也就不会引发空指针异常。但是,也不可避免地需要编写更多代码,而且会产生很多垃圾对象,增加 GC 的压力,因此在使用时需要酌情考虑。
Optional<String> opt;// 创建
opt = Optionalempty();
opt = Optionalof("text");
opt = OptionalofNullable(null);
// 判断并读取
if (optisPresent()) {
optget();
}
// 默认值
optorElse("default");
optorElseGet(() -> "default");
optorElseThrow(() -> new NullPointerException());
// 相关 *** 作
optifPresent(value -> {
Systemoutprintln(value);
});
optfilter(value -> valuelength() > 5);
optmap(value -> valuetrim());
optflatMap(value -> {
String trimmed = valuetrim();
return trimmedisEmpty() Optionalempty() : Optionalof(trimmed);
});
方法的链式调用很容易引发空指针异常,但如果返回值都用 Optional 包装起来,就可以用 flatMap 方法来实现安全的链式调用了:
String zipCode = getUser()flatMap(User::getAddress)
flatMap(Address::getZipCode)
orElse("");
Java 8 Stream API 同样使用了 Optional 作为返回类型:
stringListstream()findFirst()orElse("default");stringListstream()
max(ComparatornaturalOrder())
ifPresent(Systemout::println);
此外,Java 8 还针对基础类型提供了单独的 Optional 类,如 OptionalInt、OptionalDouble 等,在性能要求比较高的场景下很适用。
其它 JVM 语言中的空指针异常
Scala 语言中的 Option 类可以对标 Java 8 的 Optional。它有两个子类型,Some 表示有值,None 表示空。
val opt: Option[String] = Some("text")optgetOrElse("default")
除了使用 Option#isEmpty 判断,还可以使用 Scala 的模式匹配:
opt match {case Some(text) => println(text)
case None => println("default")
Scala 的集合处理函数库非常强大,Option 则可直接作为集合进行 *** 作,如 filer、map、以及列表解析(for-comprehension):
optmap(_trim)filter(_length > 0)map(_toUpperCase)getOrElse("DEFAULT")val upper = for {
text <- opt
trimmed <- Some(texttrim())
upper <- Some(trimmed) if trimmedlength > 0
} yield upper
uppergetOrElse("DEFAULT")
Kotlin 使用了另一种方式,用户在定义变量时就需要明确区分 可空和不可空类型。当可空类型被使用时,就必须进行空值检测。
var a: String = "text"a = null // 错误:无法将 null 赋值给非空 String 类型。
val b: String = "text"
// 错误: *** 作可空类型时必须使用安全 *** 作符()或强制忽略(!!)。
println(blength)
val l: Int = blength // 安全 *** 作
b!!length // 强制忽略,可能引发空值异常
Kotlin 的特性之一是与 Java 的可互 *** 作性,但 Kotlin 编译器无法知晓 Java 类型是否为空,这就需要在 Java 代码中使用注解了,而 Kotlin 支持的 注解 也非常广泛。Spring Framework 50 起原生支持 Kotlin,其空值检测也是通过注解进行的,使得 Kotlin 可以安全地调用 Spring Framework 的所有 API。
结论
在以上这些方案中,我比较推荐使用注解来预防空指针异常,因为这种方式十分有效,对代码的侵入性也较小。所有的公共 API 都应该使用 @Nullable 和 @NonNull 进行注解,这样就能强制调用方对空指针异常进行预防,让我们的程序更为健壮。
因为你创建了一个类的对象引用,然而这个对象引用却指向空的实体。就好比你有一个Sony电视的遥控器,但是你家里没有对应这个遥控器的Sony电视机,所以系统就会报错。解决办法就是你得先买对应型号的Sony电视机,换句话说你得new一个实体对象出来,然后用“=”,指向你的对象应用。例如SonyTV a = new SonyTV()。 a是对象引用,而new SonyTV()就是创建一个SonyTV()类的实体对象,然后把这个实体对象用等号与a关联起来。空指针异常是运行期的异常,编译的时候是不会检查的,只有运行的时候才会抛出,一般情况下如果出现了空指针异常,事先你不知道会有这个异常,就说明你的程序有bug,当然就要找出这个bug,然后在变量调用之前先给它赋值,另外有一种情况,你事先知道某个 *** 作会抛出空指针异常,常见一些非法的 *** 作,但是又无法避免用户执行这个 *** 作,就可以通过try,catch块捕获这个异常,然后给用户一个反馈信息,告知这个 *** 作是非法的,因为如果你直接抛出异常的话,用户可能会看不懂,比如抛出了javalangnullpointexception,一般的软件使用者是看不懂的,你试想一下如果你在玩一个游戏的时候,忽然之间抛出了一大堆异常,你肯定会埋怨这个游戏垃圾,竟然会出现这样的低级错误。一、空指针异常产生的主要原因如下:
1、当一个对象不存在时又调用其方法会产生异常objmethod() // obj对象不存在;
2、当访问或修改一个对象不存在的字段时会产生异常objmethod() // method方法不存在;
3、字符串变量未初始化;
4、接口类型的对象没有用具体的类初始化。
二、实现思路:
1、利用空指针异常的表现之一:对空字符串进行 *** 作。
2、设置一个空的字符串,然后对其进行查找 *** 作(可以使用charAt()方法)
3、对将要出现的异常进行捕获
4、打印到控制台
//java代码public class NullPointerExceptionTest {
/
实现思路:
1空指针异常的表现之一:对空的字符串进行了 *** 作
2设置一个空的字符串,然后对其进行查找 *** 作(可以使用charAt()方法)
3对将要出现的异常进行捕获
4打印到控制台
/
public static void main(String[] args) {
// TODO Auto-generated method stub
//定义一个空的字符串
String str = null;
//尝试对空字符串进行 *** 作,并捕获异常,输出异常
try{
Systemoutprintln(strcharAt(0));
}catch(Exception e){
Systemoutprintln("异常信息:");
eprintStackTrace();
}
}
}
运行结果图:
程序开发过程中的空指针异常会经常出现,要尽量保持逻辑清晰,能够及时排查错误代码,对可能发生的异常进行捕获,不要影响主程序运行。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)