package com.cheng.improve151suggest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.ListView; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import com.cheng.highqualitycodestudy.R; import com.cheng.improve151suggest.adapter.I151SuggestListAdapter; /** 第2章 基本类型 建议21: 用偶判断,不用奇判断 建议22: 用整数类型处理货币 建议23: 不要让类型默默转换 建议24: 边界,边界,还是边界 建议25: 不要让四舍五入亏了一方 建议26: 提防包装类型的null值 建议27: 谨慎包装类型的大小比较 建议28: 优先使用整型池 建议29: 优先选择基本类型 建议30: 不要随便设置随机种子 */ public class I151SChapter02Activity extends AppCompatActivity { private static final String TAG = "I151SChapter02Activity"; private ListView mChapterLV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_i151suggestchapter); initView(); initData(); } private void initView() { this.mChapterLV = (ListView) findViewById(R.id.isi_chapter_lv); } private void initData() { String[] suggests = getResources().getStringArray(R.array.i151schapter2); I151SuggestListAdapter adapter = new I151SuggestListAdapter(this, Arrays.asList(suggests)); mChapterLV.setAdapter(adapter); } /** * 建议21: 用偶判断,不用奇判断 */ private void suggest21() { int [] ints = new int[]{1,2,0,-1,-2}; for (int i = 0, len = ints.length; i < len; i++) { Log.e(TAG, i+"->"+(i%2==1?"奇数":"偶数")); // 输入负数发现判断错误 // 参考下面的模拟取余计算方法就知道导致该问题的原因 // 修正很简单,改为判断是否为0即可,如下: // i%2 == 0 ? "偶数" : "奇数" } /** * 注意 * 对于基础知识,应该“知其然,并知其所以然” */ } // 模拟取余计算,dividend被除数,devisor除数 private int remainder(int dividend, int divisor) { return dividend - dividend / divisor * divisor; } /** * 建议22: 用整数类型处理货币 */ private void suggest22() { Log.e(TAG, 10.00-9.60 + ""); // 期望输出0.4,查看输出结果 // 在计算机中浮点数有可能(注意是可能)是不准确的,它只能无限接近准确值, // 而不能完全精确。这是由浮点数的存储规则所决定的,十进制小数使用“乘2取整,顺序 // 排列”法转换成二进制小数 /** * 注意 * 在要求准确的系统中如何避免浮点数导致的不准确,有如下两种方法: * 1)使用BigDecimal * BigDecimal是专门为弥补浮点数无法精确计算的缺陷而设计的类,并且它本身也提供了加减乘除 * 的常用数学算法。特别是与数据库Decimal类型的字段映射时,BigDecimal是最优的解决方案 * 2)使用整型 * 把参与运算的值扩大100倍,并转变为整型,然后在展现时再缩小100倍,这样处理的好处是技术简单、 * 准确,一般非金融行业(如零售行业)应用较多 */ } /** * 建议23: 不要让类型默默转换 */ private static final int LIGHT_SPEED = 30 * 10000 * 1000; private void suggest23() { Log.e(TAG, "题目1:月亮光想照射到地球需要1秒,计算月亮和地球的距离"); long dis1 = LIGHT_SPEED * 1; Log.e(TAG, "月亮与地球的距离是:" + dis1 + " 米"); Log.e(TAG, "--------------------------------------------"); Log.e(TAG, "题目2:太阳光照射到地球上需要8分钟,计算太阳到地球的距离。"); //可能要超出整数范围,使用long型 long dis2 = LIGHT_SPEED * 60 * 8; Log.e(TAG, "太阳与地球的距离是:" + dis2 + " 米"); // 输出 -202888064 /* 诡异,dis2不是已经考虑到int类型可能越界的问题,并且使用了long类型,为什么还会出现负值? 那是因为Java是先运算然后再进行类型转换的,具体地说就是因为dis2的三个运算参数都是int类型, 三者相乘的结果虽然也是int类型,但是已经超过了int的最大值,所以其值就是负值了,再转换成long 类型,结果还是负值 解决方案: 加上一个“L”即可, 如 long dis2 = LIGHT_SPEED * 60L * 8; 60L是一个长整型,乘出来的结果也是一个长整型。 在实际开发中,更通用的做法是主动声明式类型转化(注意不是强制类型转换),代码如下: long dis2 = 1L * LIGHT_SPEED * 60 * 8; */ /** * 注意 * 基本类型转换时,使用主动声明方式减少不必要的Bug */ } /** * 建议24: 边界,边界,还是边界 */ private static final int LIMIT = 2000; // 一个会员拥有产品的最多数量 private void suggest24() { // 会员当前拥有的产品数量 int cur = 1000; int [] inputInts = new int[]{800, 2147483647}; for (int i=0, len=inputInts.length; i < len; i++) { int order = inputInts[i]; Log.e(TAG, "请输入需要预订的数量:" + order); // 当前拥有的与准备订购的产品数量之和 if (order>0 && order+cur<=LIMIT) { Log.e(TAG, "你已经成功预订的 " + order + " 个产品"); } else { Log.e(TAG, "超过限额,预订失败!"); } } // 竟然会输出:你已经成功预订的 2147483647 个产品 // Why?来看程序,order的值是2147483647,那再加上1000就超出int的范围了,其结果是 // -2147482649,是个负数,当然小于2000.一句话可归结其原因:数字越界使检验条件失效 /** * 注意 * 边界测试,如果一个方法接收的是int类型的参数,那以下三个值是必测的:0、正最大值、 * 负最小值,其中正最大和负最小是边界值,如果这三个值都没有问题,方法才是比较安全靠谱的 */ } /** * 建议25: 不要让四舍五入亏了一方 */ private void suggest25() { Log.e(TAG, "10.5的近似值:" + Math.round(10.5)); // 11 Log.e(TAG, "-10.5的近似值:" + Math.round(-10.5)); // 10 // 这是四舍五入的经典案例,绝对值相同的两个数字,近似值为什么就不同了呢?这是由Math.round // 采用的舍入规则所决定的(采用的是正无穷方向舍入规则)。我们知道四舍五入是有误差的:其误差 // 值是舍入位的一半。下面以银行利息计算为例来阐述该问题 // 银行账户数量,5千万 int accountNum = 5000 * 10000; // 按照人行的规定,每个季度月末的20日为银行结息日 double cost = 0.0005 * accountNum * 4; Log.e(TAG, "银行每年损失的金额:" + cost); /* 对于这个算法的误差,美国银行家提出了一个修正算法,叫做银行家舍入(Banker's Round)的近似算法, 其规则如下: 舍去位的数值小于5时,直接舍去 舍去位的数值大于6时,进位后舍去 当舍去位的数值等于5时,分两种情况:5后面还有其他数字(非0),则进位后舍去;若5后面是0(即5是 最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去 以上规则汇总成一句话:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇 要进一。举例说明,取2位精度: round(10.5551) = 10.56 round(10.555) = 10.56 round(10.545) = 10.54 要在Java5以上版本中使用银行家的舍入法则非常简单,直接使用RoundingMode类提供的Round模式即可, 示例如下: */ // 存款 BigDecimal d = new BigDecimal(888888); // 月利率,乘3计算季利率 BigDecimal r = new BigDecimal(0.001875 * 3); // 计算利息 BigDecimal i = d.multiply(r).setScale(2, RoundingMode.HALF_EVEN); // 表示使用银行家舍入法则进行近似计算 Log.e(TAG, "季利息是:" + i); /* 目前Java支持以下七种舍入方式: ROUND_UP:远离零方向舍入(向绝对值最大的方向舍入,只要舍弃位非0即进位) ROUND_DOWN:趋向零方向舍入(向绝对值最小的方向舍入,所有的位都舍弃,不存在进位) ROUND_CEILING:向正无穷方向舍入(Math.round方法使用的即为此模式) ROUND_FLOOR:向负无穷方向舍入 HALF_UP:最近数字舍入(5进)(最最经典的四舍五入模式) HALF_DOWN:最近数字舍入(5舍) HALF_EVEN:银行家算法 */ /** * 注意 * 根据不同的场景,慎重选择不同的舍入模式,以提高项目的精确度,减少算法损失 */ } /** * 建议26: 提防包装类型的null值 */ private void suggest26() { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(null); // 将空值放入List中,是否会自动转换为0?会不会报错? Log.e(TAG, "f(list)=" + f(list)); // 运行失败,报空指针异常 /* 在程序的for循环中,隐含了一个拆箱过程,在此过程中包装类型转换为了基本类型。我们知道 拆箱过程是通过调用包装对象的intValue方法来实现的,由于包装对象为null,访问其intValue 方法报空指针异常也就在所难免了。 解决方案: 加入null值检查即可,如下: count += (i!=null)?i:0; */ /** * 注意 * 包装类型参与运算时,要做null值校验 */ } // 接收一个元素是整数的List参数,计算所有元素之和 private int f(List<Integer> list) { int count = 0; for (int i : list) { count += i; } return count; } /** * 建议27: 谨慎包装类型的大小比较 */ private void suggest27() { Integer i = new Integer(100); Integer j = new Integer(100); // 比较两个包装对象大小 Log.e(TAG, "i == j : " + (i == j)); // false Log.e(TAG, "i > j : " + (i > j)); // false Log.e(TAG, "i < j : " + (i < j)); // false /** * 注意 * 在Java中“==”是用来判断两个操作数是否有相等关系的,如果是基本类型则判断值是否相等,如果 * 是对象则判断是否是一个对象的两个引用,也就是地址是否相等 * 在Java中“>”和“<”用来判断两个数字类型的大小关系,注意只能是数字型的判断,对于Integer包装 * 类型,是根据其intValue()方法的返回值(也就是其相应的基本类型)进行比较的 * 对于两个对象之间的比较就应该采用相应的方法,而不是通过Java的默认机制来处理 */ } /** * 建议28: 优先使用整型池 */ private void suggest28() { int[] ints = new int[]{127, 128, 555}; for (int i=0,len=ints.length; i < len; i++) { int ii = ints[i]; Log.e(TAG, "\n====" + ii + " 的相等判断======"); // 两个通过new产生的Integer对象 Integer iInteger = new Integer(ii); Integer jInteger = new Integer(ii); Log.e(TAG, "new 产生的对象:" + (iInteger==jInteger)); // 基本类型转为包装类型后比较 iInteger = ii; jInteger = ii; Log.e(TAG, "基本类型转换的对象" + (iInteger==jInteger)); // 通过静态方法生成的对象 iInteger = Integer.valueOf(ii); jInteger = Integer.valueOf(ii); Log.e(TAG, "valueOf产生的对象" + (iInteger==jInteger)); } /* 结果: ====127===== false true true ====128==== false false false ====555==== false false false */ /* 很不可思议,数字127的比较结果竟然与其他两个数字不同,它的装箱动作所产生的对象竟然是 同一个对象,valueOf产生的也是同一个对象,但是大于127的数字128和555在比较过程中所产 生的却不是同一个对象,这是为什么? 1)new产生的Integer对象 new声明的就是要生成一个新对象,这是两个对象,地址肯定不同 2)装箱生成的对象 首先要说明的是装箱动作是通过valueOf方法实现的,也就是说后两个算法是相同的,那结果肯定 也是一样的,现在的问题是:valueOf是如何生成对象的呢?来看下Integer.valueOf的源码: public static Integer valueOf(int i) { final int offset = 128; if (i >= -128 && i <= 127) { // must cache return IntegerCache.cache[i + offset]; } return new Integer(i); } cache是IntegerCache内部类的一个静态数组,容纳的是-128到127之间的Integer对象。通过 valueOf产生包装对象时,如果int参数在-128到127之间,则直接从整型池中获得对象,不在该 范围的int类型则通过new生成包装对象 整型池的存在不仅仅提高了系统性能,同时也节约了内存空间,这也是使用整型池的原因,也就是 在声明包装对象的时候使用valueOf生成,而不是通过构造函数来生成的原因。顺便提醒大家,在 判断对象是否相等的时候,最好是用equals方法,避免用“==”产生非预期结果 */ /** * 注意 * 通过包装类的valueOf生成包装实例可以显著提高空间和时间性能 */ } /** * 建议29: 优先选择基本类型 */ private void suggest29() { int i = 140; // 分别传递int类型和Integer类型 f29(i); // 输出:基本类型的方法被调用 f29(Integer.valueOf(i)); // 输出:基本类型的方法被调用 /* 整个f29(Integer.valueOf(i))的执行过程是这样的: i通过valueOf方法包装成一个Integer对象 由于没有f(Integer i)方法,编译器“聪明”地把Integer对象转换成int int自动拓宽为long,编译结束(使用的是f29(long a)方法) */ /** * 注意 * 重申,基本类型优先考虑 */ } private void f29(long a) { Log.e(TAG, "基本类型的方法被调用"); } private void f29(Long a) { Log.e(TAG, "包装类型的方法被调用"); } /** * 建议30: 不要随便设置随机种子 */ private void suggest30() { Random r1 = new Random(); for (int i = 0; i < 4; i++) { Log.e(TAG, "第" + i + "次" + r1.nextInt()); } Random r2 = new Random(1000); for (int i = 0; i < 4; i++) { Log.e(TAG, "第" + i + "次" + r2.nextInt()); } /* 多次打印后会发现r1产生的随机数都是是不同的,而r2打印的随机数,似乎不随机了,几次 打印出来的几个数是相同的,问题何在? 这是因为产生随机数的种子被固定了,在Java中,随机数的产生取决于种子,随机数和种子 之间的关系遵从以下两个规则: 种子不同,产生不同的随机数 种子相同,即使实例不同也产生相同的随机数 顺便提一下,在Java中有两种方法获得不同的随机数:通过java.util.Random类获得随机 数的原理和Math.random方法相同,Math.random()方法也是通过生成一个Random类的实例, 然后委托nextDouble()方法的,两者是殊途同归,没有差别 */ /** * 注意 * 若非必要,不要设置随机数种子 */ } }