我们程序员开发,总是抱怨怎么又要加班,自己测试没问题,交给测试一堆问题;曾子曰:“吾日三省吾身“。每天自我反省是程序员的基本素养,然后总结忽略的地方记录下来。
这里举几个Java代码中常见例子
当业务中遇到金钱的计算,都是带小数点的,如果你不知道 小数 的二进制与十进制 互转的问题那么很有可能出现实际与预期不服的情况。
System.out.println(0.5-0.4);
》》》输出结果:
0.09999999999999998
《Effective Java》中提到一个原则,那就是float和double只能用来作科学计算或者是工程计算,java的设计者给编程人员提供了一个很有用的类java.math.BigDecimal,他可以完善float和double类无法进行精确计算的缺憾。
光说不练,我们来看看BigDecimal,发现精度还是不准确
System.out.println(new BigDecimal(0.5).subtract(new BigDecimal(0.4)));
》》》输出结果:
0.09999999999999997779553950749686919152736663818359375
其实我们又忽视了一点,就是new BigDecimal(0.1)
这时候就是已经是精度不准确了
System.out.println(new BigDecimal(0.1));
》》》输出结果:
0.1000000000000000055511151231257827021181583404541015625
那么究竟我们要怎样才能做到精度不丢失呢?
构造参数使用String
System.out.println(new BigDecimal("0.5").subtract(new BigDecimal("0.4")));
》》》输出结果:
0.1
我们知道比较String
是否相同,不能直接使用==
,如果这都不清楚了,自己去复习,基本数据类型和引用类型是怎么比较相等的;
在有一些业务中会涉及到字符串的拼接,较少的拼接还好,如果是遇到for
循环,那就会增加内存开销
这是后有经验的大佬就然你使用StingBuilder
,内部的实现大家有兴趣可以去看看,简单来说就是内部有个可扩容的数组,让它成为可变字符串。
这时候如果要比较两个StringBuilder是否相等
StringBuilder str1=new StringBuilder("123");
StringBuilder str2=new StringBuilder("123");
System.out.println(str1.equals(str2));
》》》输出结果:
false
为啥会这样呢?
StringBuilder 没有去实现Object的equals方法
所以我们一定要注意,很容易忽视的;
这下是不是明白 阿里的开发手册中规定谨慎使用继承的方式进行扩展,代码越来越多,你根本不知道有没有实现哪些方法,都要点进去看,影响开发效率;
《Java 开发手册》中有一条规定:谨慎使用继承的方式进行扩展,优先使用组合的方式实现。
还有关于数组比较的
int[] a={1,2};
int[] b={1,2};
System.out.println(a.equals(b));
》》》输出结果:
false
//正确的比较
System.out.println(Arrays.equals(a, b));
》》》输出结果:
true
因为没有弄清楚什么是对象锁,什么是类锁,导致线上问题
//编写两个线程模拟多用户操作
public class Demo implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
update();
}
//更新数据的方法
public void update(){
synchronized (this) { //this 为对象锁
System.out.println("开始线程"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("结束线程"+Thread.currentThread().getName());
}
}
public static void main(String[] args){
Demo a=new Demo(); //用户a
Demo b=new Demo(); //用户b
Thread threadA=new Thread(a);
Thread threadB=new Thread(b);
threadA.start();
threadB.start();
}
}
》》输出:
开始线程Thread-0
开始线程Thread-1
结束线程Thread-0
结束线程Thread-1
//线程同时开始同时结束 加锁失败
解决办法加类锁,这里只是举例,实际业务可能还有更复杂的设计,确保访问的效率
public void update(){
synchronized (Demo.calss) { //*.calss 为类锁 或者 加在静态方法上
System.out.println("开始线程"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("结束线程"+Thread.currentThread().getName());
}
}
你是否有过通过遍历一个ArrayLIst
,然后做过滤操作?
List<String> list= new ArrayList<String>() {{
add("a");
add("b");
add("c");
add("d");
}};
for (String str: list) {
if (str.equals("b")) {
list.remove(str);
}
}
System.out.println(list);
最后的结果是什么呢?
抛出异常java.util.ConcurrentModificationException
具体原因不展开分析了,需要的小伙伴可以给我留言,我再写一篇详细讲解。
这时候有小伙伴是不是开始尝试使用下标进行删除了
List<String> list= new ArrayList<String>() {{
add("a");
add("b");
add("c");
add("d");
}};
for (int i=0;i<list.size();i++) {
if (str.get(i).equals("b")) {
list.remove(i);
}
}
System.out.println(list);
这样为啥就会成功呢?
每次遍历的时候,都会重新执行list.size()
获取新的长度 看到这是不是也猜到了remove
的时候会改变内部的数组长度。
写段代码举个例子。新手总是喜欢复制粘贴
Map<Demo,String> map=new HashMap(2);
Demo a=new Demo("001");
map.put(a, "aaaa"); //使用对象作为key
//使用时,使用新的对象(内部的值是一样的) 同样可以获取值
//但是新手由于只是模仿了写法,获取到值总是null,百思不得其解,还说之前的代码就是这么写的啊
System.out.println(map.get(new Demo("001")));
这就是典型的没弄清楚HashMap底层原理的
所以说面试时,hashMap
底层实现是必问的问题,简单提一下就是,为了保证获取元素的效率。底层通过数组的形式存放,通过hashCode()
计算出放到哪一个数组的位置,下次获取时可以通过hashCode()
计算出数组下标拿到对应的值。
那么这样算下标肯定会又算成相同的啊,也就是hash碰撞,那么怎么解决呢?那么就在数组位置再增加一个链表(hash桶),再通过equals比较key的值,为了再次提高hash桶查询的效率,JDK8以后是链表+红黑树的结构。
了解到原理后,我们就可以知道 原来是要实现hashCode()
equals(Object obj)
这两个方法
//求一个数的是否是奇数
int num=5;
System.out.println(num%2==1); //对2求余数 == 1就是奇数
int num =-5;
num&2; //结果为-1
num%2!=0; //这样判断才是正确的
System.out.println(0.5-0.3);
》》》输出:
0.2 测试通过
//如果多考虑一些数据可能就不通过了
System.out.println(0.5-0.4);
》》》输出:
0.09999999999999998
String name="aaa";
Demo demo=null;
return "aaa".equals(name)?"aaaaa":demo.getName();
//如果这时,把name的值改成bbbb呢?
List<Demo> list=new ArrayList();
Demo a=new Demo("001");
Demo b=null;
list.add(a);
list.add(b);
for(Demo demo:list){
demo.getName(); //这就会报异常
}
引用阿里的Java编程规范
编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
C:Correct,正确的输入,并得到预期的结果。
D:Design,与设计文档相结合,来编写单元测试。
E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果
程序员的路注定不顺利,一路上可能不止九九八十难,所以我们需要团结,相互学习,总结工作中遇到的问题
当然我这篇文章只是抛砖引玉
欢迎各位大佬留言你们在工作中发现的问题