阅读目录

  • 建议47:在equals中使用getClass进行类型判断
  • 建议48:覆写equals方法必须覆写hashCode方法
  • 建议49:推荐覆写toString方法
  • 建议50:使用package-info类为包服务
  • 建议51:不要主动进行垃圾回收
回到顶部

建议47:在equals中使用getClass进行类型判断

  本节我们继续讨论覆写equals的问题,这次我们编写一个员工Employee类继承Person类,这很正常,员工也是人嘛,而且在JavaBean中继承也很多见,代码如下:

 1 public class Employee extends Person {2     private int id;3 4     public Employee(String _name, int _id) {5         super(_name);6         id = _id;7     }8 9     public int getId() {
10         return id;
11     }
12
13     public void setId(int id) {
14         this.id = id;
15     }
16
17     @Override
18     public boolean equals(Object obj) {
19         if (obj instanceof Employee) {
20             Employee e = (Employee) obj;
21             return super.equals(obj) && e.getId() == id;
22         }
23         return false;
24     }
25
26 }
27
28 class Person {
29     private String name;
30
31     public Person(String _name) {
32         name = _name;
33     }
34
35     public String getName() {
36         return name;
37     }
38
39     public void setName(String name) {
40         this.name = name;
41     }
42
43     @Override
44     public boolean equals(Object obj) {
45         if (obj instanceof Person) {
46             Person p = (Person) obj;
47             if (null == p.getName() || null == name) {
48                 return false;
49             } else {
50                 return name.equalsIgnoreCase(p.getName());
51             }
52         }
53         return false;
54     }
55 }

  员工类增加了工号ID属性,同时也覆写了equals方法,只有在姓名和ID都相同的情况下才表示同一个员工,这是为了避免一个公司中出现同名同姓员工的情况。看看上面的代码,这里的条件已经相当完善了,应该不会出错了,那我们测试一下,代码如下:  

1 public static void main(String[] args) {
2         Employee e1 = new Employee("张三", 100);
3         Employee e2 = new Employee("张三", 1000);
4         Person p1 = new Person("张三");
5         System.out.println(p1.equals(e1));
6         System.out.println(p1.equals(e2));
7         System.out.println(e1.equals(e2));
8     }

  上面定义了两个员工和一个社会闲杂人员,虽然他们同名同姓,但肯定不是同一个,输出都应该是false,但运行之后结果为: true  true  false

  很不给力呀,p1竟然等于e1,也等于e2,为什么不是同一个类的两个实例竟然也会相等呢?这很简单,因为p1.equals(e1)是调用父类Person的equals方法进行判断的,它使用的是instanceof关键字检查e1是否是Person的实例,由于两者村子继承关系,那结果当然是true了,相等也就没有任何问题了,但是反过来就不成立了,e1和e2是不可能等于p1,这也是违反对称性原则的一个典型案例。

  更玄的是p1与e1、e2相等,但e1和e2却不相等,似乎一个简单的符号传递都不能实现,这才是我们分析的重点:e1.equals(e2)调用的是子类Employee的equals方法,不仅仅要判断姓名相同,还要判断Id相同,两者工号是不同的,不相等也是自然的了。等式不传递是因为违反了equals的传递性原则,传递性原则指的是对于实例对象x、y、z来说,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。

  这种情况发生的关键是父类引用了instanceof关键字,它是用来判断一个类的实例对象的,这很容易让子类钻空子。想要解决也很简单,使用getClass来代替instanceof进行类型判断,Person的equals方法修改后如下所示: 

@Overridepublic boolean equals(Object obj) {if (null != obj && obj.getClass() == this.getClass()) {Person p = (Person) obj;if (null == p.getName() || null == name) {return false;} else {return name.equalsIgnoreCase(p.getName());}}return false;}

  当然,考虑到Employee也有可能被继承,也需要把它的instanceof修改为getClass。总之,在覆写equals时建议使用getClass进行类型判断,而不要使用instanceof。

回到顶部

建议48:覆写equals方法必须覆写hashCode方法

覆写equals方法必须覆写hasCode方法,这条规则基本上每个Javaer都知道,这也是JDK的API上反复说明的,不过为什么要则这么做呢?这两个方法之间什么关系呢?本建议就来解释该问题,我们先看看代码:

public class Client48 {public static void main(String[] args) {// Person类的实例作为map的keyMap<Person, Object> map = new HashMap<Person, Object>() {{put(new Person("张三"), new Object());}};// Person类的实例作为List的元素List<Person> list = new ArrayList<Person>() {{add(new Person("张三"));}};boolean b1 = list.contains(new Person("张三"));boolean b2 = map.containsKey(new Person("张三"));System.out.println(b1);System.out.println(b2);}
}

  代码中的Person类与上一建议的Person相同,equals方法完美无缺。在这段代码中,我们在声明时直接调用方法赋值,这其实也是一个内部匿名类,现在的问题是b1和b2值是否都为true?

  我们先来看b1,Person类的equals覆写了,不再判断两个地址相等,而是根据人员的姓名来判断两个对象是否相等,所以不管我们的new Person("张三")产生了多少个对象,它们都是相等的。把张三放入List中,再检查List中是否包含,那结果肯定是true了。

  接下来看b2,我们把张三这个对象作为了Map的键(Key),放进去的是张三,检查的对象还是张三,那应该和List的结果相同了,但是很遗憾,结果为false。原因何在呢?

  原因就是HashMap的底层处理机制是以数组的方式保存Map条目的(Map Entry)的,这其中的关键是这个数组的下标处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有Map条目,并且与传入的值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。同理,检查键是否存在也是根据哈希码确定位置,然后遍历查找键值的。

  接着深入探讨,那对象元素的hashCode方法返回的是什么值呢?它是一个对象的哈希码,是由Object类的本地方法生成的,确保每个对象有一个哈希码(也是哈希算法的基本要求:任意输入k,通过一定算法f(k),将其转换为非可逆的输出,对于两个输入k1和k2,要求若k1=k2,则必须f(k1)=f(k2),但也允许k1  != k2 , f(k1)=f(k2)的情况存在)。

  那回到我们的例子上,由于我们没有覆写hashCode方法,两个张三对象的hashCode方法返回值(也就是哈希码)肯定是不相同的了,在HashMap的数组中也找不到对应的Map条目了,于是就返回了false。

  问题清楚了,修改也很简单,在Person类中重写一下hashCode方法即可,代码如下: 

class Person{@Overridepublic int hashCode() {return new HashCodeBuilder().append(name).toHashCode();}   }

  其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具,使用起来非常方便,大家可以直接项目中集成(为何不直接写hashCode方法?因为哈希码的生成有很多种算法,自己写麻烦,事儿又多,所以必要的时候才取"拿来主义",不重复造轮子是最好的办法。)

回到顶部

建议49:推荐覆写toString方法

  为什么要覆写toString方法,这个问题很简单,因为Java提供的默认toString方法不友好,打印出来看不懂,不覆写不行,看这样一段代码: 

public class Client49 {public static void main(String[] args) {System.out.println(new Person("张三"));}
}class Person {private String name;public Person(String _name) {name = _name;}public String getName() {return name;}public void setName(String name) {this.name = name;}}

  输出结果是:Perso@188edd79.如果机器不同,@后面的内容也会不同,但格式都是相同的:类名+@+hashCode,这玩意是给机器看的,人哪能看懂呀!这就是因为我们没有覆写Object类的toString方法的缘故,修改一下,代码如下:

@Overridepublic String toString() {return String.format("%s.name=%s", this.getClass(),name);}

  如此即就可以在需要的时候输出调试信息了,而且非常友好,特别是在bean流行的项目中(一般的Web项目就是这样),有了这样的输出才能更好地debug,否则查找错误就有点麻烦!当然,当bean的属性较多时,自己实现就不可取了,不过可以直接使用apache的commons工具包中的ToStringBuilder类,简洁,实用又方便。可能有人会说,为什么通过println方法打印一个对象会调用toString方法?那是源于println的打印机制:如果是一个原始类型就直接打印,如果是一个类类型,则打印出其toString方法的返回值,如此而已。同时现在IDE也很先进,大家debug时也可查看对象的变量,但还是建议大家覆写toString方法,这样调试会更方便哦。

回到顶部

建议50:使用package-info类为包服务

  Java中有一个特殊的类:package-info类,它是专门为本包服务的,为什么说它特殊,主要体现在三个方面:

  1. 它不能随便创建:在一般的IDE中,Eclipse、package-info等文件是不能随便被创建的,会报"Type name is notvalid"错误,类名无效。在Java中变量定义规范中规定如下字符是允许的:字母、数字、下划线,以及那个不怎么写的$符号,不过中划线可不在之列,那么怎么创建这个文件呢?很简单,用记事本创建一个,然后拷贝进去再改一下就成了,更直接的办法就是从别的项目中拷贝过来。
  2. 它服务的对象很特殊:一个类是一类或一组事物的描述,比如Dog这个类,就是描述"阿黄"的,那package-info这个类描述的是什么呢?它总是要有一个被描述或陈述的对象吧,它是描述和记录本包信息的。
  3. package-info类不能有实现代码:package-info类再怎么特殊也是 一个类,也会被编译成 package-info.class,但是在package-info.java文件不能声明package-info类。   

  package-info类还有几个特殊的地方,比如不可以继承,没有接口,没有类间关系(关联、组合、聚合等)等,Java中既然有这么特殊的一个类,那肯定有其特殊的作用了,我们来看看它的特殊作用,主要表现在以下三个方面:

  • 声明友好类和包内访问常量:这个比较简单,而且很实用,比如一个包中有很多内部访问的类或常量,就可以统一放到package-info类中,这样很方便,便于集中管理,可以减少友好类到处游走的情况,代码如下:
class PkgClazz {public void test() {}}class PkgConstant {static final String PACKAGE_CONST = "ABC";}

  注意以上代码是放在package-info.java中的,虽然它没有编写package-info的实现,但是package-info.class类文件还是会生成。通过这样的定义,我们把一个包需要的常量和类都放置在本包下,在语义上和习惯上都能让程序员更适应。

  • 为在包上提供注解提供便利:比如我们要写一个注解(Annotation),查看一下包下的对象,只要把注解标注到package-info文件中即可,而且在很多开源项目中也采用了此方法,比如struts2的@namespace、hibernate的@FilterDef等.
  • 提供包的整体注释说明:如果是分包开发,也就是说一个包实现了一个业务逻辑或功能点或模块或组件,则该包需要一个很好的说明文档,说明这个包是做什么用的,版本变迁历史,与其他包的逻辑关系等,package-info文件的作用在此就发挥出来了,这些都可以直接定义到此文件中,通过javadoc生成文档时,会吧这些说明作为包文档的首页,让读者更容易对该包有一个整体的认识。当然在这点上它与package.html的作用是相同的,不过package-info可以在代码中维护文档的完整性,并且可以实现代码与文档的同步更新。  

  创建package-info,也可以利用IDE工具如下图:

  

解释了这么多,总结成一句话:在需要用到包的地方,就可以考虑一下package-info这个特殊类,也许能起到事半功倍的作用。

回到顶部

建议51:不要主动进行垃圾回收

  很久很久以前,在java1.1的年代里,我们经常会看到System.gc这样的调用---主动对垃圾进行回收,不过,在Java知识深入人心后,这样的代码就逐渐销声匿迹了---这是好现象,因为主动进行垃圾回收是一个非常危险的动作。

  之所以危险,是因为System.gc要停止所有的响应,才能检查内存中是否存在可以回收的对象,这对一个应用系统来说风险极大,如果是一个Web应用,所有的请求都会暂停,等待垃圾回收器执行完毕,若此时堆内存(heap)中的对象少的话还可以接受,一但对象较多(现在的web项目是越做越大,框架、工具也越来越多,加载到内存中的对象当然也就更多了),这个过程非常耗时,可能是0.01秒,也可能是1秒,甚至20秒,这就严重影响到业务的运行了。

  例如:我们写这样一段代码:new String("abc"),该对象没有任何引用,对JVM来说就是个垃圾对象。JVM的垃圾回收器线程第一次扫描(扫描时间不确定,在系统不繁忙的时候执行)时给它贴上一个标签,说"你是可以回收的",第二次扫描时才真正的回收该对象,并释放内存空间,如果我们直接调用System.gc,则是说“嗨,你,那个垃圾回收器过来检查一下有没有垃圾对象,回收一下”。瞧瞧看,程序主动找来垃圾回收器,这意味着正在运行的系统要让出资源,以供垃圾回收器执行,想想看吧,它会把所有的对象都检查一遍,然后处理掉那些垃圾对象。注意哦,是检查每个对象。

  不要调用System.gc,即使经常出现内存溢出也不要调用,内存溢出是可分析的,是可以查找原因的,GC可不是一个好招数。

作者:阿赫瓦里
出处:http://www.cnblogs.com/selene/
本文以学习、研究和分享为主,版权归作者和博客园共有,欢迎转载,如果文中有不妥或者错误的地方还望大神您不吝指出。如果觉得本文对您有所帮助不如【推荐】一下吧!如果你有更好的建议,不如留言一起讨论,共同进步!此外,大家也可以支持一下自家苹果, 再次感谢您耐心的读完本篇文章。

转载于:https://www.cnblogs.com/LH923613603/p/7067643.html

转载---编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议47~51)...相关推荐

  1. 转载----编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议1~5)...

    阅读目录 建议1:不要在常量和变量中出现易混淆的字母 建议2:莫让常量蜕变成变量 建议3:三元操作符的类型务必一致 建议4:避免带有变长参数的方法重载 建议5:别让null值和空值威胁到变长方法    ...

  2. 转载--编写高质量代码:改善Java程序的151个建议(第5章:数组和集合___建议60~64)

    阅读目录 建议60:性能考虑,数组是首选 建议61:若有必要,使用变长数组 建议62:警惕数组的浅拷贝 建议63:在明确的场景下,为集合指定初始容量 建议64:多种最值算法,适时选择 噢,它明白了,河 ...

  3. 转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议16~20)...

    阅读目录 建议16:易变业务使用脚本语言编写 建议17:慎用动态编译 建议18:避免instanceof非预期结果 建议19:断言绝对不是鸡肋 建议20:不要只替换一个类 回到顶部 建议16:易变业务 ...

  4. 转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议11~15)...

    阅读目录 建议11:养成良好习惯,显示声明UID 建议12:避免用序列化类在构造函数中为不变量赋值 建议13:避免为final变量复杂赋值 建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题 ...

  5. 转载--编写高质量代码:改善Java程序的151个建议(第4章:字符串___建议52~55)

    阅读目录 建议52:推荐使用String直接量赋值 建议53:注意方法中传递的参数要求 建议54:正确使用String.StringBuffer.StringBuilder 建议55:注意字符串的位置 ...

  6. 转载--编写高质量代码:改善Java程序的151个建议(第4章:字符串___建议56~59)

    阅读目录 建议56:自由选择字符串拼接方法 建议57:推荐在复杂字符串操作中使用正则表达式 建议58:强烈建议使用UTF编码 建议59:对字符串持有一种宽容的心态 回到顶部 建议56:自由选择字符串拼 ...

  7. 转载---编写高质量代码:改善Java程序的151个建议(第2章:基本类型___建议26~30)

    阅读目录 建议26:提防包装类型的null值 建议27:谨慎包装类型的大小比较 建议28:优先使用整型池 建议29:优先选择基本类型 建议30:不要随便设置随机种子 回到顶部 建议26:提防包装类型的 ...

  8. 转载--编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议31~35)...

    阅读目录 建议31:在接口中不要存在实现代码 建议32:静态变量一定要先声明后赋值 建议33:不要覆写静态方法 建议34:构造函数尽量简化 建议35:避免在构造函数中初始化其它类 书读的多而不思考,你 ...

  9. 转载-----编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议6~10)...

    阅读目录 建议6:覆写变长方法也循规蹈矩 建议7:警惕自增的陷阱 建议8:不要让旧语法困扰你 建议9:少用静态导入 建议10:不要在本类中覆盖静态导入的变量和方法 回到顶部 建议6:覆写变长方法也循规 ...

最新文章

  1. 编写精美的聊天界面(左边显示接收消息,右边显示发送消息)(项目已上传GitHub)
  2. Git的remote
  3. 专科java程序设计试卷_java程序设计试卷(含答案)
  4. JVM调优——之CMS GC日志分析
  5. android studio修改配置文件夹(.android .gradle .AndroidStudio)位置
  6. 四线接近开关接线图_开关、电机、断路器、电热偶、电表接线图!非常齐全,快囤起来吧...
  7. !KMP算法完整教程
  8. javascript-流程控制-循环-分支-三元运算符
  9. td里面字体大小怎么改_教你王者荣耀改战区
  10. 6004.ubuntu18.04移植qgroundcontrol地面站
  11. webgis期末考试试题_WebGIS考试参考试题
  12. 【学习笔记】Guice学习小结
  13. java安卓屏幕护眼效果好_当前安卓机自带的护眼模式,比护眼大师的好嘛?
  14. nodejs取汉字的首字母大写
  15. 静态手绘图-屁民科普
  16. 计算机打游戏的硬件,玩电脑常见的六大误区 看看你犯了几条
  17. SQL注入学习详细过程
  18. bazel 构建问题总结
  19. C/C++文件读写操作 —— windowsAPI
  20. Johnson法则简要证明

热门文章

  1. 电商ERP和传统ERP到底有什么不同
  2. m索引OFDM调制解调系统的性能仿真分析
  3. Dart(3)-常量
  4. c#DataTable 查重
  5. C# DataTable相关操作
  6. Java利用while求一等比数列_UPC-6760 Problem H  九连环  【重庆OI2018】等比数列求和JAVA高精度...
  7. RPC架构组件详解刨析
  8. linux操作系统面试题大全
  9. 会议室预约系统遇到的问题
  10. 与 XML 技术有关的软件项目