【effective java读书笔记】通用程序设计(二)

本文是Effective Java读书笔记的第二部分,主要讨论了编程中的精度问题,建议在需要精确答案时避免使用double和float。文章通过示例展示了如何改进代码以避免不均匀分布的问题,并解释了为何基本类型优于装箱基本类型。此外,还通过实例演示了装箱类型的潜在陷阱。
摘要由CSDN通过智能技术生成

前言:

以前曾经做ERP时,总是遇到尾差尾数的问题。比如:库存一箱苹果10元钱,假如一共3个苹果。拿走两个还剩多少钱。好了,言归正传,回到今天的读书笔记,精度问题。

第47条:了解和使用类库;

代码零:

public class RandomTest {
	private static final Random rnd = new Random();
	public static void main(String[] args) {
		int n = 2*(Integer.MAX_VALUE/3);
		int low = 0;
		for (int i = 0; i < 1000000; i++) {
			if (random(n)<n/2) {
				low++;
			}
		}
		System.out.println(low);
		
	}

	static int random(int n){
		return Math.abs(rnd.nextInt())%n;
	}
}
执行结果:

667068

这个结果不论多少次,都大概是这个66万左右。说明得到结果并没有均匀分布。看看我们使用的方案:

Math.abs(rnd.nextInt())%n

随机取Int值的绝对值 与 n 取余 ,也就是绝对值比n小的都过滤掉再与n取余的结果集。至于为什么没有均匀分布,不深究。


改进后的代码零:

public class RandomTest2 {
	private static final Random rnd = new Random();
	public static void main(String[] args) {
		int n = 2*(Integer.MAX_VALUE/3);
		int low = 0;
		for (int i = 0; i < 1000000; i++) {
			if (random(n)<n/2) {
				low++;
			}
		}
		System.out.println(low);
	}
	static int random(int n){
		return rnd.nextInt(n);
	}
}
执行结果:

499683

大概是一半左右。符合要求。当然这个是类库提供给我们的方案。

第48条,如果需要精确的答案,避免使用double和float;

代码一:

public class ExactTest {
	public static void main(String[] args) {
		System.out.println(1.03-0.42);
		System.out.println(1.00-9*0.10);
	}
}

执行结果:

0.6100000000000001

0.09999999999999998

这个结果是不是很意外?继续看第二段代码。

代码二:

我有1元钱,第一次花了0.1,第二次花0.2,第三次花0.3,第四次花0.4,以此类推到买不起。按道理说,我应该刚好买4个商品。

@Test
	public void test() {
		double funds = 1.0;
		int itemsBought = 0;
		for (double price = 0.1; funds >= price; price += 0.1) {
			funds -= price;
			itemsBought++;
		}
		System.out.println(itemsBought + "items bought.");
		System.out.println("Change:$" + funds);
	}
执行结果:

3items bought.

Change:$0.3999999999999999

一开始看到结果我是拒绝的。

原因:double,float没办法精确表示0.1。


改进版本代码二:

@Test
	public void test2() {
		BigDecimal funds = new BigDecimal("1.00");
		int itemsBought = 0;
		final BigDecimal TEN_CENTS = new BigDecimal("0.10");
		for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
			funds = funds.subtract(price);
			itemsBought++;
		}
		System.out.println(itemsBought + "items bought.");
		System.out.println("Change:$" + funds);
	}
执行结果:

4items bought.

Change:$0.00

非常棒!精确以后就用BigDecimal吧,更好的是8种想要的四舍五入都有。

第49条,基本类型优先于装箱基本类型;

代码三:

@Test
	public void test3() {
		Comparator<Integer> naturalOrder = new Comparator<Integer>() {
			@Override
			public int compare(Integer first, Integer second) {
				return first < second ? -1 : (first == second ? 0 : 1);
			}
		};
		System.out.println("规则:A比B小结果-1,A与B相等结果0,A比B大结果1");
		System.out.println("1和2比较的结果"+naturalOrder.compare(1, 2));
		System.out.println("2和2比较的结果"+naturalOrder.compare(2, 2));
		System.out.println("3和2比较的结果"+naturalOrder.compare(3, 2));
	}
执行结果:

规则:A比B小结果-1,A与B相等结果0,A比B大结果1

1和2比较的结果-1

2和2比较的结果0

3和2比较的结果1

似乎、好像是正确的呢,那如果我这样测试一下呢?

@Test
	public void test6() {
		Comparator<Integer> naturalOrder = new Comparator<Integer>() {
			@Override
			public int compare(Integer first, Integer second) {
				return first < second ? -1 : (first == second ? 0 : 1);
			}
		};
		Integer first = new Integer(42);
		Integer second = new Integer(42);
		switch (naturalOrder.compare(first, second)) {
		case 1:
			System.out.println("第一个数"+first+"大于第二个数"+second);
			break;
		case 0:
			System.out.println("第一个数"+first+"等于第二个数"+second);
			break;
		case -1:
			System.out.println("第一个数"+first+"小于第二个数"+second);
			break;
		default:
			break;
		}
	}
执行结果:

第一个数42大于第二个数42

这似乎是不可理解,但看这个代码仔细分析一下:

first < second ? -1 : (first ==second ? 0 : 1)

先比较大小,拆箱。42<42所以执行后面的

(first == second ? 0 : 1)

但是==这个符号却不会造成拆箱,对引用对象来说,会比较两个对象是否指向同一个引用

Integer first =new Integer(42);

Integer second =new Integer(42);

很明显不是同一个对象引用,结果返回1。“第一个数42大于第二个数42”。

代码四:

public class ExactTest3 {
	static Integer i;
	public static void main(String[] args) {
		if (i == 42) {
			System.out.println("unbelivable");
		}
	}
}

执行结果:

Exception in thread "main"java.lang.NullPointerException

at com.test.ExactTest3.main(ExactTest3.java:6)

这个原因也是一个令人迷惑的原因。Integer包装类型和基本类型相互比较,会造成包装类型拆包。然而Integer=null,所以空指针了。

代码五:

	
	@Test
	public void test4(){
		long currentTime = System.currentTimeMillis();
		Long sum = 0L;
		for (long i = 0; i < Integer.MAX_VALUE; i++) {
			sum +=i;
		}
		System.out.println(sum);
		long lastTime = System.currentTimeMillis();
		System.out.println(lastTime - currentTime);
	}	
	
	@Test
	public void test5(){
		long currentTime = System.currentTimeMillis();
		long sum = 0L;
		for (long i = 0; i < Integer.MAX_VALUE; i++) {
			sum +=i;
		}
		System.out.println(sum);
		long lastTime = System.currentTimeMillis();
		System.out.println(lastTime - currentTime);
	}

执行结果1:

2305843005992468481

8524

执行结果2:

2305843005992468481

972

结果1耗时8秒多,结果2耗时0.9秒,差了近10倍。

原因在sum在代码中被错误的声明为Long包装类型。每次执行下操作时:

sum +=i;

会先将sum拆箱后执行+运算,再装箱赋值给sum。反复如此,性能极低。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值