最近泛泛地读了这本《Effective Java》,不得不说这本书中的许多建议都非常不错,看过一遍后就会觉得作者的这些建议都不会过时。对于我们的编程思路会有许多帮助和提升!在此我将不断更新一些暂时我能理解的比较好的建议。也推荐大家看下这本书!ヾ(๑╹◡╹)ノ"
一、消除非受检警告 (10.31日)晴
在日常编程中,我们会遇到许多的警告,有些警告比较好消除,但同时也有一些警告并不是很好消除。加入消除了所有的警告则表明代码是类型安全的,这是一件值得高兴的事,你也会更加自信自己的代码是可以实现预期的功能。
但是如果这个警告消除不了,你又清楚得知道警告的代码是安全的,对程序的整体运行不会造成大的影响。只有在这种情况下才可以考虑使用@SuppressWarnings(“unchecked”)注解来禁止这条警告。若你在禁止警告之前并未确认这个 警告是安全的那么就是自欺欺人了,代码在编译的时候不会报错,但是在运行的过程中仍然会出错。
@SuppressWarnings 注解可以用在任何粒度的级别中,从单独的局部变量声明至整个类都可以使用。比较推荐使用的粒度越小越好,切记不要在整个类上使用@SuppressWarnings,这样可能会掩盖比较重要的警告。
每当使用@SuppressWarnings(“unchecked”)注解时一定要添加相应的注释来解释为什么这么做是安全的,这样既可以帮助他人理解代码也可以帮助自己在日后更快地理解,就是所谓的帮助别人的同时你也在帮助自己。如果你觉得这个注释很难写,那么就要思考这个注解有没有必要了。
附上一段话,我觉得写得是非常好的关于@SuppressWarnings了。虽然不记得是哪位大神写得了,但觉得真的很棒!
J2SE 提供的最后一个批注是
@SuppressWarnings。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。@SuppressWarnings
批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,您将调查它,如果您确定它不是问题,您就可以添加一个
@SuppressWarnings
批注,以使您不会再看到警告。虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 —
您看到的每一个警告都将值得注意
二、当心字符串的连接功能(11.2)晴
今天是11月的第二天,也是备战双十一的第二天,在这个晴朗的天气下,我进行了第二次更新!( ̄▽ ̄)/话不多说,开始正题吧!
我们都知道字符串连接操作符("+")是可以把多个字符串合并为一个字符串的便利途径,如果你只是想要产生单行输出,或者只是构造一个字符串来表示一个较小的、大小固定的对象,使用连接符是非常便利的。但是并不适合运用在大规模的场景中。为了连接n个字符串而重复使用"+"则需要n的平方级时间。这是因为字符串是不可变的。当两个字符串被连接在一起的时候不,它们的内容都要被copy。
例如参考以下的代码片段:
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
String str = "";
for(long i = 0; i < 50000;i++) {
sb.append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
startTime = System.currentTimeMillis();
for(long i = 0; i < 50000;i++) {
str += i;
}
endTime = System.currentTimeMillis();
System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
}
上述的两种代码的性能差别非常大,在我的机器上使用StringBuilder大概的时间为10ms,但是用String的时间是5798ms。因为StringBuilder的开销会随着数量呈线性增加而String则会随着数量呈平方级增加。所以需要增加的字段越多用"+"则消耗的时间越多。
所以在日常编程中如果字符串需要的变动会比较大的话就采用StringBuilder的形式,如果字符串变动较小则使用String的形式会比较好。
今天就到此结束啦 拜拜!( ̄▽ ̄)~*
三、坚持使用@Override注解 11.5小雨
在学习JavaSE的时候,我们肯定都会接触到两个概念 分别是重写和重载。重写意味着子类修改了父类的方法,而重载则是指在一个类中具有多个同名方法但其参数列表不同。
在编程中我们常常会接触到的一个方法就是equals(),如"abc".equal(“123”)。当用作在String类的时候我们常常不会去动这方法,但是当我们其他类需要用到equals()时且希望根据自己的判断条件决定是否相等的情况下,我们可能有时候会去手写这段代码(虽然现在更多时候是直接用IDE去生成)那么就可能产生错误如下面这段代码:
public class Dog extends Animal implements Serializable{
private String type;
private String name;
private int size;
public Dog(String type, String name,int size) {
super();
this.name = name;
this.type = type;
this.size = size;
}
@Override
public String toString() {
return "Dog [type=" + type + ", name=" + name + ", size=" + size + "]";
}
@Override
public int hashCode() {//如果要重写equals方法一定也要同时重写hashCode方法哦
// TODO Auto-generated method stub
return size;
}
public boolean equals(Dog dog) {
return size == dog.size;
}
然后接着执行以下程序
@Test
public void fun() {
Dog dog = new Dog("狗","巴哥犬",1);
Dog dog2 = new Dog("狗","哈士奇",1);
Set<Dog> set = new HashSet<Dog>();
set.add(dog);
set.add(dog2);
System.out.println(dog.equals(dog2));
for(Dog dog3 : set) {
System.out.println(dog3);
}
}
可能你会觉得set中只会存储一个Dog对象,因为HashSet会先去比较该类hashCode是否相等,再会去调用equals方法(这里我们把hashcode写死了)。得到输出如下
true
Dog [type=狗, name=巴哥犬, size=1]
Dog [type=狗, name=哈士奇, size=1]
诶,明明equals是显示true,根据判断应该不会加入set中去啊。问题出现在因为这个equals方法是我们自己写得,且没有添加@Override注解,JVM也不会认为代码存在问题。而set.add中最终是根据父类的方法
public boolean equals(Object obj)。所以才会出现以上的错误。
只要你在原来的equals(Dog dog)上添加@Override注解后IDE就会马上提示你这里出现了问题快去给我改改。
所以在我们写这些父类的方法的时候不要自作聪明哦。如果手写的话还是先添加个@Override注解会比较可靠。当然现在都是IDE自动生成啦~~( ̄▽ ̄)~~
如果要重写equals方法最好也要同时重写hashCode方法哦!
如果要重写equals方法最好也要同时重写hashCode方法哦!!
如果要重写equals方法最好也要同时重写hashCode方法哦!!!
四、如果需要精确的答案,那么就要避免使用float和double 11.9雨过天晴
float和double类型主要是为了科学计算和工程计算而设计的。它们执行二进制浮点运算,是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计。然而它们并没有提供完全精确的结果,所以不应该被用于需要精确结果的场合。float和double尤其不适合用于货币计算。因为要让一个float或者double精确地表示0.1(或者10的任何其他负数次方)是不可能的。——书本原话
请看下面的测试代码
比如1.03 - 0.42 = 0. 61
1.00 - 9 * 0.1 = 0.1
相信大家都毫无疑问吧,但是
@Test
public void fun1() {
System.out.println("1.03 - 0.42 = " + (1.03 - 0.42));
System.out.println("1.00 - 0.1 * 9 = " + (1.00 - 0.1 * 9));
}
得到的结果如下:
这和我们算出来的结果相差好多。
还看一个相似的例子
如果有个商品第一次买是0.1元第二次买是0.2元…每次都会增加0.1元 那么你有1.00元你可以买多少个呢?
发挥我数学学渣的水平得出的结论是 4个 然后我刚好没钱但是java得到的结果却是这样的
@Test
public void fun2() {
double money = 1.00;
int item = 0;
for(double singleMoney = 0.1; money >= singleMoney; singleMoney+=0.1) {
money -= singleMoney;
item ++;
}
System.out.println("购买了多少个item: " + item );
System.out.println("还剩多少钱:" + money );
}
运行结果如下:
惊了! … 这又一次出错了。
那么我们该怎么解决这个问题呢 , 不然以后用代码写数学运算都不行了。
解决这个问题的正确办法就是使用BIgDecimal、int、long
下面是一个BigDecimal的简单翻版
@Test
public void fun5() {
final BigDecimal SINGLE_MONEY = new BigDecimal(".10");
int item = 0;
BigDecimal allMoney = new BigDecimal("1.00");
for(BigDecimal price = SINGLE_MONEY;
allMoney.compareTo(price) >= 0;
price = price.add(SINGLE_MONEY)) {
item++;
allMoney = allMoney.subtract(price);
}
System.out.println("购买了多少个item: " + item );
System.out.println("还剩多少钱:" + allMoney );
}
控制台输出
呼,可算是放心了。
然而使用BigDecimal有两个缺点(1)与使用基本运算类型相比,(2)这样做会非常的不方便而且会比较慢。对于解决这样一个简单的问题,后一种的缺点不是很要紧,但是前一种的缺点会让你比较难受。
在生活中,我们常常用int 或者long运算这主要取决于数字的大小和你想要保留的精度,但一般来说我们是使用分为单位。以下是使用int后的修改版
@Test
public void fun6() {
int item = 0;
int allMoney = 100;
for(int price = 10;allMoney >= price;price += 10) {
item++;
allMoney -= price;
}
System.out.println("购买了多少个item: " + item );
System.out.println("还剩多少钱:" + allMoney );
}
控制台的输出如下
哦哟,这样也可以哦!其实我们常常接触的微信、支付宝都是以分为单位的。所以一般涉及到精确运算就可以采用Int 和 long。