暗藏杀机? 不简单的三目运算符号!

别再瞎移位了!Verilog有符号定点数右移的正确姿势与避坑指南 本文深入解析Verilog中有符号定点数右移操作的常见陷阱与正确实现方法。通过对比逻辑右移与算术右移的区别,结合补码原理和Vivado实战案例,提供避免负数变正问题的解决方案,并分享移位在乘除运算中的应用技巧与防坑指南。 阅读详情

最近,在一个业务改造中,使用三目运算符重构了业务代码,没想到测试的时候竟然发生 NPE (Null Pointer Exception)的问题。

image

重构代码非常简单,代码如下:

// 方法返回参数类型为 Integer
//  private Integer code;
SimpleObj simpleObj = new SimpleObj();
// 其他业务逻辑
if (simpleObj == null) {
    return -1;
} else {
    return simpleObj.getCode();
}

这段 if 判断,感觉很是繁琐,于是使用三目运算符重构了一把,代码如下:

// 方法返回参数类型为 Integer
SimpleObj simpleObj = new SimpleObj();
// 其他业务逻辑
return simpleObj == null ? -1 : simpleObj.getCode();

测试的时候,第四行代码抛出了空指针,这里代码很简单,显然只有 simpleObj或simpleObj.getCode()才有可能发生 NPE 问题。

但是我明明为 simpleObj做过判空判断,simpleObj 对象肯定不是 null,那么只有 simpleObj.getCode() 返回为 null。但是我的代码并没有对这个方法返回值做任何操作,为何会触发 NPE?

难道是又是自动拆箱导致的 NPE 问题?

在解答这个问题之前,我们首先复习一下三目运算符。

三目运算符

三目运算符,官方英文名称:Conditional Operator ? :,中文直译条件表达式,本文不纠结名称,统一使用三目运算符。

三目运算符的基本用法非常简单,它由三个操作数的运算符构成,形式为:

<表达式 1> ?<表达式 2> : <表达式 3>

三目运算符的计算从左往右计算,首先需要计算计算表达式 1 ,其结果类型必须为 Boolean 或 boolean,否则发生编译错误。

当表达式 1 的结果为 true,将会执行表达式 2,否则将会执行表达式 3。

表达式 2 与表达式 3 最后的类型必须得有返回结果,即不能为是 void,若为 void ,编译时将会报错。

最后需要注意的是,表达式 2 与表达式 3 不会被同时执行,两者只有一个会被执行。

踩坑案例

了解完三目运算符的基本原理,我们简化一下开头例子,复现一下三目运算符使用过程的一些坑。假设我们的例子简化成如下:

//设置成true,保证表达式 2 被执行
boolean flag = true; 
int simpleInt = 66;
Integer nullInteger = null;

案例 1

第一个案例我们根据如下计算 result 的值。

int result = flag ? nullInteger : simpleInt;

这个案例为开头的例子的简化版本,运算上述代码,将会发生 NPE 的。

为什么会发发生 NPE 呢?

这里可以给大家一个小技巧,当我们从代码上没办法找到答案时,我们可以试试查看一下编译之后字节码,或许是 Java 编译之后增加某些东西,从而导致问题。

使用 javap -s -c class 查看 class 文件字节码,如下:

image

可以看到字节码中加入一个拆箱操作,而这个拆箱只有可能发生在 nullInteger。

那么为什么 Java 编译器在编译时会对表达式进行拆箱?难道所有数字类型的包装类型都会进行拆箱吗?

三目运算符表达式发生自动拆箱,其实官方在 「The Java Language Specification(简称:JLS)」15.25 节[1]中做出一些规定,部分内容如下:

JDK7 规范

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.

用大白话讲,如果表达式 2 与表达式 3 类型相同,那么这个不用任何转换,三目运算符表达式结果当然与表达式 2,3 类型一致。

当表达 2 或表达式 3 其中任一一个是基本数据类型,比如 int,而另一个表达式类型为包装类型,比如 Integer,那么三目运算符表达式结果类型将会为基本数据类型,即 int。

ps:有没有疑问?为什么不规定最后结果类型都为包装类那?

这是 Java 语言层面一种规范,但是这个规范如果强制让程序员执行,想必平常使用三目运算符将会比较麻烦。所以面对这种情况, Java 在编译器在编译过程加入自动拆箱进制。

所以上述代码可以等同于下述代码:

int result = flag ? nullInteger.intValue() : simpleInt;

如果我们一开始的代码如上所示,那么这里错误点其实就很明显了。

案例 2

接下来我们在第一个案例基础上修改一下:

//设置成true,保证表达式 2 被执行
boolean flag = true; 
int simpleInt = 66;
Integer nullInteger = null;
Integer objInteger = Integer.valueOf(88);
int result = flag ? nullInteger : objInteger;

运行上述代码,依然会发生 NPE 的问题。当然这次问题发生点与上一个案例不一样,但是错误原因却是一样,还是因为自动拆箱机制导致。

这一次表达式 2 与表达式 3 都为包装类 Integer,所以三目运算符的最后结果类型也会是 Integer。

但是由于 result是 int 基本数据类型,好家伙,数据类型不一致,编译器将会对三目运算符的结果进行自动拆箱。由于结果为 null,自动拆箱将报错了。

上述代码等同为:

int result = (flag ? nullInteger : objInteger).intValue();

案例 3

我们再稍微改造一下案例 1 的例子,如下所示:

//设置成true,保证表达式 2 被执行
boolean flag = true;
int simpleInt = 66;
Integer nullInteger = null;
Integer result = flag ? nullInteger : simpleInt;

案例 3 与案例 1 右边部分完全相同,只不过左边部分的类型不一样,一个为基本数据类型 int,一个为 Integer。

按照案例 1 的分析,这个也会发生 NPE 问题,原因与案例 1 一样。

这个之所以拿出来,其实想说下,上述三目运算符的结果为 int 类型,而左边类型为 Integer,所以这里将会发生自动装箱操作,将 int类型转化为 Integer。

上述代码等同为:

Integer result = Integer.valueOf(flag ? nullInteger.intValue() : simpleInt);

案例 4

最后一个案例,与上面案例都不一样,代码如下:

//设置成true,保证表达式 2 被执行
boolean flag = true; 
Integer nullInteger = null;
Long objLong = Long.valueOf(88l);

Object result = flag ? nullInteger : objLong;

运行上述代码,依然将会发生 NPE 的问题。

这个案例表达式 2 与表达式 3 类型不一样,一个为 Integer,一个为 Long,但是这两个类型都是 Number的子类。

面对上述情况,JLS 规定:

Otherwise, binary numeric promotion (§5.6.2[2]) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.

Note that binary numeric promotion performs value set conversion (§5.1.13[3]) and may perform unboxing conversion (§5.1.8[4]).

大白话讲,当表达式 2 与表达式 3 类型不一致,但是都为数字类型时,低范围类型将会自动转为高范围数据类型,即向上转型。这个过程将会发生自动拆箱。

Java 中向上转型并不需要添加任何转化,但是向下转换必须强制添加类型转换。

上述代码转化比较麻烦,我们先从字节码上来看:image

第一步,将 nullInteger拆箱。

第二步,将上一步的值转为 long 类型,即 (long)nullInteger.intValue()。

第三步,由于表达式 2 变成了基本数据类型,表达式 3 为包装类型,根据案例 1 讲到的规则,包装类型需要转为基本数据类型,所以表达式 3 发生了拆箱。

第四步,由于三目运算符最后的结果类型为基本数据类型:long,但是左边类型为 Object,这里就需要把 long 类型装箱转为包装类型。

所以最后代码等同于:

Object result = Long.valueOf(flag ? (long)nullInteger.intValue() : objLong.longValue());

总结

看完上述四个案例,想必大家应该会有种感受,没想到这么简单的三目运算符,既然暗藏这么多「杀机」。

不过大家也不用过度害怕,不使用三目运算符。只要我们在开发过程重点注意包装类型的自动拆箱问题就好了,另外也要注意三目运算符的计算结果再赋值的时候自动拆箱引发的 NPE 的问题。

最好大家在开发过程中,都遵守一定的规范,即保持表达式 2 与表达式 3 的类型一致,不让 Java 编译器有自动拆箱的机会。

建议大家没事经常看下阿里出品的『Java 开发手册』,在最新的「泰山版」就增加三目运算符的这一节规范。

image

最后一定要做好的单元测试,不要惯性思维,觉得这么简单的一个东西,看起来根本不可能出错的。

本文选自公众号 “java极客技术”

请关注我的订阅号

订阅号.png

​

从一道BUUCTF旧题pwn2_sctf_2016,聊聊整数符号性漏洞的利用与防护 本文深入分析了CTF竞赛中的经典题目pwn2_sctf_2016,探讨了整数符号性漏洞的原理与利用方法。通过详细讲解有符号与无符号整数的二进制本质、漏洞利用链构建及现代防护机制的绕过技巧,帮助读者全面理解这一常见漏洞类型。同时提供了从安全编码到编译器辅助防御的实用防护建议,适合二进制安全研究人员和CTF选手参考学习。 阅读详情

相关推荐

浮点运算验证:形式化方法与RTL黄金参考模型实践

浮点运算验证是芯片设计中的关键挑战,涉及IEEE-754标准下的复杂运算规则和边界条件处理。传统仿真方法难以覆盖所有场景,而形式化验证通过数学证明确保设计正确性。本文重点探讨RTL-to-RTL验证方法,通过构建周期精确的SystemVerilog黄金参考模型,解决C参考模型存在的语义鸿沟问题。该方法结合分层验证策略和AI生成验证属性,显著提升验证效率。在7nm GPU项目中,验证周期从8周缩短至2周,并发现多个深层次bug,展示了形式化方法在浮点运算验证中的工程价值。

weixin_30684743的博客 801

三目运算符_不是吧,还有人不知道三目运算符的BUG

三目运算符一直是众多开发者信手拈来的一种写法,它简化了if-else的臃肿的写法,而是用一行代码替代,就感觉无形之中秀了一把。殊不知,这么帅气的代码也暗藏着一个BUG。缘由头天晚上发布了一个功能,本以为是波澜不惊的一个需求,结果第二天kibana打出了成吨的NPE日志。这些NPE日志大多都不约而同都指向了我写的一行代码,我立马推了下我的眼镜,开始排查起来了。问题代码Kibana的堆栈日志定位在第8...

weixin_34359680的博客 170

Verilog里signed和unsigned的坑,我踩了!手把手教你用$signed()函数避坑

本文深入探讨了Verilog中signed和unsigned类型的常见陷阱,通过实际案例展示了未声明常量、右值运算类型判定和自动扩位机制等问题。重点介绍了$signed()函数的应用技巧,帮助数字IC工程师避免类型转换错误,提升代码可靠性。

weixin_31714129的博客 332

C语言三目运算符之坑

代码片段: #include <stdio.h> int main() { int a = -22; unsigned int b = 0; unsigned int c = 4294967274; ssize_t t = (a == 0 ? b : a); ssize_t tc = a; printf("t = %zd tc = %zd\n", t, tc); ...

qq_25468687的博客 1453

CPRI原理及应用--帧结构

对于CPRI 控制字,协议将1个超帧(256基本帧)的控制数据划分为一个“子通道”,将子通道内超帧内的控制字进行行列划分,分为4列X64行,每64个基本帧组成1列。如下图所示。每个Xs子通道内Ns的定义基本相同,在做控制字设计时,主要已Xs和Ns和X.Y.Z开展控制字映射。

u014035968的博客 2325

脑电数据预处理十四:主成分分析(PCA)用于伪迹去除与降维

本文探讨了主成分分析(PCA)在脑电图(EEG)预处理中的双重应用。针对EEG数据的高维特性和伪迹干扰问题,PCA既可有效去除高振幅伪迹(如眼动、心电等),又能实现数据降维。文章详细解析了PCA的核心原理,包括正交基向量投影和方差最大保留机制,并提供了基于MNE-Python的完整实现方案。通过代码示例展示了PCA在伪迹去除和降维中的具体应用效果,包括功率谱密度和时域波形的可视化对比。虽然ICA在复杂伪迹处理上更具优势,但PCA以其算法简单、计算高效的特点,成为EEG分析流程中不可或缺的基础工具。

qq_32516809的博客 1417

Java中三目运算符的问题

以下内容首发于我的个人博客网站: http://riun.xyz 在Java的三目运算符中,当存在运算时,如果返回的是Integer类型就会触发拆箱,此时如果返回的Integer是null,那么就会抛出NullPoint异常(拆箱,将null转化为数字,无法转化)。 package Test; /** * @author: HanXu * on 2020/4/22 * Class description: */ public class Demo4 { /** 三目运算符:

hanhanhanxu的博客 377

APB Timer验证环境搭建——学习笔记(6)

(1)为RELOAD VALUE配置初始值32'h8000_0000,设定边界值32'h4000_0000,可以看到减到想要的边界值的时候,counter重新加载RELOAD VALUE,重新开始计数;这根线,统计等待到中断的次数,与理论值做。(2)设定Reload value。(1)由于counter。recount总共次数;

qq_41651535的博客 497

C语言术语

英汉双译

随便写写 1650

从jieba分词到BERT-wwm——中文自然语言处理(NLP)基础分享系列(12)

今天是本系列的最后一期,完整代码呈现Huggingface transformers平台上微调BERT-wwm-ext的过程方法。

weixin_49518391的博客 3762

从Blender舞步到夜空画卷:基于Pix飞控的无人机编队灯光秀全链路开发实践

本文详细介绍了基于Pixhawk飞控的无人机编队灯光秀全链路开发实践。从Blender三维动画设计、QGroundControl地面站二次开发与MAVLink通信桥接,到PX4飞控的OFFBOARD模式路径控制与LED灯光驱动实现,最后通过Gazebo仿真验证并完成真机组装与首飞。文章为无人机爱好者提供了一套完整的、从创意设计到夜空执行的开源解决方案,重点阐述了多机同步与灯光控制的核心技术挑战与应对策略。

zeta9的博客 584

Java基础语法

Java基础语法 文章目录Java基础语法注释标识符**标识符注意点****数据类型****基本数据类型****数值类型****整数类型****浮点类型****字符类型****boolean类型**(**布尔值**)**整数拓展****浮点数拓展****字符拓展****布尔值拓展**类型转换**注意点:****变量****注意点**:常量**变量的命名规范****运算符****逻辑运算****字符串连接符****三元运算符**包机制JavaDoc**参数信息**Java流程控制用户交互Scanner注意:S

IreneAdler1的博客 1247

LangChain .NET:C#开发者构建AI应用的RAG框架与实战指南

大语言模型(LLM)作为当前人工智能的核心技术,通过深度学习海量文本数据,掌握了强大的语言理解和生成能力。其原理基于Transformer架构,通过自注意力机制捕捉长距离依赖关系,从而实现对复杂语义的建模。这一技术价值在于能够将非结构化的自然语言转化为可计算、可推理的表示,为各类智能应用提供认知基础。在实际工程实践中,LLM常被应用于构建检索增强生成(RAG)系统和AI智能体(Agent)。RAG通过结合外部知识库检索与LLM生成,有效提升了问答的准确性和事实性,是构建企业知识库、智能客服等场景的关键技术。

weixin_42538175的博客 704

无符号数跟有符号数之间暗藏杀机

无符号和有符号之前暗藏杀机你get到了吗,不要在遇到坑了 一、直接上代码 大家看看这段代码有什么错误吗? float sum_elements(float a[], unsigned length) { int i = 0; float sum = 0; for(i = 0; i <= length -1; ++i) sum += a[i]; return sum; } 仔细观察,再仔细观察。。。。这个函数一看就是求一个数组

weixin_44261839的博客 256

使用STARTER V5.6软件将项目文件下载到S120驱动器控制单元CF卡中的具体方法演示

使用STARTER V5.6软件将项目文件下载到S120驱动器控制单元CF卡中的具体方法演示

Robot_PLC_HMI_Automation 1912

OpenHarmony之Histreamer引擎深度解析:pipeline_core架构如何全面取代GStreamer,一统音视频播放与录制

Histreamer是OpenHarmony多媒体框架的核心媒体引擎,采用架构设计。一个轻量级的媒体引擎组件,提供播放、录制等场景的媒体数据流水线处理。1.播放场景分为如下几个节点:数据源读取、解封装、解码、输出;2.录制场景分为如下几个节点:数据源读取、编码、封装、输出。该引擎从OH5.0开始取代了原有的媒体引擎gstreamer(部分文档还保留较老架构的讲解),提供统一的音视频播放、录制、元数据解析等能力。

m0_67505153的博客 332

算法提高 6-17复数四则运算 蓝桥杯

不知道蓝桥杯平台又搞什么鬼,复制出来的效果像是一坨shit,只能截图了,回归正题,复述四则运算是我们在高中学习的内容,相信在大家刚接触编程语言的时候都做过类似的题目,这种题目本身不难,关键是要满足题目中的条件,而这种细节性的东西又不容易被掌握,乍看题目,描述简简单单,实际暗藏杀机,最关键的一个就是数据类型,给出的例子全都是整型,题目中也没有说是什么数据类型,于是我就习惯性的用了int,当然是华丽的

colorfulshark 2454

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

复合的赋值运算符例题_Java千问:关于Java语言复合赋值运算符的两个问题,快来瞧瞧!...

我们知道,在Java以及很多高级编程语言当中,都有一种运算符叫做复合赋值运算符。复合赋值运算符由两个符号组成,它所能完成的运算操作也分为两步:第一步是运算,第二步是赋值。比如说:上面的这两条语句相当于但是,如果碰到下面这样的情况,a的值该应该是多少呢?有人认为应该按以下方式来计算,因为我们都知道,在四则运算规则中,遵循“先乘除,后加减”的原则按照这样的方式来计算,得到a的值应该是7,但实际运行程序...

weixin_29734285的博客 191

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Java千问:关于Java语言复合赋值运算符的两个问题,快来瞧瞧!

我们知道,在Java以及很多高级编程语言当中,都有一种运算符叫做复合赋值运算符。复合赋值运算符由两个符号组成,它所能完成的运算操作也分为两步:第一步是运算,第二步是赋值。比如说: 上面的这两条语句相当于 但是,如果碰到下面这样的情况,a的值该应该是多少呢? 有人认为应该按以下方式来计算,因为我们都知道,在四则运算规则中,遵循“先乘除,后加减”的原则 按照这样的方式来计算,得...

穆哥学堂--Java学习园地 535

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

复合的赋值运算符例题_关于Java语言复合赋值运算符的两个问题,快来瞧瞧

短文涨姿势,看了不白看,不关注等啥?我们知道,在Java以及很多高级编程语言当中,都有一种运算符叫做复合赋值运算符。复合赋值运算符由两个符号组成,它所能完成的运算操作也分为两步:第一步是运算,第二步是赋值。比如说:上面的这两条语句相当于但是,如果碰到下面这样的情况,a的值该应该是多少呢?有人认为应该按以下方式来计算,因为我们都知道,在四则运算规则中,遵循“先乘除,后加减”的原则按照这样的方式来计算...

weixin_34184682的博客 267

java short 赋值_Java千问:关于Java语言复合赋值运算符的两个问题,快来瞧瞧!...

我们知道,在Java以及很多高级编程语言当中,都有一种运算符叫做复合赋值运算符。复合赋值运算符由两个符号组成,它所能完成的运算操作也分为两步:第一步是运算,第二步是赋值。比如说:上面的这两条语句相当于但是,如果碰到下面这样的情况,a的值该应该是多少呢?有人认为应该按以下方式来计算,因为我们都知道,在四则运算规则中,遵循“先乘除,后加减”的原则按照这样的方式来计算,得到a的值应该是7,但实际运行程序...

weixin_39645308的博客 200

int** 赋值_关于Java语言复合赋值运算符的两个问题,快来瞧瞧

短文涨姿势,看了不白看,不关注等啥?我们知道,在Java以及很多高级编程语言当中,都有一种运算符叫做复合赋值运算符。复合赋值运算符由两个符号组成,它所能完成的运算操作也分为两步:第一步是运算,第二步是赋值。比如说:上面的这两条语句相当于但是,如果碰到下面这样的情况,a的值该应该是多少呢?有人认为应该按以下方式来计算,因为我们都知道,在四则运算规则中,遵循“先乘除,后加减”的原则按照这样的方式来计算...

weixin_39861920的博客 317

做完这四道题你还敢说自己理解 `unsigned` 吗?

unsigned。

2401_88912522的博客 931

【C语言避坑指南】一道经典的 unsigned char 下溢陷阱题,你的答案对了吗?

警惕无符号数:在写for循环倒计数时,千万不要用unsigned类型做递减判断(如i >= 0),这会导致死循环或逻辑错误。数学思维:这类题目本质上是模运算(Modulo Arithmetic)。对于,所有的运算都是在模 256 的意义下进行的。调试技巧:如果在实际开发中遇到类似逻辑卡死,尝试打印变量的值,你很可能会看到变量突然从一个小数变成一个接近 255 的大数。希望这篇解析能帮你彻底搞懂无符号数的坑!最后这是助于理解的图。

2401_86187158的博客 425

语言运算顺序题目_总结几个C语言中的“坑”

今天给大家分享几个C语言中的坑。一、带参数的宏展开顺序#include <stdio.h> #define f(a,b) a##b #define g(a) #a #define h(a) g(a) int main(void) { printf("%sn",h(f(1,2))); printf("%sn",g(f(1,2))); return 0; ...

weixin_33342041的博客 146

C++ bitset性能优化实战:从底层原理到高效位运算编程

位运算作为计算机科学的基础概念,通过直接操作二进制位实现高效的数据处理。其核心原理是利用CPU原生支持的AND、OR、XOR等指令,在单个时钟周期内完成整字操作,实现数据级并行。这种技术价值在于能以极低的内存开销和极高的运算速度处理大量布尔状态,特别适用于状态压缩、标志位管理和集合运算等场景。在C++中,std::bitset是位运算的典型实现,它通过编译期确定大小和整字存储策略,在缓存优化和批量运算方面表现卓越。然而,其随机访问开销和多线程安全性需要特别注意。本文深入剖析bitset的底层机制,结合性能陷

cuililai5127的博客 424
上一篇: IP 如何存储?
下一篇: 02. 就该这么学并发 - 线程的就绪与执行
码哥说
博客等级 码龄8年 5645粉丝 172原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码哥说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值