学习Java中遇到的问题积累_1

1.奇数性

看下面代码时候是否能判断参数 i 是奇数?

public static boolean isOdd(int i){ return i % 2 == 1; 
}

答案是: NO
看似正确的判断奇数, 但是如果 i 是负数, 那么它返回值都是false
造成这种现象的是 => 从思想上固化, 认为奇数只在正数范围, 故判断负数将报错, 在C++中也是, 负数取余还是负.
在Java中取余操作定义产生的后果都满足下面的恒等式:

int数值a, 与非零int数值b 都满足下面的等式:
(a / b) * b + (a % b) == a

从上面就可以看出, 当取余操作返回一个非零的结果时, 左右操作数具有相同的正负号, 所以当取余在处理负数的时候, 以及会考虑负号.
而上面的这个问题, 解决方法就是避免判断符号:

public static boolean isOdd(int i){ return i % 2 != 0; 
}

让结果与0比较, 很容易避免正负号判断.

思考:
1.在使用取余操作的时候要考虑符号对结果的影响
2.在运算中, 尝试使用0解决符号问题, 在一定程度上避免符号对结果的影响

2.浮点数产生的误差

看下面代码会打印出什么样的结果?

public class Change{ public static void main(String args[]){ System.out.println(2.00 - 1.10); } 
}

从主观上看, 打印的结果必然是0.90, 然后这却是一个主观错误.
对于1.10这个数, 计算机只会使用近似的二进制浮点数表示, 产生精度影响.
从上面的例子中来看, 1,10在计算机中表示为1.099999, 这个1.10并没有在计算机中得到精确的表示.
针对这个精度问题, 我们可能会选择: System.out.printf("%.2f%n", 2.00 - 1.10);解决, 尽管打印出来的是正确答案, 但是依旧会暴露出一个问题: 如果精度控制在2.00 - 1.0010; 那么精度误差依旧会出现.
这里也说明了: 使用printf, 计算机底层依旧是使用二进制的方式来计算, 只不过这种计算提供了更好的近似值而已.
那么应该怎么解决这个问题呢?
首先想到是使用int模拟小数每一位, 然后计算, 最后将结果又转化为小数;
以此想到的就是使用BigDecimal类, 它主要用于精确小数运算.

import java.math.BigDecimal; 
public class Change1{ public static void main(String args[]){ System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10"))); } 
}

通过上面的代码就能得到一个精确的值.
注: 使用BigDecimal的时候, 不要使用BigDecimal(double d)的构造方法, 在double与double之间传值的时候依旧会引起精度损失. 这是一个严重的问题.
BigDecimal底层采用的就是int[], 使用String的时候, 会将String不断取每一位存入int[], 使用double的时候, 同理将数字的每一位存入int[], 但是double本身存在误差, 导致存入的数据会出现误差,例: 0.1存入double就表示为0.1000000099999999, 因此不使用double类型的构造函数

思考:
当然对于精确要求不高的地方, 完全可以使用float/double, 但是对于要求精度的计算, 比如货币 一定要使用int, long, BigDecimal.

3.长整数造成数据溢出

看下面的代码会打印什么?

public class LongDivision{ public static void main(String args[]){ final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY); } 
}

整个过程, 除数与被除数都是long型, 很容易保存这两个数, 结果一定是1000, 但是结果让你失望了, 结果是5.
这又是怎么回事呢?
首先这个表达式: 24606010001000总是在int类型的基础上进行计算. 即表达式是按照int的规则计算
很容易看出这个表达式计算的范围早已超出int的取值范围, 纵然使用long去存储计算结果, 但是在计算的过程中就已经出现计算数据溢出, 这是一个隐藏错误.
Java目标确定类型的特性 => 如上例子, 不同通过 long 去确定24606010001000按照long进行存储.
必须指定数据类型, 才能按照指定的规则进行运算.
就用前面这个例子来看, 当指定24为24L就能防止数据计算溢出, 在进行乘法运算的时候就已经是在long的基础上进行计算.

public class LongDivision{ public static void main(String args[ ]){ final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000; final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000; System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY); } 
}

思考:
这个问题给了我一个深刻的教训, 当操作数很大的时候, 要预防操作数溢出, 当无法确定计算数会不会溢出, 所要做的就是用储存范围最大的类型: long 来进行计算

4.long的 “L” 与 “l” 所引发的错误

从上面 “长整数运算造成数据溢出” 引发又一个问题, 看下面例子:

public class Elementary{ public static void main(String[] args){ System.out.println(12345+5432l); } 
}

乍一看, 这很简单, 计算结果时是 6666, 但是打印的结果是 17777, 我开始头晕了, 这很不合理.
思考过后, 发现了一个问题:
我把 “l” 看作是 “1”, “l” 只是用于标识5432是一个long类型, 这个视觉上的错误将会引发更严重的问题.
思考:
小写字母 l 与 1 很容易造成混淆, 为了避免这种错误, 在表示long类型数据的, 要做的就是将 “l” 换做 “L”, 掐断产生混乱的源头.

5.多重类型转换引发的数值变化

看这样的一个例子:

public class Multicast{ public static void main (String[] args){ System.out.println((int)(char)(byte) -1); } 
}

看似结果是 -1, 但是运行之后, 结果变为 65535
分析一下:
byte下的-1 => 1111,1111,1111,1111,1111,1111,1111,1111 32位(4个字节) 首位1表示负号.
byte到char => 变为 0000,0000,1111,1111 16位(2个字节)首位0, 就此负号变正号.
char到int => 变为 0000,0000,0000,0000,0000,0000,1111,1111 32位(4个字节)
由此可见, 在byte到char的变化过程中出现符号转换的问题. char首位总是0使得负号变正号

类型转换的过程存在这样的简单规则:
如果最初的数值类型是有符号的,那么就执行符号扩展;如果它是 char,那么不管它将要被转换成什么类型,都执行零扩展。因此这也就解释了为什么byte到char的过程存在负号变正号.

为了在转换的过程中保留符号, 就使用位掩码进行限制, 例如: char c = (char)(b & 0xff); 这样就能保证符号具有保留

思考:
在对有符号与无符号之间的转换, 一定要注意上面的转换规则, 如果不能确定转换符号是否正确, 那么就避免出现有符号到无符号之间的转换.

6.避免所谓聪明的编程技巧

对于交换两个变量的内容, 在C/C++中存在一种这样的编程技巧:

int x = 1111;
int y = 2;
x^=y^=x^=y; 
cout<<x<<" "<<y; 

这样一个简单的连续异或就能解决变量的交换问题, 这种写法在很久以前是为了减少临时变量的使用, 所以这种做法在现在也得到了保留.
首先看这样一个问题, 表达式x^=y, 在C/C++的编译器中是先计算出y的值, 然后再获取x的值, 最后再计算表达式. 但在Java中的做法是先获得x的值, 再获得y的值, 最后再计算.
Java的语言规范描述: 操作符的操作数是从左往右求值.
这使得在计算 x^ =y^ =x^ =y表达式中的第二个x的时候是在计算x^ =y之前的值( x的值依旧是1111 ), 并不是x^=y后的值, 这就导致了计算上的错误.
所以在Java中准确的写法是:

y = ( x^=( y^=x ) )^y

思考:
上面的这种写法极其容易引起错误, 程序的可读性受到很大的影响, 所以在写代码的时候要思考一个问题, 除非编译器能确定操作数的运算顺序, 不然不要让编译器去确定操作数的计算顺序, 就比如这样的表达式: x=a[i]+±a[j]++. 很容易导致错误.

7.避免使用混合运算

看如下代码:

public class DosEquis{ public static void main(String[] args){ char x = 'X'; int i = 0; System.out.println(true ? x : 0); System.out.println(false ? i : x); } 
}

看似将打印: XX, 但是结果却是X88. 这是一个出乎意料的结果.
思考之后, 将可能得出这样的结论: 出现这样问题的原因是操作数的类型自动提升, char=>int.
但是又有一个问题就是为什么第一个运算不是88. 找其根本原因还是在于条件表达式的运算规则:

A ? B : C
B, C为相同类型, 那么表达式的计算结果就是B, C的类型
B, C不是相同类型的时候, 那么计算结果就按照B的类型(此时B必须是式子中最高类型).此时C的类型就自动上升为式子中最高的类型, 例: false  ? x : i, 输出是0, 而不是0对应的字符.

上面的规则决定了, 将调用哪一个print的重载函数.
这种条件表达式返回值, 很容易受B, C类型影响. 当根据返回值作条件判断的时候, 这种性质也将导致一个严重的问题.
思考:
上面的问题说明了, 在条件表达式中, 最后再后两个操作数使用相同类型的操作数, 以此避免返回值类型不确定的问题, 并且在其他的表达式计算中, 一定要理清楚数值之间的类型转换.

8.发现隐藏的类型转换

在这样的表达式: x += i; 按照平常的理解, 它一定是x = x + i; 可是这样的运算表达式是建立在x与i具有相同的类型的基础上的, 如果当x, i类型不相同的时候, 将会引发一个问题就是精度损失.
就比如:

short x = 0;
int i = 99999;
x += i;

现在的x不是99999, 而是-31073.
当 x += i 的时候, 出现的问题就是i自动转型为short, 此时x的值就不再是99999. 而当你将表达式写为: x = x + i 的时候, 这是一种显式的转型, 自然需要强转操作. 从而避免了隐藏的类型转换.
思考:
复合运算会隐藏出现转型操作, 这种转型操作很有可能出现精度丢失.
所以在进行复合运算的时候, 避免两边的操作数是不同的类型, 防止编译器出现危险的窄化类型, 或者不使用复合运算, 人为进行类型转换.

9.字符串的"+"运算符

看如下代码:

public class LastLaugh{public static void main(String[] args){ System.out.print("H"+"a"); System.out.print('H'+'a'); } 
}

由于长期受 “+” 运算符的影响, 上面的代码, 很容易把 ‘H’+‘a’ 也看作是字符串的连接, 这是一种惯性的思考方式.
在 ‘H’+‘a’ 表达式的运算中, 是将 ‘H’, ‘a’, 上升为int, 进行数值运算.
如果想让两个字符连接在一起, 可以采用:

1.使用 StringBuffer/StringBuild 做 append 运算.StringBuild s = "".append('H');
2.使用String s = "" + 'H' +'a'; 使用字符串连接.String s1 = "" + 'H' + 'a';String s2 = 'a' + 'H' + "";System.out.println(s1);System.out.println(s2);注: 避免 s2 的写法, 这样写 'a'+'H' 依旧做 int 的数值运算

思考:
在使用 “+” 运算符一定要注意操作数的类型, 以防止惯性思维导致的运算错误. 在某些场合这种错误有可能是致命性的.

看完字符的 “+” 运算符, 现在再来字符数组的 "+"运算符 :

public class A{ public static void main(String[] args){ String letters = "ABC"; char[] numbers = {'1', '2', '3'}; System.out.println(letters + " easy as " + numbers); } 
}

上面的代码, 最终的打印结果不是 ABC easy as 123, 而是ABC easy as [C@16f0472.
如果想到的打印结果是ABC easy as 123, 那么犯的错误还是上面相同的错误.
在打印结果的时候, 首先会进行字符串连接, 当 “easy as” 这个字符串连接 char[] 的时候, 那么调用的是char[] 的toString(), 而系统并没有重写toString(), 所以最后调用的就是Object的toString();
为了修正这样的错误, 给出如下解决方式:

1.使用String.valueOf(number); 转字符串后再进行连接操作.
2.使用System.out.println(number); 调用重载的println(char[] c);

而在C/C++中, char numbers[4] = {‘1’, ‘2’, ‘3’, ‘\0’ }; 代表的就是一个字符串.
思考:
牢记, 数组类型的toString都没有重写, 如果想获得数组中的值, 避免调用数组类型的toString, 或者让系统隐藏调用, 而是直接遍历数组获得其中的值.

10."=="运算符进行比较

  • 问题1:
    这里先说明第一个问题, 就是Java中的 “==” 运算符: 在比较基本类型的时候, 是比较基本类型值的关系; 在比较数组, 或者对象的时候是比较对象之间的引用值关系. 但是这里要注意的是:
    在比较Integer, Long(本人亲测)这两种的时候, 比较-128~127的时候是从缓存池中拿取数据.
Integer中的equals方法
public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;}
这个过程中实现的是将Integer拆包,-128~127不需要拆包,可直接使用==比较.
Integer的缓存池-128~127: 自动装箱过程中使用valueOf创建对象,因此直接会使用缓存池中对象.

思考:
这里我想表达的意思就是, 如果要进行对象内容之间的比较, 务必重写equals, 然后使用equals. 还有避免在基本类型与包装类型混合状态的基础上使用 “==”, 就比如 Integer. 这个很容易导致错误.

  • 问题2
    当看到这样的代码的时候:
public class AnimalFarm{ public static void main(String[] args){ final String pig = "length: 10"; final String dog = "length: " + pig.length(); System.out. println("Animals are equal: " + pig == dog); } 
}

我想去比较pig与dog引用值关系, pig 与 dog 的引用值肯定是相同的, 但是最后的输出结果却是false. 在这里忽略了一个问题, 那就是前面的 “+” 的运算级比 “==” 的运算级高, 看似是比较pig与dog的引用值, 最后却是比较"Animals are equal: length: 10"与dog的引用值关系.
现在给出下面的修正方案:

1.System.out.println("Animals are equal: " + (pig == dog));
2.System.out.println("Animals are equal: " + pig.equals(dog));

思考:
从这里也看出, 比较对象内容的时候, 务必使用已经重载后equals, 除非刻意比较两个对象的引用值, 否则千万别使用"==".

上面有错, 还请指出, 如果认为我写的还不错, 还请点个赞, 多多支持一下, O(∩_∩)O~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468669.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 开发者最应该知道的命令汇总

&#xff11;最近发现一个好东西&#xff0c;在 github 上发现的&#xff0c;我觉得非常适合大家研究 linux&#xff0c;说白了就是一些命令而已&#xff0c;只不过是作者帮忙总结好了&#xff0c;比较适合大家开发和学习 linux 系统&#xff0c;so , 推荐给大家学习下。https:…

华为任职资格_华为采购总部专业任职资格标准|

目 录 序 言 概述 .........................第一部分 级别定义.....................第二部分 资格标准 ....................1、采购工程师&#xff08;生产采购&#xff09;任职资格标准........2、采购员&#xff08;生产采购&#xff09;任职资格标准............3、采购员…

VirtualBox设置共享目录(主机win7,虚拟机Ubuntu)

1、安装增强功能包 启动虚拟机后&#xff0c;在 设备 -》 分配光驱 选择VBoxGuestAdditions.iso增强包镜像&#xff08;在virtualbox安装目录下&#xff09; 在虚拟机中挂载光驱镜像&#xff1a; #mkdir /mnt/cdrom#mount /dev/cdrom /mnt/cdrom 执行增强包安装程序&#xff…

C 语言内存分配

&#xff11;昨天有一个群里的同学问我&#xff0c;他问我的问题是 c 语言函数是存在哪里的&#xff1f;是如何执行的&#xff1f;我下意识的觉得这位同学应该是个初学者&#xff0c;所以今天就写下一些基础方面的内容&#xff0c;「C语言的内存布局」。程序代码可以看做是一个…

正则表达式符号特殊详解_常用正则表达式_Java中正则表达式的使用

正则表达式符号详解 限定符: 指定一个组件必须出现多少次才能满足. 1.使用 “*”, “”, “?” 作为限定符: "*": 匹配前面的子表达式零次或多次。例如&#xff0c;zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。"": 匹配前面的子表达…

python中求包含5的数_Python 内置函数 ( ) 可以返回列表、元组、字典、集合、字符串以及 range 对象中元素个数。_学小易找答案...

【简答题】实例1:求两数相除的结果。 先后输入2个数据,计算第一个数除以第二个数的结果。 要求能够处理输入数据为非数字、除数为零、文件末尾EndOfFile 和用户使用Ctrl + C 命令终止程序等异常,提高用户体验。 当输入数据为非数字时提示错误信息并让用户重新输入。 input()函数…

没有梦想,你跟咸鱼有什么分别?

&#xff11;标题起的有点夸张&#xff0c;其实这个就是一个招聘贴&#xff0c;之前从来没有用发头条文章来招聘&#xff0c;实在不好意思&#xff0c;这个招聘对我非常重要&#xff0c;这是一个非常好的朋友公司的招聘信息&#xff0c;也希望大家帮忙扩散一下&#xff0c;因为…

一个很Low的通讯录管理系统(但是能用)C/C++单链表实现

通讯录管理系统的设计 问题需求分析 在计算机还未普及之前通讯管理都是由联系人采用名片&#xff0c;通讯录往往采用的是笔录手工记帐的方式来操作的。现在一般的通讯录管理都是采用计算机作为工具的实用的计算机通讯录管理程序来帮助人们进行更有效的通讯录信息管理。本通讯…

2017《面向对象程序设计》课程作业三

作业链接github链接 对于文件读写和多参数主函数学习过程中遇到的问题 这次文件读写改用了C的形式&#xff0c;然后总体还算顺利&#xff0c;借鉴了林燊的&#xff0c;因为他写的代码最容易看懂&#xff1b;还有就是借鉴了《C程序设计》&#xff0c;讲真&#xff0c;谭浩强的还…

做人力资源需要掌握python_9种人力资源分析工具,高效打造数字化HR全流程

对于许多人和组织而言&#xff0c;采用HR分析是一大进步。确实&#xff0c;我经常被问到&#xff1a;“最好使用什么人力资源分析工具&#xff1f;”本文将为您提供该问题的答案&#xff0c;以下是要使用的九种最佳人力资源分析工具的列表。1、RR是最常用的HR分析工具。R非常适…

华为不做黑寡妇,开源编译器,与友商共建安卓性能

&#xff11;今天我的一个老哥开了头条号&#xff0c;第一次发文章&#xff0c;我觉得不错&#xff0c;拿来用用&#xff0c;给大家看看华为技术总工的文采。这位总工潜伏在我的微信群里很少说话&#xff0c;大家一定要有这个想法&#xff0c;就是最低调的那个人&#xff0c;真…

点击链接如何直接跳转到相对应的聊天窗口

解决这个问题的步骤如下&#xff1a; <a target"_blank" href"http://wpa.qq.com/msgrd?v3&uin3237465337&siteqq&menuyes">一、登陆腾讯官方网站&#xff1a;http://wp.qq.com/ 二、登陆之后&#xff0c;点“设置”&#xff0c;按下图…

哈夫曼树编码与译码(完整C/C++实现代码)

哈夫曼编码的设计与应用 问题需求分析 用哈夫曼编码(Huffman Coding)&#xff0c;又称霍夫曼编码&#xff0c;是一种编码方式&#xff0c;哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法&#xff0c;该方法完全依据字符出现概率来构造异字头的平均长…

移动应用开发实例_物联网改变移动应用开发的4种方式

图片来源&#xff1a;pixabay.com来源&#xff1a;物联之家网(iothome.com)转载请注明来源&#xff01;物联网改变了移动应用程序的开发格局。那么&#xff0c;为物联网开发移动应用程序有何不同&#xff1f;物联网与移动应用程序开发齐头并进。物联网改变了人类与机器的互动方…

谁都能看懂的网络模型知识

&#xff11;.网络是我们做嵌入式 避无可避的知识点&#xff0c;但是网络的层次很多&#xff0c;很多时候我们根本理解不了其中的层次和作用&#xff0c;今天跟我们公司的 X 总聊到这个&#xff0c;给我普及了一些知识&#xff0c;我觉得非常有用&#xff0c;分享给大家。最近事…

Java微信公众平台获取签名

如果想使用微信的JSSDK&#xff0c;需要注册公众号和获取签名&#xff0c;Java代码实现如附件&#xff1a; 使用的是servlet&#xff0c;access_token和ticket缓存在application作用域里 附件为源代码&#xff1a;wx.zip转载于:https://www.cnblogs.com/yrcn/p/6831808.html

常用排序算法以及算法性能测试(完整C/C++代码实现)

排序算法性能的比较 注: 由于只是测试算法性能, 所以不会对排序算法做深入讲解, 在随后的时间将会推出排序的详细讲解 问题需求分析 排序算法经过了很长时间的演变&#xff0c;产生了很多种不同的方法。每种算法主要针对不同的数列进行排序&#xff0c;这些排序算法具有各自…

jquery点击事件传值加载页面_jQuery添加options点击事件并传值

var formStr "{supplierId:供应链企业|%-jm-sprt-%|93794498-3}";$.ajax({type : "post",dataType : "json",cache : false,url : "../../../webservice/gmall.json", // 提交到一般处理程序请求数据data :"businessCode6100100…

我就随便BB一下

&#xff11;.如果我开始写文章的时候&#xff0c;停顿了几分钟&#xff0c;那结果应该很明显&#xff0c;这一定是一篇比较垃圾的文章&#xff0c;没什么东西值得看&#xff0c;所以我把之前的删除了&#xff0c;重新开头来写&#xff0c;我认为&#xff0c;一个好的开头一定是…

JS代码优化工具Prepack

最近&#xff0c;Facebook 发布 Prepack :一个优化 JavaScript 源代码的工具&#xff0c;实际上它是一个 JavaScript 部分求值器(Partvaluator)&#xff0c;可在编译时执行原本在运行时的计算过程&#xff0c;并通过重写 JavaScript 代码来提高其执行效率。(西安尚学堂&#xf…