Java高质量代码之 — 面向对象

来源:互联网 发布:峨眉山旅游 知乎 编辑:程序博客网 时间:2024/04/28 08:17
前言:由于上一个星期工作繁忙,利用上下班和晚上睡前空余的时间拜读了秦小波老师的《改善Java程序的151建议》,感觉廓然开朗,注意到了很多平时在编写代码中并不会注意的问题,甚至感觉自己对Java只是略懂皮毛,不足以登大雅之堂,特此与读者分享读书笔记,以下内容摘自《改善Java程序的151建议》一书和笔者的理解 


Java高质量代码系列文章 
      面向对象篇:http://ray-yui.iteye.com/blog/1926984 
      数据类型篇:http://ray-yui.iteye.com/blog/1927251 
          字符串篇:http://ray-yui.iteye.com/blog/1927647 
      数组与集合(1):http://ray-yui.iteye.com/blog/1928170 


1.在接口中不要存在实现代码 
      接口中存在实现?接口中应该只能声明抽象方法,常量,继承其他接口,但抽象方法当中不能存在实现吧?请观看一下的一段代码 

Java代码  收藏代码
  1. public interface TestInterface {  
  2.   
  3.     public static final Test test = new Test() {  
  4.         public void doSomething() {  
  5.             System.out.println("这就是实现");  
  6.         }  
  7.     };  
  8.   
  9.     void test();  
  10. }  


      从以上代码中可以看到,在接口当中使用了匿名内部类的方式对接口当中的常量进行了实现,这种编码方式可以说是很好很强大,但由此带来的是什么?接口从此不再稳定,接口和类有了依赖,当Test类方法doSomething改变时,直接影响了接口,而接口应该是一种规范,是稳定不变的 


2.静态变量一定要先声明后使用 
      标题是什么意思?难道可以使用一个没有声明的静态变量吗?请看以下代码 

Java代码  收藏代码
  1. public class Test {  
  2.     static {  
  3.         i = 100;  
  4.     }  
  5.     public static int i = 1;  
  6.   
  7.     public static void main(String[] args) {  
  8.         System.out.println("此时 i 的输出为 1" + i);  
  9.     }  
  10. }  


      从以上代码看到,i先被使用,然后再进行了初始化,那为什么会出现这样的情况?首先要明确,静态变量在类初始化首先被加载的,注意,此时只加载了 i 这个变量,但还没有对 i 进行赋值,然后JVM会根据类中的静态块来顺序执行,以上代码由于静态块static{}放置顺序在前面,所以首先被执行,然后再执行int i = 1的操作 


3.不要复写静态方法和静态方法使用类名调用: 
      在Java中,可以通过子类对父类的方法进行复写从而增强或减弱父类的行为,但复写是针对非静态方法的,我们都知道静态方法是针对类本身的,通过类名进行调用,当然也可以使用对象来调用,但这样Eclipse本身就会给你报个小警告,使用对象调用是不规范的,所以子类和父类应该尽量避免静态方法重名和静态方法使用类名调用 


4.构造函数尽量简化 
      我们都知道,在New出一个新对象时,对象会执行一个方法,此方法名为构造函数,而构造函数当中,应该尽量不出现业务代码,因为构造函数的职责就是为类的创建进行初始化的,例如对对象的属性值进行初始化,而业务代码应该封装在类的方法当中,让构造函数尽量简单 


5.用构造代码块精炼代码 
      现在我们有这样一个需求,在Test类初始化时需要记录日志,但Test类当中有多个构造函数,当然我们可以在每个构造函数当中编写日志,但使用构造代码块将更加的简洁完成以上需求,请看以下代码 

Java代码  收藏代码
  1. public class Test {  
  2.     // 直接编写1对{}代表构造代码块  
  3.     {  
  4.         System.out.println("我是记录日志的构造代码快");  
  5.     }  
  6.   
  7.     public Test() {  
  8.         System.out.println("执行无参构造");  
  9.     }  
  10.   
  11.     public Test(String id) {  
  12.         System.out.println("执行有参构造");  
  13.     }  
  14. }  


      以上代码中,构造代码块会增加到每个构造函数中的第一行的位置,这样就代表执行构造代码块再执行构造函数本身的内容 


6.使用匿名类的构造函数 
      在我们开发当中,若想快速实现一个接口,例如是Comparator(比较器),我们会使用匿名类的形式来进行实现,但有没有想过,匿名类的构造函数在何方?我们都知道构造函数是没有重写的,那我们在对自定义的接口使用匿名方式实现时,如何使用构造函数?请看以下代码 

Java代码  收藏代码
  1. public class Test {  
  2.     private Comparator<Test> comparator = new Comparator<Test>() {  
  3.         {  
  4.             System.out.println("其实我可以代替匿名类的构造函数");  
  5.         }  
  6.         @Override  
  7.         public int compare(Test o1, Test o2) {  
  8.             return 0;  
  9.         }  
  10.     };  
  11. }  


      使用构造代码块就可以实现对匿名类的构造函数. 


7.让多重继承成为现实 
      我们都知道Java当中是单继承的,但我们可以使用婉转的方式来实现多继承,这就需要使用到内部类 

Java代码  收藏代码
  1. public abstract class Runable {  
  2.   
  3.     private int speed = 10;  
  4.   
  5.     void run() {  
  6.         System.out.println("running..." + speed);  
  7.     }  
  8. }  
  9.   
  10.   
  11. public abstract class Flyable {  
  12.   
  13.     private int speed = 10;  
  14.   
  15.     void fly() {  
  16.         System.out.println("flying..." + 10);  
  17.     }  
  18. }  
  19.   
  20.   
  21. //动物能飞,能跑  
  22. public class Animal {  
  23.   
  24.     public void fly() {  
  25.         new Flyable() {  
  26.         }.fly();  
  27.     }  
  28.   
  29.     public void run() {  
  30.         new Runable() {  
  31.         }.run();  
  32.     }  
  33. }  


      当然我们可以在Animal中再创建一个内部类继承runable或flyable然后在run或fly中创建内部类来实现,这里只给出最方便快捷的实现 


8.让工具类不可实例化 
      工具类只是帮助我们完成某些特定操作,而且工具类所有方法都应该是static,而且不能实例化,当然很简单就可以完成,只需将构造函数私有化(private)即可,但Java强大的反射机制还是能让我们通过反射形式来实例化工具类,此时更应该在工具类私有的构造函数中加入抛出异常的代码,当初始化时即抛出异常 


9.避免对象的浅拷贝 
      何谓浅拷贝?有经典的案例,请看如下代码 

Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.         int[] numArray = new int[10];  
  3.         int[] numArray2 = null;  
  4.         // 此种就为浅拷贝,拷贝的是引用  
  5.         numArray2 = numArray;  
  6.   
  7.         // 此种为深度拷贝,拷贝的是元素,不是引用  
  8.         for (int i = 0, size = numArray.length; i < size; i++) {  
  9.             numArray2[i] = numArray[i];  
  10.         }  
  11.   
  12.     }  


      而在Java中,只要实现了Cloneable接口的对象,都被视为具有拷贝能力,但需要注意,clone方法的拷贝为浅拷贝. 


9.复写equlas时需要注意的事情: 
      在复写equlas时需要注意,要考虑Null值得情况,推荐中equlas中使用getClass进行类型的判断,复写equlas时必须复写hasCode方法 


10.不主动进行垃圾回收 
      主动进行垃圾回收是一个非常危险的操作,因为System.gc()需要停止所有的响应来扫描垃圾,所有的请求会全部停止,等待垃圾回收器执行完毕,此时若然堆中存在大量对象时,那这个过程将会非常耗时 



总结: 
      笔者在本文章中只从《改善Java程序的151建议》中提取部分进行归纳性叙述,推荐各位读者购买这本书,该书不仅从事例中学习,而且涉及到原理,底层的实现,不仅告诉你应该怎么做,还告诉你为什么要这样做.