提出问题

项目中如何编写通用程序???

解决问题

以下来自《Effective Java》中的读书笔记:

将局部变量的作用域最小化

要是局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方申明。
几乎每个局部变量的声明,都应该包含一个初始化表达式。

下面是一种对局部变量的作用域进行最小化的循环做法。

例如:

  1. //防止每次循环都去调用size方法,提高性能
  2. for(int i=0,len = userList.size();i < len;i++){
  3. .....
  4. }

最后一种将局部变量的作用域最小化的方法是使方法小而集中。(这个在之前的文章中已经多次提到了)

for-each循环优于传统的for循环
例如:

  1. //不推荐
  2. for(Iterator i=c.iterator();i.hasNext()){
  3. doSomething(i.next());
  4. }
  5. //推荐
  6. for(Element e:c){
  7. doSomething(e);
  8. }

有三种情况无法使用for-each循环:

  • 过滤:如果需要遍历集合。并删除选定的元素,就需要使用显示的迭代器,以便可以调用它的remove方法。
  • 转换:如果需要遍历列表或者数组,并取代部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值。
  • 平行迭代:如果需要并行的遍历多个集合,就需要显示的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步迁移。

    了解和使用类库

    尽可能的使用第三方类库。通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。

例如:
org.apache.commons.lang3
总而言之,不要重新发明轮子,如果你要做的事情看起来是十分常见的,有可能类库中已经有某个类完成了这样的工作,如果确实是这样,就使用现成的。如果还不清楚是否存在这样的类,就去查一查。

如果需要准确的答案,请避免使用float和double
float和double类型不适合用于货币计算,解决办法是使用BigDecinal,int和long进行货币计算。

总而言之,对于任何需要精确答案的计算任务,请不要使用float或者double。

基本类型优先于装箱基本类型
基本类型:int,double,boolean等等
引用类型:String和List等等
装箱基本类型:Integer,Double和Boolean等等

例如:

  1. static Integer i;
  2. public static void main(String[] args) {
  3. //自动拆箱,报NullPointerException异常
  4. if(i == 42){
  5. System.out.println("unbelievable");
  6. }
  7. }

当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,这种情况无一例外。

上面问题就是修正i为int类型就好了。

例如:

//这个程序存在性能问题
public static void main(String[] args) {
   Long sum = 0L;
   for(long i=0;i<Integer.MAX_VALUE;i++){
       //变量被反复的装箱和拆箱,导致性能问题
       sum += i;
   }
   System.out.println(sum);
}

那么什么时候应该使用装箱基本类型呢?

  • 第一个是作为集合中的元素,键和值。
  • 第二个是在参数化类型中,必须使用装箱基本类型作为类型参数。

总之,当可以选择的时候,基本类型要优先于装箱基本类型。基本类型更加简单,也更加快速,如果必须使用,装箱基本类型要特别小心。自动装箱减少了使用装箱基本类型的繁琐性,但是并没有减少它的风险。当程序用 == 操作符比较两个装箱基本类型时。他做了个同一性比较,这几乎肯定不是你所希望的。当程序进行涉及装箱和拆箱基本类型的混合类型计算时,他会进行拆箱。当程序进行拆箱时,会抛出空指针异常。最后当程序装箱了基本类型值时,会导致高开销和不必要的对象创建。

如果其它类型更适合,则尽量避免使用字符串

字符串不适合代替其他的值类型,数值类型应当被适当地转换为数值类型,比如int,float或者double,布尔类型就应该被转化为boolean类型等等。
字符串不适合代替枚举类型。
例如:

String compounKey = className + "#" + i.next();

缺点:如果用来分隔的字符也出现在某个域中,结果就会出现混乱。

  • 字符串不适合代替聚集类型。(不可伪造的键有时候被称为能力)

例如:

//有问题,在线程中,如果客户传入key相同,安全性就很差
   public static set(String key,Object value){
   }
   public static Object get(String key){
   }
//改正:
   public static class Key{
       Key(){}
   }
   public static Key getKey(){
       return new Key();
   }
   //重点在这里哦,使用对象Key哦
   public static void set(Key key,Object value);
   public static Object get(Key key);

当心字符串连接的性能

对于大规模的场景中,为连接n个字符串而重复的使用字符串连接操作符,需要n的平方级的时间。

具体的详细内容,请看之前的这篇文章:

优雅编程之这样考虑字符性能,你就“正常”了(二十二)

通用接口引用对象
例如:

//正确
List<Subscribe> subscribes = new Vector<>();
//欠缺
Vector<Subscribe> subscribes = new Vector<>();

当你决定更换实现时,所要做的就只是改变构造器中类的名称。

List<Subscribe> subscribes = new ArrayList<>();

周围的所有代码都可以继续工作。

但是

如果没有合适的接口存在,完全可以用类,而不是接口来引用对象。例如考虑值类,比如String和BigInteger。记住,值类很少会用多个实现编写。它们通常是final
类实现了接口,但是它提供了接口中不存在的额外方法,如果程序依赖于这些额外的方法,这种类就应该只被用来引用它的实例,它很少应该被用作参数类型。

接口优先于相射机制

反射机制需要付出代价:

  • 丧失了编译时类型检查的好处:如果程序企图用反射方式调用不存在或不可访问的方法,在运行时它将会失败。
  • 执行反射访问所需要的代码非常笨拙和冗长。
  • 性能损耗。反射方法调用的比普通方法调用慢了很多。

通常普通应用程序在运行时不应该以反射方式访问对象。

谨慎地使用本地方法

Java Native Interface(JNI)允许Java程序可以调用本地方法(native method)

使用本地方法来提高性能的做法不值得提倡。
使用本地方法有一些严重的缺点。因为本地语言不是安全的

谨慎的进行优化
有三条与优化相关的格言是每个人都应该知道的。

很多计算机上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他的原因,甚至包括盲目地做傻事。
不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源。
在优化方面,我们应该遵守2条原则:1.不要进行优化。2.(仅针对专家)还是不要进行优化,也就是说,在你还没有绝对清晰的未优化方案之前,请不要进行优化。

总之,不要费力去编写快速程序,应该努力编写好的程序,速度自然会随之而来。

普遍接受的命名惯例

  • 包名称:一个或者多个包的简短名称组成,每个简短名称通常不超过8个字符(事实发现,JDK上的包也些也超过8个字符:java.awt.datatransfer )。所以作者在这里使用通常,也就是有例外

java.applet
java.awt
java.awt.color

  • 类和接口名称,包括枚举和注解名称:通常由一个或者多个单词组成,每个单词的首字母大写(TimerTask),尽量避免缩写,除非类似这种:max或者min

  • 方法和域的名称:与类和接口名称类似,只是方法首字母小写。方法的名称一般由动词或者动词,名称组成。如:remove,removeUser等等

  • 常量:如:public static final int MIN_VALUE,全部大写,中间用下划线分割

  • 局部变量名称与成员变量的名称类似,只不过它允许缩写

类型参数名称:通常由单个字母组成,T表示任意类型,E表示集合元素类型,K,V表示键和值,X表示异常