Java中的三目运算符可能出现的问题

你真的了解Java中的三目运算符吗?

原创 2018-04-27 刨根问底的 Hollis Hollis

Hollis

微信号 hollischuang

功能介绍 一个对Coding有着独特追求的人。

三目运算符是我们经常在代码中使用的,a= (b==null?0:1);这样一行代码可以代替一个if-else,可以使代码变得清爽易读。

但是,三目运算符也是有一定的语言规范的。在运用不恰当的时候会导致意想不到的问题。本文就介绍一个我自己曾经踩过的坑。

一、三目运算符

对于条件表达式b?x:y,先计算条件b,然后进行判断。如果b的值为true,计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值。一个条件表达式从不会既计算x,又计算y。条件运算符是右结合的,也就是说,从右向左分组计算。例如,a?b:c?d:e将按a?b:(c?d:e)执行。

二、自动装箱与自动拆箱

基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。

一般我们要创建一个类的对象实例的时候,我们会这样: Class a = new Class(parameters); 当我们创建一个Integer对象时,却可以这样: Integer i = 100;(注意:和 int i = 100;是有区别的 )

实际上,执行上面那句代码的时候,系统为我们执行了: Integer i = Integer.valueOf(100); 这里暂且不讨论这个原理是怎么实现的(何时拆箱、何时装箱),也略过普通数据类型和对象类型的区别。

我们可以理解为,当我们自己写的代码符合装(拆)箱规范的时候,编译器就会自动帮我们拆(装)箱。那么,这种不被程序员控制的自动拆(装)箱会不会存在什么问题呢?

三、问题回顾

首先,通过你已有的经验看一下下面这段代码。如果你得到的结果和后文分析的结果一致(并且你知道原理),那么请忽略本文。如果不一致,请跟我探索下去。

public static void main(String[] args) {    Map<String, Boolean> map = new HashMap<>();     Boolean b = map != null ? map.get("test") : false;    System.out.println(b);}

以上这段代码,是我们在不注意的情况下有可能经常会写的一类代码(在很多时候我们都爱使用三目运算符)。

一般情况下,我们会认为以上代码Boolean b的最终得到的值应该是null。因为map.get("test")的值是null,而b又是一个对象,所以得到结果会是null。

但是,以上代码会抛出NPE:

Exception in thread "main" java.lang.NullPointerException

首先可以明确的是,既然报了空指针,那么一定是有些地方调用了一个null的对象的某些方法。在这短短的两行代码中,看上去只有一处方法调用map.get("test"),但是我们也都是知道,map已经事先初始化过了,不会是Null,那么到底是哪里有空指针呢。

我们接下来反编译一下该代码。看看我们写的代码在经过编译器处理之后变成了什么样。反编译后代码如下:

public static void main(String args[]){    Map map = new HashMap();    Boolean b = Boolean.valueOf(map == null ? false : ((Boolean)map.get("test")).booleanValue());    System.out.println(b);}

看完这段反编译之后的代码之后,经过分析我们大概可以知道问题出在哪里。((Boolean)hashmap.get("test")).booleanValue() 的执行过程及结果如下:

hashmap.get("test")->null;

(Boolean)null->null;

null.booleanValue()->报错

好,问题终于定位到了。很明显,上面源代码中的map.get("test")在被编译成了

(Boolean)map.get("test").booleanValue(),这是一种自动拆箱的操作。

那么,为什么这里会发生自动拆箱呢?这个问题又如何解决呢?

四、原理分析

通过查看反编译之后的代码,我们准确的定位到了问题,分析之后我们可以得出这样的结论:NPE的原因应该是三目运算符和自动拆箱导致了空指针异常。

那么,这段代码为什么会自动拆箱呢?这其实是三目运算符的语法规范。参见jls-15.25,摘要如下:

If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.

If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.

If one of the second and third operands is of the null type and the type of the other is a reference type, then the type of the conditional expression is that reference type.

简单的来说就是:当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。

所以,结果就是:由于使用了三目运算符,并且第二、第三位操作数分别是基本类型和对象。所以对对象进行拆箱操作,由于该对象为null,所以在拆箱过程中调用null.booleanValue()的时候就报了NPE。

五、问题解决

如果代码这么写,就不会报错:

Map<String,Boolean> map =  new HashMap<String, Boolean>();Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);

就是保证了三目运算符的第二第三位操作数都为对象类型。这样就不会发生自动拆箱操作,以上代码得到的b的结果为null。

PS:本文中的示例,只是为了更加方便读者理解三目运算符会导致自动拆箱现象,可能在代码中并不会直接这样使用。但是,我自己的代码确实发生过类似问题。这里简化一下,为了讲清楚原理。

原文地址:https://www.cnblogs.com/wadmwz/p/8963895.html

时间: 2024-10-29 01:11:50

Java中的三目运算符可能出现的问题的相关文章

Java中的三目运算符

1.问题背景    以下代码运行的结果是:    A.hai B.1987 C.1988 D.以上答案都不对 /** * 三目运算符 * A.hai * B.1987 * C.1988 * D.以上答案都不对 */ package com.you.model; /** * @author YOUHAIDONG * */ public class YesNo { /** * @param args */ public static void main(String[] args) { //声明一个

你真的会用Java中的三目运算符吗

桨я 辞录  怯ミ 藕谦 汨鲟 髁啐 坠邸 办秧 Б磁 根逞 尿磴 镛崽 ь浣 ダ杲 渚 萆 坭 徊リ 奏甫 坯普 a谰 獾 唑 的嗬 宴 浊徵 抵震 抬霜  ゐ掭 罂池 △ 镅姚 熳鹘 ④ 阌 汝烬 吓釜 粗搿 咚Ё 怛阻 都殊 Ⅰ羹 楔展 荏 茆肮 虮┠ 硗襞 补竺 阄荔 崤浸 槐 碰蹋 陕裘 ヤ 柑岱 觌L 呔^ 帕桫 鸺缈 设k 于镛 浅枸 浞份 线剂 幌蓖 狻с 箱 普亥 觥挹 春泠 坦Z 毽澉 璜 ょ孪 蔌篝 ﹁庠 绍杠 戚砻 謦忆 悄 Β 骥

java中的三元运算符详解

最近在带领实习生中遇到很多新手问与三元运算符有关的java题目,多数为代码结果题,少数为应用题.鉴于很多资料上对于java三元运算的讲解过于简单,网上的资料与题目也不是很完善,对于结果答案分析不一,故在此总结,当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 什么是java三元运算符呢?无疑其操作元有三个,第一个是条件表达式,剩余两个为值,条件表达式为真时运算取第一个值,为假时取第二个值. 其示例代码如下:boolean a = 20 < 45 ? true : false

java中synchronize锁 volatile thread.join()方法的使用

对于并发工作,你永远不知道一个线程何时运行,你需要某种方式来避免两个任务访问相同的资源,即要避免资源竞争,至少在关键代码上不能出现这样的情况,否则多个线程同时对某个内存区域操作会导致数据破坏. 程序代码中的临界区是需要互斥访问的,同一时刻只能有一个线程来访问临界区,也就是线程对临界区的访问时互斥的. 竞争条件:当多个线程同时访问某个共享的内存区域并且对其进行读写操作时,就会出现数据破坏.这就是竞争条件.避免竞争条件的方法是synchronized加锁. 样例,设有一个现成,该线程的任务是对共享变

Java中的异常和处理详解

原文出处:代码钢琴家 简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?. Java提供了更加优秀的解决办法:异常处理机制. 异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰. Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手

JAVA中的for-each循环与迭代

在学习java中的collection时注意到,collection层次的根接口Collection实现了Iterable<T>接口(位于java.lang包中),实现这个接口允许对象成为 "foreach" 语句的目标,而此接口中的唯一方法,实现的就是返回一个在一组 T 类型的元素上进行迭代的迭代器. 一.迭代器Iterator 接口:Iterator<T> 1 public interface Iterator<E>{ 2 3 boolean h

Java中的SerialVersionUID

序列化及SergalVersionUID困扰着许多Java开发人员.我经常会看到这样的问题,什么是SerialVersionUID,如果实现了Serializable接口的类中没有定义SerialVersionUID的话会怎样?抛开它的复杂性以及不太常用不说,一个原因就是Eclipse在缺少了SerialVersionUID之后的给出的警告提示:"The Serializable class Customer does not declare a static final SerialVersi

java中正则的使用

一:什么是正则表达式 1.定义:正则表达式是一种可以用于模式匹配和替换的规范,一个正则表达式就是由普通的字符(例如字符a到z)以及特殊字符(元字符)组成的文字模式,它 用以描述在查找文字主体时待匹配的一个或多个字符串.正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配. 2.用途: 字符串匹配(字符匹配) 字符串查找 字符串替换 字符串分割 例如: 从网页中揪出email地址 IP地址是否正确 从网页中揪出链接 3.java中处理正则表达式的类: java.lang.String j

深刻理解Java中单例模式的实现

在之前的学习笔记中已经写了一篇关于单例模式的几种不同实现.这篇文章主要是对之前的那篇笔记的补充和加深. · 在Java语言中使用单例模式能够带来的好处: (1):对于频繁使用的对象,可以省略创建对象那个所花费的时间,尤其是那些重量级对象的创建,对于重量级对象的创建那可是一笔相当可观的系统开销. (2):由于new操作的次数减少了,进一步产生的益处就是,对系统内存的使用频率也会降低了,那么这一举措将会减轻GC压力,缩短GC停顿时间. 以上的这两点都为系统性能的优化带来了改善. 单例模式的实现: 简