一.在非idea编译的情况下,不要只替换一个类
我们经常在系统中定义一个常量接口(或常量类),以囊括系统中涉及的常量,从而简化代码,方便开发,在很多的开源项目中采用了类似的方法,比如在Struts2中,org.apache.struts2.StrutsConstants就是一个常量类,它定义了一个Status框架中配置的有关的常量,而org.apache.status2.StatusStatics则是一个常量接口,其中定义了一个OGNL访问的关键字。
关于常量接口(类)我们来看一个例子,首先定义一个常量类:
public class Constant{
//定义人类寿命的极限
public final static int MAX_AGE = 150;
}
这是一个非常简单的常量类,定义了一个人类的最大年龄,我们引用这个常量,代码如下:
public class Client {
public static void main(String[] args){
System.out.println("人类的极限寿命:"+Constant.MAX_AGE);
}
}
运行的结果非常简单。目前的代码编写都是在“智能型”IDE工具中完成的,下面我们暂时回溯到原始时代,也就是回归到记事本编写代码的时代,然后看看会发生什么奇妙的事情把。
修改常量类Constant类,人类的寿命增加了,最大能活到180岁,代码如下:
public class Constant {
//定义人类的极限寿命
public final static int MAX_AGE = 180;
}
然后重新编译:javac Constant, 编写完成后执行:java Client,大家想看看输出的极限年龄是多少码?
输出的结果是:“人类寿命极限是:150”,竟然没有改变为180,太奇怪了,这是为何?原因是:对于final修饰的基本数据类型和String类型,编译器会认为它是稳定态(Immutable Status),所以在编译时就直接把值编译到字节码中了,避免了在运行期引用(Run-time Referece),以提高代码的执行效率。针对我们的雷子来说,Client类在编译时,字节码中就写不上“150”这个常量,而不是一个地址的引用,因此无论你后续怎么修改常量类,只要不重新编译Client类,输出还是照旧。
而对于final修饰的类(即非基本类型),编译器认为它是不稳定态(Mutable Status),在编译时建立的则是引用关系(该类型也叫做Soft Final),如果Client类引入的常量是一个类或者实例,即使不重新编译也会输出最新值。
呃,我们例子中为什么不在IDE工具中运行呢?那是因为在IDE中不能重现该问题,若修改了Constant类,IDE工具会自动编译多有的引用类,“智能”化屏蔽了该问题,但潜在的风险其实仍然存在。
注意: 发布应用系统时禁止使用类文件替换的方式,整体war包发布才是万全之策。
二.用偶判断,不用奇判断
判断一个数是奇数还是小学里学的基本知识,能够被2整除的整数是偶数,不能被2整除的是奇数,这规则简单又明了,还有什么好考虑?好,我们来看一个例子,代码如下:
public class Client {
public static void main(String[] args){
//接受键盘输入参数
Scanner input = new Scanner(System.in);
System.out.println("请输入多个数字判断奇偶:");
Where(input.hasNextInt()){
int i = input.nextInt();
String str = i+ "->" + (i%2 == 1?"奇数":"偶数");
Syetem.out.println(str);
}
}
}
输入多个数字,然后判断每个数字的奇偶性,不能被2整除就是奇数,其他的都是偶数,完全是根据奇偶数的定义编写的程序,我们看看打印的结果:
前三个还是很靠谱,第四个参数-1怎么可能会是偶数呢,这Java也太差劲了,如此简单的计算也会出错!我们先了解一下Java中的取余(%标示符)算法,模拟代码如下:
//模拟取余计算,dividend被除数,divisor除数
public static int remainder(int dividend,int divisor){
return dividend - dividend / divsor * divsor;
}
看到这段代码,相信大家都会心地笑了,原来Java是这么处理取余计算的呀。根据上面的模拟取余可知,当输入-1的时候,运算结果是-1,当然不等于1了,所以它就是被判断为偶数了,也就是说我们的判断失误了。问题明白了,修正也是很简单,改为判断是否是偶数即可,代码如下:
注意: 对于基础知识,我们应该“知其然,并知其所以然”。
三.边界,边界,还是边界
某商家生产的电子产品非常畅销,需要提前30天预定才能抢到手,同时它还规定了一个会员可拥有的最多产品数量,目的是防止囤积压货肆意加价。会员的预定过程是这样的:先登录官方网站,选择产品型号,然后设置需要预定的数量,提交,符合规则即下单成功,不符合规则提示下单失败。后台的处理逻辑模拟如下:
public class Client {
//一个会员拥有产品的最多数量
public final static int LIMIT = 2000;
public static void main(String[] args){
//会员当前拥有的产品数量
int cur = 1000;
Scanner input = new Scanner(System.in);
System.out.println("请输入需要预定的数量:");
Where(input.hasNext()){
int order = input.nextInt();
//当前拥有的与准备预定的产品数量之和
if(order>0 && order+cur<=LIMIT){
System.out.println("你已经成功预定的"+order+"个产品!");
}else{
System.out.println("超过限额,预定失败!");
}
}
}
}
这是一个简易的订单处理程序,其中cur代表的是会员已经拥有的产品数量,LIMIT是一个会员最多拥有的产品数量,如果当前预定数量与拥有数量之和超过了最大数量,则预定失败,否则下单成功。业务逻辑很简单,同时在web界面上对订单数量做了严格的校验,比如不能是负值、不能超过最大数量等,但是人算不如天算,运行不大俩小时数据库中就出现异常:某会员拥有的产品数量与预定数量之和远远大于限额。我们模拟一下:
看到没,这个数字远远超过2000的限额,但是竟然预定成功了,真实神奇!
看着2147483647这个数字很眼熟?那就对了,它是int类型的最大值,没错,有人输入一个最大值,是校验条件失效了,why?我们来看程序,order的值是2147483647,那在加上1000就超出int的范围了,其结果是-2147482649,那当然是小于正数2000了!一句话可归原因:数字超界使校验条件失效。
在单元测试中,有一项测试叫做边界测试(也有交临界值测试),如果一个方法接受的int类型的参数,那么以下三个值是必测的:0、最大值、最小值,其中正最大和负最小是边界值,如果这三个值都没有问题,方法才是比较安全可靠的。我们的例子就是因为缺少边界测试,导致生产系统产生了严重的偏差。
四.优先使用整形池
看代码我们解决问题,
public static void main(String[] args){
Scanner in = new Scanner(System.in);
while(in.hasNextInt()){
int input = in.nextInt();
System.out.println("\n-----"+ii+"的相等判断-----");
//俩个通过new 产生的对象
Integer i = new Integer(input);
Integer j = new Integer(input);
System.out.println("new产生的对象:"+(i==j));
//基本类型转换成包装类型后比较
i=input;
j=input;
System.out.println("基本类型转换的对象:"+(i==j));
//通过静态方法生成一个实例
i=Integer.valueOf(input);
j=Integer.valueOf(input);
System.out.println("valueOf产生的对象:"+(i==j));
}
}
分别输入三个值,127,128,555产生的结果图:
很不可思议,对吧。那这是为什么呢?
(1)new产生的Integer对象
new声明就是要生成一个新的对象,没二话,这是俩个对象,地址肯定不相等。false
(2)装箱生成的对象
对于这一点,首先要说明的是装箱动作是通过valueOf方法实现的,也就是说后俩个算法是相同的,那结果肯定也是一样。那现在的问题是:valueOf是如何生成对象的呢?我们阅读一下Integer.valueOf的实现代码。
public static Integer valueOf(int i){
final int offset = 128;
if(i>=-128 && i<=127){
return IntegerCache.cache(i);
}
return new Integer(i);
}
显而易见,如果是-128到127之前的int类型转换成Integer对象,则直接从cache数组中取,那这个cache数据是作甚么?
static final Integer cache[] = new Integer[-(-128)+127+1];
static{
for(int i=0; i<cache.length; i++){
cache[i] = new Integer(i-128);
}
}
cache是IntegerCache内部类的一个静态数组,容纳的是-127到128之间的Integer对象。通过valueOf产生包装对象时,如果int参数在-128到127之间,则直接在从整型池中获取对象,不再该范围内的int 类型则通过new 生成包装对象。
总结:通过包装类的valueOf生成包装实例可以显著提高空间和时间性能。