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()方法的,两者是殊途同归,没有差别
*/
/**
* 注意
* 若非必要,不要设置随机数种子
*/
}
}