简介

java中可以被称为Number的有byte,short,int,long,float,double和char,我们在使用这些Nubmer的过程中,需要注意些什么内容呢?一起来看看吧。

Number的范围

每种Number类型都有它的范围,我们看下java中Number类型的范围:

考虑到我们最常用的int操作,虽然int的范围够大,但是如果我们在做一些int操作的时候还是可能超出int的范围。

超出了int范围会发送什么事情呢?看下面的例子:

    public void testIntegerOverflow(){
System.out.println(Integer.MAX_VALUE+1000);
}

运行结果:-2147482649。

很明显Integer.MAX_VALUE+1000将会超出Integer的最大值范围,但是我们没有得到异常提醒,反而得到了一个错误的结果。

正确的操作是如果我们遇到了Overflow的问题,需要抛出异常:ArithmeticException。

怎么防止这种IntegerOverflow的问题呢?一般来讲,我们有下面几种方式。

  • 第一种方式:在做Integer操作之前,进行预判断是否超出范围:

举个例子:

    static final int safeAdd(int left, int right) {
if (right > 0 ? left > Integer.MAX_VALUE - right
: left < Integer.MIN_VALUE - right) {
throw new ArithmeticException("Integer overflow");
}
return left + right;
}

上面的例子中,我们需要进行两个整数相加操作,在相加之前,我们需要进行范围的判断,从而保证计算的安全性。

  • 第二种方式:使用Math的addExact和multiplyExact方法:

Math的addExact和multiplyExact方法已经提供了Overflow的判断,我们看下addExact的实现:

    public static int addExact(int x, int y) {
int r = x + y;
// HD 2-12 Overflow iff both arguments have the opposite sign of the result
if (((x ^ r) & (y ^ r)) < 0) {
throw new ArithmeticException("integer overflow");
}
return r;
}

看下怎么使用:

    public int addUseMath(int a, int b){
return Math.addExact(a,b);
}
  • 第三种方式:向上转型

既然超出了Integer的范围,那么我们可以用范围更大的long来存储数据。

    public static long intRangeCheck(long value) {
if ((value < Integer.MIN_VALUE) || (value > Integer.MAX_VALUE)) {
throw new ArithmeticException("Integer overflow");
}
return value;
} public int addUseUpcasting(int a, int b){
return (int)intRangeCheck((long)a+(long)b);
}

上面的例子中,我们将a+b转换成了两个long相加,从而保证不溢出范围。

然后进行一次范围比较,从而判断相加之后的结果是否仍然在整数范围内。

  • 第四种方式:使用BigInteger

我们可以使用BigInteger.valueOf(a)将int转换成为BigInteger,再进行后续操作:

    public int useBigInteger(int a, int b){
return BigInteger.valueOf(a).add(BigInteger.valueOf(b)).intValue();
}

区分位运算和算数运算

我们通常会对Integer进行位运算或者算数运算。虽然可以进行两种运算,但是最好不要将两种运算同时进行,这样会造成混淆。

比如下面的例子:

x += (x << 1) + 1;

上面的例子是想做什么呢?其实它是想将3x+1的值赋给x。

但是这样写出来让人很难理解,所以我们需要避免这样实现。

再看下面的一个例子:

    public void testBitwiseOperation(){
int i = -10;
System.out.println(i>>>2);
System.out.println(i>>2);
System.out.println(i/4);
}

本来我们想做的是将i除以4,结果发现只有最后一个才是我们要的结果。

我们来解释一下,第一个i>>>2是逻辑右移,将会把最左边的填充成0,所以得出的结果是一个正值1073741821。

第二个i>>2是算数右移,最左边的还是会填充成1,但是会向下取整,所以得出结果是-3.

直接使用i/4,我们是向上取整,所以得出结果是-2.

注意不要使用0作为除数

我们在使用变量作为除数的时候,一定要注意先判断是否为0.

兼容C++的无符号整数类型

在java中只有16位的char表示的是无符号整数,而int实际上表示的是带符号的整数。

而在C或者C++中是可以直接表示无符号的整数的,那么,如果我们有一个32位的无符号整数,该怎么用java来处理呢?

    public int readIntWrong(DataInputStream is) throws IOException {
return is.readInt();
}

看上面的例子,我们从Stream中读取一个int值,如果是一个32位的无符号整数,那么读出来int就变成了有符号的负整数,这和我们的期望是相符的。

考虑一下,long是64位的,我们是不是可以使用long来表示32位的无符号整数呢?

    public long readIntRight(DataInputStream is) throws IOException{
return is.readInt() & 0xFFFFFFFFL; // Mask with 32 one-bits
}

看上面的例子,我们返回的是long,如果将32位的int转换成为64位的long,会自动根据符号位进行补全。

所以这时候我们需要和0xFFFFFFFFL进行mask操作,将高32位重置为0.

NAN和INFINITY

在整型运算中,除数是不能为0的,否则直接运行异常。但是在浮点数运算中,引入了NAN和INFINITY的概念,我们来看一下Double和Float中的定义。

public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
public static final float NaN = 0.0f / 0.0f;

1除以0就是INFINITY,而0除以0就是NaN。

接下来,我们看一下NAN和INFINITY的比较:

public void compareInfinity(){
System.out.println(Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY);
}

运行结果是true。

    public void compareNaN(){
System.out.println(Double.NaN == Double.NaN);
}

运行结果是false。

可以看到NaN和NaN相比是false。

那么我们怎么比较NaN呢?

别急,Double提供了一个isNaN方法,我们可以这样使用:

System.out.println(Double.isNaN(Double.NaN));

接下来我们看一个在代码中经常会用到的一个Double解析:

    public void incorrectParse(String userInput){
double val = 0;
try {
val = Double.valueOf(userInput);
} catch (NumberFormatException e) {
}
//do something for val
}

这段代码有没有问题?咋看下好像没有问题,但是,如果我们的userInput是NaN,Infinity,或者-Infinity,Double.valueOf是可以解析得到结果的。

    public void testNaN(){
System.out.println(Double.valueOf("NaN"));
System.out.println(Double.valueOf("Infinity"));
System.out.println(Double.valueOf("-Infinity"));
}

运行输出:

NaN
Infinity
-Infinity

所以,我们还需要额外去判断NaN和Infinity:

public void correctParse(String userInput){
double val = 0;
try {
val = Double.valueOf(userInput);
} catch (NumberFormatException e) {
}
if (Double.isInfinite(val)){
// Handle infinity error
} if (Double.isNaN(val)) {
// Handle NaN error
}
//do something for val
}

不要使用float或者double作为循环的计数器

考虑下面的代码:

for (float x = 0.1f; x <= 1.0f; x += 0.1f) {
System.out.println(x);
}

上面的代码有什么问题呢?

我们都知道java中浮点数是不准确的,但是不一定有人知道为什么不准确。

这里给大家解释一下,计算机中所有与的数都是以二进制存储的,我们以0.6为例。

0.6转成为二进制格式是乘2取整,0.6x2=1.2,取整剩余0.2,继续上面的步骤0.2x2=0.4,0.4x2=0.8,0.8x2=1.6,取整剩余0.6,产生了一个循环。

所以0.6的二进制格式是.1001 1001 1001 1001 1001 1001 1001 ... 无限循环下去。

所以,有些小数是无法用二进制精确的表示的,最终导致使用float或者double作为计数器是不准的。

BigDecimal的构建

为了解决float或者Double计算中精度缺失的问题,我们通常会使用BigDecimal。

那么在使用BigDecimal的时候,请注意一定不要从float构建BigDecimal,否则可能出现意想不到的问题。

    public void getFromFloat(){
System.out.println(new BigDecimal(0.1));
}

上面的代码,我们得到的结果是:0.1000000000000000055511151231257827021181583404541015625。

这是因为二进制无法完美的展示所有的小数。

所以,我们需要从String来构建BigDecimal:

    public void getFromString(){
System.out.println(new BigDecimal("0.1"));
}

类型转换问题

在java中各种类型的Number可以互相进行转换:

比如:

  • short to byte or char
  • char to byte or short
  • int to byte, short, or char
  • long to byte, short, char, or int
  • float to byte, short, char, int, or long
  • double to byte, short, char, int, long, or float

或者反向:

  • byte to short, int, long, float, or double
  • short to int, long, float, or double
  • char to int, long, float, or double
  • int to long, float, or double
  • long to float or double
  • float to double

从大范围的类型转向小范围的类型时,我们要考虑是否超出转换类型范围的情况:

    public void intToByte(int i){
if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE)) {
throw new ArithmeticException("Value is out of range");
}
byte b = (byte) i;
}

比如上面的例子中,我们将int转换成为byte,那么在转换之前,需要先判断int是否超出了byte的范围。

同时我们还需要考虑到精度的切换,看下面的例子:

    public void intToFloat(){
System.out.println(subtraction(1111111111,1111111111));
} public int subtraction(int i , float j){
return i - (int)j;
}

结果是多少呢?

答案不是0,而是-57。

为什么呢?

因为这里我们做了两次转换,第一次从1111111111转换到float,float虽然有32位,但是只有23位是存放真正的数值的,1位是符号位,剩下的8位是指数位。

所以从1111111111转换到float发送了精度丢失。

我们可以把subtraction方法修改一下,首先判断float的范围,如果超出了23bit的表示范围,则说明发送了精度丢失,我们需要抛出异常:

    public int subtraction(int i , float j){
System.out.println(j);
if ((j > 0x007fffff) || (j < -0x800000)) {
throw new ArithmeticException("Insufficient precision");
}
return i - (int)j;
}

当然还有一种办法,我们可以用精度更高的double来做转换,double有52位来存放真正的数据,所以足够了。

    public int subtractionWithDouble(int i , double j){
System.out.println(j);
return i - (int)j;
}

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-number/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

java安全编码指南之:Number操作的更多相关文章

  1. java安全编码指南之:基础篇

    目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...

  2. java安全编码指南之:文件IO操作

    目录 简介 创建文件的时候指定合适的权限 注意检查文件操作的返回值 删除使用过后的临时文件 释放不再被使用的资源 注意Buffer的安全性 注意 Process 的标准输入输出 InputStream ...

  3. java安全编码指南之:字符串和编码

    目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...

  4. java安全编码指南之:输入校验

    目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...

  5. java安全编码指南之:Mutability可变性

    目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...

  6. java安全编码指南之:可见性和原子性

    目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...

  7. java安全编码指南之:异常处理

    目录 简介 异常简介 不要忽略checked exceptions 不要在异常中暴露敏感信息 在处理捕获的异常时,需要恢复对象的初始状态 不要手动完成finally block 不要捕获NullPoi ...

  8. java安全编码指南之:lock和同步的正确使用

    目录 简介 使用private final object来作为lock对象 不要synchronize可被重用的对象 不要sync Object.getClass() 不要sync高级并发对象 不要使 ...

  9. java安全编码指南之:输入注入injection

    目录 简介 SQL注入 java中的SQL注入 使用PreparedStatement XML中的SQL注入 XML注入的java代码 简介 注入问题是安全中一个非常常见的问题,今天我们来探讨一下ja ...

随机推荐

  1. 图解比原链Tensority算法:如何让POW做到人工智能友好

    共识算法说起 区块链系统首先是分布式系统,而一致性是分布式系统的基础问题,要保证系统满足不同程度的一致性,则就要用到共识算法. 现在主流的算法有POW.POS.DPOS等等,比特币采用的POW共识算法 ...

  2. C#LeetCode刷题之#56-合并区间(Merge Intervals)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3676 访问. 给出一个区间的集合,请合并所有重叠的区间. 输入: ...

  3. C#LeetCode刷题-线段树

    线段树篇 # 题名 刷题 通过率 难度 218 天际线问题   32.7% 困难 307 区域和检索 - 数组可修改   42.3% 中等 315 计算右侧小于当前元素的个数   31.9% 困难 4 ...

  4. Python多进程队列间传递对象

    前言 在python 需要在队列中传递对象, 会出现进程不能正常退出的情况. 其原因是因为 在父进程 向子进程传入的Queue对象不对, Queue对象正常是子进程之间的信息传递, 而当我在父进程 创 ...

  5. Git常用命令参考手册

    配置 # 查看全局配置列表 git config -l # 查看局部配置列表 git config --local --list # 查看已设置的全局用户名/邮箱 git config --globa ...

  6. Debug很重要

    之前做一个小功能,就是用php发送邮件,项目中已经使用了wordpress的wp_mail,所以同事建议我继续用wp_mail函数. 然而遇到了一个奇怪的情况,邮件没有发出去,也没有任何报错日志. 照 ...

  7. 使用Postman工具做接口测试(五)——生成随机参数

    引言 我们平时使用最多的接口调试工具就是postman了,比如开发将一个接口给到你,你想看看接口是否正常.最常用的方法就是用postman去调一下.如果通,就写接口测试用例,反之,将开发打一顿吧o(* ...

  8. 用Python的Pandas和Matplotlib绘制股票唐奇安通道,布林带通道和鳄鱼组线

    我最近出了一本书,<基于股票大数据分析的Python入门实战 视频教学版>,京东链接:https://item.jd.com/69241653952.html,在其中给出了MACD,KDJ ...

  9. JavaScript学习系列博客_35_JavaScript 正则表达式的使用

    正则表达式的使用 先说RegExp对象的一个方法 test() - 使用这个方法可以用来检查一个字符串是否符合正则表达式的规则,如果符合则返回true,否则返回false. 1.用正则表达式来检查一个 ...

  10. Linux kernel 模块 hello 测试

    原文链接:https://www.cnblogs.com/nerohwang/p/3621316.html hello.c 文件: #include <linux/kernel.h> /* ...