建议19:断言绝对不是鸡肋
Java对assert默认是不开启的,使用时,编译时需要用javac -source;执行时需要使用java -ea。
由于带有assert语句的程序运行时,使用了新的ClassLoader和Class类,因此,这种程序必须在JDK1.4(或者更高版本)的JRE下运行,而不能在老版本的JRE下运行。
由于我们可以选择开启assertion功能,或者不开启,另外我们还可以开启一部分类或包的assertion功能,所以运行选项变得有些复杂。通过这些选项,我们可以过滤所有我们不
关心的类,只选择我们关心的类或包来观察。下面介绍两类参数:
参数 -esa 和 -dsa:
它们含义为开启(关闭)系统类的assertion功能。由于新版本的Java的系统类中,也使了assertion语句,因此如果用户需要观察它们的运行情况,就需要打开系统类的assertion功
能 ,我们可使用-esa参数打开,使用 -dsa参数关闭。
-esa和-dsa的全名为-enablesystemassertions和-disenablesystemassertions,全名和缩写名有同样的功能。
参数 -ea和-ea:
它们含义为开启(关闭)用户类的assertion功能:通过这个参数,用户可以打开某些类或包的assertion功能,同样用户也可以关闭某些类和包的assertion功能。打开assertion功
能参数为-ea;如果不带任何参数,表示打开所有用户类;如果带有包名称或者类名称,表示打开这些类或包;如果包名称后面跟有三个点,代表这个包及其子包;如果只有三个点
,代表无名包。关闭assertion功能参数为-da,使用方法与-ea类似。
-ea和-da的全名为-enableassertions和-disenableassertions,全名和缩写名有同样的功能。
下面表格表示了参数及其含义,并有例子说明如何使用。
参数 例子 说明
-ea java -ea 打开所有用户类的assertion
-da java -da 关闭所有用户类的assertion
-ea:<classname> java -ea:MyClass1打开MyClass1的assertion
-da:<classname> java -da: MyClass1关闭MyClass1的assertion
-ea:<packagename> java -ea:pkg1打开pkg1包的assertion
-da:<packagename> java -da:pkg1关闭pkg1包的assertion
-ea:... java -ea:...打开缺省包(无名包)的assertion
-da:... java -da:...关闭缺省包(无名包)的assertion
-ea:<packagename>... java -ea:pkg1...打开pkg1包和其子包的assertion
-da:<packagename>... java -da:pkg1...关闭pkg1包和其子包的assertion
-esa java -esa打开系统类的assertion
-dsa java -dsa关闭系统类的assertion
综合使用 java -dsa:MyClass1:pkg1关闭MyClass1和pkg1包的assertion
java编程语言中断言的关键字 assert,其基本的使用方法
assert expression1
或者
assert expression1 : expression2
assert两个特性,1:assert默认是不启用的,2:assert抛出的异常AssertError是继承自Error
assert不要使用的情况,1:在对外公开的方法中,不要使用 2:在使用assert功能时不要惨呀业务逻辑,因为java对assert的支持是可选的。
assert可以使用的情况,1:在私有方法中放置assert作为输入参数的校验,2:流程控制中不可能到达的区域,因为当程序执行到这里的时候就是错误的。
3:建立程序探针
例如,在一个财会系统中,公司的支出和收入必须保持一定的平衡关系,因此我们可以编写一个表达式检查这种平衡关系,如下表示。
private boolean isBalance() {
……
}
在这个系统中,在一些可能影响这种平衡关系的方法的前后,我们都可以加上assert验证:assert isBalance():"balance is destoried";
挂在一个连接,一个写的关于java assert不错的文章
建议20:不要只替换一个类
final 修饰的基本类型和String类型,编译器会认为他是稳定态,所以在编译时就直接把值编译到字节码中了,避免了在运行期引用,以提高代码的执行效率。
final 修饰的类(即非基本类型),编译器认为它是不稳定态,在编译时建立的则是引用关系。
建议21:用偶判断,不用奇判断
public void test() {
int i = -1;
System.out.println(i % 2 == 1 ? "奇数" : "偶数");
}
打印结果是,偶数。我们先来了解一下java中的取余算法,模拟代码如下:
被除数 除数
public static int remainder(int dividend, int divisor) {
return dividend - dividend / divisor * divisor;
}
这回大家明白了吧,
修改为 System.out.println(i % 2 == 0 ? "偶数" : "奇数"); 就好了。
建议22:使用整数类型处理货币
执行代码
System.out.println(10 - 9.4); 运行结果是 0.5999999999999996,至于为什么给我就不说了,大家可以上网查阅一下相关资料(嘻嘻,其实我也不是很明白)。
这个时候,可以把参与运算的值扩大到整数,计算后的结果在相应的再缩小倍数。如果有更好的办法,欢迎大家评论。
建议23:不要让类型默默的转换
int i = 30 * 10000 * 1000;
System.out.println(i);
long L = i * 60 * 8;
System.out.println(L);
执行的结果是:
300000000
-2028888064
为什么第二个数是负数呢,那是因为java是先运算然后再进行类型转换的,具体的说就是因为 L 的三个运算参数都是int类型的,三者相乘的结果虽然也是int类型的,
但是已经超过了int的最大值(基本类型:int 二进制位数:32(即占用四个字节)包装类:java.lang.Integer 最小值:-2147483648最大值:2147483647),所以
其值就是负值了(为什么是负值?应为过界了就会从头开始,意思是 2147483647 加上 1 结果是 2147483648,以此类推),在转换成long型,结果还是负值。
将代码修改为 long L = i * 60L * 8; 60L是一个长整形,乘出来的结果也是一个长整形(此乃java的基本转换规则,向数据范围大的方向转换,也就是加宽类型)。
在实际开发中,更通用的做法是主动的声明式类型转化(注意不是强制的类型转换),代码如下:long L = 1L * i * 60 * 8;(注意,基本类型转换时,使用主动声明式
减少不必要的bug)。
建议25:不要让四舍五入亏了一方
具体的内容就不说了,下面就列举一下java支持的一下七种舍入方式(想采用什么舍入模式使用RoundingMode设置即可):
ROUND_UP:远离零方向舍入
ROUND_DOWN:趋向零方向舍入
ROUND_CEILING:向正无穷方向舍入
ROUND_FLOOR:向负无穷方向舍入
HALF_UP:最近数字舍入(5进),这就是我们最最经典的四舍五入模式
HALF_DOWN:最近数字舍入(5舍),在四舍五入中,5是进位的,而在HALF_DOWN中却是舍弃不进位的。
HALF_EVEN:银行家算法。
关于以上具体的舍入计算方法就不说了,请大家查看java的JDK文档
在普通的项目中舍入模式不会有太多影响,可以直接使用Math.round()方法,但在大量与货币数字交互的项目中,一定要选择好近似的计算模式,尽量减少因算法
不同而造成的损失。
建议26:提防包装类型的null
执行以下方法
public static void main(String[] args) {
List<Integer> l = new ArrayList<Integer>();
l.add(1);
l.add(2);
l.add(null);
int count = 0;
for(int i : l) {
count += i;
}
System.out.println(count);
}
代码的运行结果是,报错 Exception in thread "main" java.lang.NullPointerException,报错的原因是在程序的for循环中,隐含了一个拆箱过程,
在此过程中包装类型转换为了基本类型。拆箱的过程调用的是对象的 intValue() 方法来实现的,由于包装类型是 null 值,访问intValue() 方法报空
指针异常也就在所难免了。
将count ++ i 修改为 count += (i != null) ? i : 0 就ok了
对于此类问题,我们要谨记一点:包装类型参与运算时,要做null值校验。