从刚开始学编程就经常会看到许多地方有类似 if(XX==null)之类的语句,总觉得有点不解,直到现在开始参与项目才懂得非空判断的意义与重要性,觉得自己对null
和其他空值的理解太过模糊,于是系统的学习了一下,写个总结,希望可以对有同样需求的人有一定参考作用。
- 1.空值有哪些类型
- 2.为什么要做空判断
- 3.用return减少嵌套
1、空值有哪些类型
一般来说空值是指null,但是只判断null并不能解决所有问题,除了常见的空字符串,长度为0数组,还有其他一些基本数据类型的默认值等。所以这里说的空值并不仅限严格意义上的空值,包括了其他可能代表无数据、没有值或者需要判断的情况。
1.1基本数据类型
对于八种基本类型来说去判断==null是错误的,因为基本类型的取值范围并没有null,即使声明后没有进行赋值Java也会根据不同类型给予初始值,整数类型int、byte、short、long的默认值为0,带小数点的float、double默认值为0.0,boolean的默认值为false,char的默认值是‘’。
所以判断的参数是八种基本类型时应该注意不能判断==null,应该根据类型选择对比的值,虽然直接使用这些默认值不像null会引起空指针异常,但是作为数据是否被有效赋值的判断在很多情况下也是必要的。
1.2 引用数据类型
当我们声明一个非基本数据类型变量,而不进行赋值的情况下,java会自动赋值null。所以当我们去调用这个未赋值变量的方法,或者访问其内部变量的时候就会抛出空指针异常,也是最常见需要if(xxx==null)
语句的情况。比较值得注意的是有一些具有内部对象的模型类,要考虑到内部对象是否为null的情况。
1.2.1、 String
String虽然也是继承自Object,默认值也为null,但是对于常见的应用场景,空串""
也是我们应该进行判断的情况。可以用:
if(s == null || s.length() <= 0){......} //s是一个String类型
进行判断,
需要注意s == null
是必要且必须在前面的,否则会因为null调用方法而抛出空指针异常。
1.3 容器类
1.3.1、 数组
基本上有4种情况:
- 1 声明但不定义长度,值为null
- 2 长度为0数组,不为null,但注意不可直接取出元素,会抛出索引越界错误(ArrayIndexOutOfBoundsException)
- 3 基本类型数组声明且定义长度,但不赋值,所有元素值为该基本类型默认值
- 4 引用类型数组声明且定义长度,但不赋值,所有元素为null
int[] arrayNull; //值为null
int[] arrayZero = new int[0]; //值为{}
int[] arrayInt = new int[2]; //值为{0,0}
Object[] arrayObj = new Object[2]; //值为{null,null}
1.3.2、 集合
- 不赋值的情况下为null
- List:允许重复元素,可以加入任意多个null。
- Set:不允许重复元素,最多可以加入一个null。
- Map:Map的key最多可以加入一个null,value字段没有限制。
常见数据类型对应需要判断的空值
(其实我只是想试试用markdown画表而已.......)
类型 | 所属 | 空值、默认值或可能需要处理的值 |
---|---|---|
int | 基本数据类型 | 0 |
float、double | 基本数据类型 | 0.0 |
String | 引用数据类型 | null、"" |
数组 | 容器类 | null、{}、{null}、{0,0} 等等 |
List | 容器类 | null、内部元素为null |
2、为什么要做空判断
往大了说就是让程序具有健壮性还有防御性编程之类的大东西了,但是作为新手对这些东西理解过于模糊,所以结合项目实际分析,列出以下几点:
2.1、避免空指针异常
在定义变量而不赋值的情况下,除了基本数据类型,其他类型变量都会默认为null。
null也可以认为是除基本数据类型外其他类型的默认值,所以一个含参数的方法,只要参数不为基本数据类型,在调用时都可以填入null为参数。
public void funcString(String s) {
s.isEmpty();
}
//在main方法中调用
funcString(null); //参数传入null IDE并不会报错 可以进行编译
//运行后会抛出NullPointerException
可以看出null作为参数传入方法,但是一旦传入null,内部相当于进行了这一过程null.isEmpty();
报错也就很正常了。
所以类似这种需要对对象的方法进行方法调用的情况加一个非空判断就非常必要了。
再举一个比较实际的例子:
假设服务器返回这样的一段json数据
{
"id": 5,
"title": "我是文章标题",
"comment": {
"userid": 4,
"content": "我是评论内容~~~"
}
}
现在把它映射成这样的一个对象
public class Article {
private int id; //文章id
private String title;//文章标题
private Comment comment;//文章评论
class Comment {
//内部对象“评论”
private int userId;//评论用户Id
private String content;//评论内容
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Comment getComment() {
return comment;
}
public void setComment(Comment comment) {
this.comment = comment;
}
}
以安卓最基本的控件TextView为例,我们现在把文章评论的内容显示出来
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
String commentContent=getArticle().getComment().getContent();
textView.setText(commentContent);
}
public Article getArticle(){
//获取数据 映射成Article对象 并返回
}
以上的例子并没有加入非空判断,而文章评论也顺利的显示出来了
但是,如果一篇刚发布的文章,看到的人可能都没几个,又怎么会有评论呢?服务器返回的数据很可能是这样的:
{
"id": 6,
"title": "我是文章标题",
"comment": {
}
}
这时这段代码
String commentContent=getArticle().getComment().getContent();
运行时就变成了
String commentContent=null.getContent();
所以必须加入非空判断:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
if (getArticle().getComment()!=null){
String commentContent=getArticle().getComment().getContent();
textView.setText(commentContent);
}
}
2.2、增加程序性能
同样以上面的例子来说,这段代码
String commentContent=getArticle().getComment().getContent();
也可以通过try catch语句来捕捉空指针异常并进行处理
但是通过非空判断的方式,当程序运行到if (getArticle().getComment()!=null)
,就不再继续进行,从性能的角度说也更合适。
3、用return减少嵌套
使用非空判断的时候因为要使用到if--else语句,不免要多出一些嵌套,当逻辑比较简单的时候还好,如果复杂一点的逻辑就可能要使用到多重嵌套,看着一堆迷之缩进是很影响代码可读性的,所以从小养成用return语句减少嵌套的习惯是很重要的。
继续优化上面那段代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
if (getArticle().getComment() == null) return; //把判断条件改为相反 一旦符合return结束方法
String commentContent = getArticle().getComment().getContent();
textView.setText(commentContent);
}
使用return后少了一对花括号,在这个例子里可能看不出太大好处,但是在一些复杂判断多重嵌套下,使用return可以明显提升代码可读性。
另外需要用else的时候也可以这样:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
if (getArticle().getComment() == null) {
//把else里的语句 放到里面 最后加上return 跳过后面语句
textView.setText("暂时没有评论");
return;
}
String commentContent = getArticle().getComment().getContent();
textView.setText(commentContent);
}
总结
主要还是理清你写的方法接收的参数或者是Api接收的数据是什么类型,有哪些可能性。然后对症下药去写判断就行啦~~