未完 Java各种比较 : == | equals | compareTo | compare | instanceof

[color=red][b]Equality Operator == :[/b][/color]
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.21[/url]
一 基本数字类型之间、基本数字类型和其包装类对象之间使用 “==”,比较的是它们的数字值。[quote]称为 Numerical Equality Operator。具体点说:
如果参与==的两个操作数都是基本数字类型,或者一个是基本数字类型一个是基本数字类型的包装类时,做的是两个操作数的数字值的比较:有包装类,则Unboxing;Unboxing完后若存在基本类型不匹配,则Binary Numeric Promotion,直到两个操作数类型一致后,再做数字值的比较。

int i = 2;
double d = 2;
System.out.println(i == d); //true
System.out.println(i == new Float(2)); //true
System.out.println(d == new Byte((byte)2)); //true
[/quote]二 boolean与boolean、boolean与Boolean之间使用 “==”,比较的是它们的布尔值。
三 两个引用类型之间使用 “==”,比较的是他们是否指向同一个对象。
或者表述为:两个对象之间使用 “==”,比较的是这两个对象的内存地址值。
注意:
1 当通过Autoboxing,而不是new的方式创建包装类 Byte / Short / Integer / Long / Character / Boolean 的实例时,由于Java对整数包装类常用区间上包装类实例的缓存和对Boolean取值的常量化,将会导致出现一些意想不到的结果:
[url]http://wuaner.iteye.com/blog/1668172[/url]


[color=red][b]equals(Object obj):[/b][/color]
== 做对象比较,得出“相等”的结论所表达的是“左右两边就是完完全全的同一个对象”。但在很多场景下,这种严格意义上的“对象相等”并不是我们想要的,我们更加期望通过对象的状态来区别它们是否相等,如用户注册与登录时“邮箱地址相等且登录名相同的用户,就是同一个用户”。这样的场景在实际中比比皆是,于是Java为Object类提供了equals()方法,让你可以通过重写它,来定制自己认可的“相等”。
equals()方法在Object类中的默认实现,是完全等价于 == 的。
Java也给出了重写equals方法的五条约定;这些约定确实是很有必要的,为了满足这些约定,你应该通过测试来检验自己的equals方法,以使它足够健壮。其中的自反性和非空性(For any non-null reference value x, x.equals(null) should return false)还好说,另外三个[b]对称性、传递性和一致性[/b]需要在重写时着重检验。
[b]equals() 和 hashcode()的关系 - 为什么重写equals()必须重写hashcode()? (Bloch,Effective Java 2nd)[/b][quote]JDK src中对对象hashcode()方法产生的hash code,有以下约定:
[b]1[/b] 只要对象的equals()方法做比较所用的状态(字段)没有被修改,那么对该对象调用多次hashcode()方法都应该始终返回同一个hash code;
[b]2[/b] equals()方法比较结果为相等的两个对象,他们的hashcode()方法返回的hash code必须相同;
[b]3[/b] equals()方法比较结果为不相等的两个对象,不要求你必须为他们产生不相同的hash code;但程序员应该明白为不相等的对象产生不相同的hash code可以提高基于散列表的容器类(HashMap/Hashtable/HashSet)的性能。

[b]首先hashcode()方法返回的int类型hash code是什么,干什么用的?[/b]
看名便知,对象的hash code是与散列表(Hash Table, [url]http://wuaner.iteye.com/blog/553007[/url])有关的。散列表插入删除快,以及常数级的快速查找等优点,使java多个容器类都是用了散列表作为其内部的数据结构实现方式,如HashMap/Hashtable/HashSet etc。Java里对象的hash code,正是散列表相关概念里的“关键字”(key)。记录在散列表中的存储地址address,正是通过散列函数H(key)产生的。

[b]约定 1 告诉我们:[/b]
equals()方法和hashcode()方法应该是基于对象相同的状态字段;对不可变类(immutable class,实例不能被修改的类),如Java中的Integer、String、BigDecimal等,他们的实例对象的状态永远不会变,重写equals和hashcode相对容易;对可变类(mutable class,状态字段会在使用中被修改),我们应该记住:不要让equals和hashcode方法依赖于那些变化的状态(字段)。

[b]为什么重写equals()必须重写hashcode()?[/b]
原因源自JDK的要求: equals()相等的对象其hash code必须相同。
未重写hashcode()的话,Object类的hashcode()方法返回的hash code,是对象的内存地址通过一定方式转换成的integer,你可以通过System.identityHashCode(obj)取得这个默认的hash code;这种机制保证了不互相 == 的对象,其hash code一定不相同([i]As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects。但这句话也不完全正确,默认hash code也是存在相同的可能的,参见:[url]http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6321873[/url] [/i])。那么,状态上符合equals()相等的两个不同对象,其hash code也将是不同的。所以,我们应该在重写equals时,也必须重写hashcode()。

[b]为什么equals()相等的对象其hash code必须相同?[/b]
1 举一个反例:equals相等的对象,其hash code不等,会出现什么后果那?我们知道,对象在散列桶(hash buckets)中的位置(address或说index)是通过hashcode经散列函数计算出来的;equals相等,hash code不相同的对象,则他们会被散列到不同的bucket中。
2 Set接口元素不能重复、Map接口作为key的对象不能重复,这个"重复"的判断依据是通过调用equals()方法来判断的。但同时,这些以散列表为底层实现的容器类,为了性能的需要,缓存了对象的hash code值(确切说是hash code经过second hash后的值),并将这个缓存值和equals一起,也作为对象重复的判断依据。那么,如果两个对象equals相等、hash code却不同,则会出现两个对象被重复装入容器的问题;且你想通过一个对象获取容器中一个equals相等(但hash code不同)的对象时,是无法获取到的。

[b]为什么equals()不相等的对象其hash code应该尽可能地不相同?[/b]
对象的hash code对应散列表概念里的“关键字”。既然是“关键字”,关键字的选取策略,遵循的就是唯一性原则:不同记录,其关键字不应该相同。不同的关键字尚且有冲突的可能(参见散列表冲突的定义),关键字本身都存在相同的话冲突更是严重,最终导致散列表性能的大幅下降。
举一个极端的例子:假设某类的所有对象,其hash code都是一个固定的、完全相同的整数,则经过散列函数H(hash code)得到的存储地址address则也将自始至终都是同一个,导致散列表退化为一个彻头彻尾的链表,丧失其快速查找定位的优点。

通过以上分析看的出来,如果你重写了类的equals方法,并试图将该类的对象放入基于散列表的容器类(作为Set元素,或作为Map的key)的话,则你也必须重写它的hashcode方法!!其他时候,hashcode方法重不重写其实关系不大。但作为一个好的习惯,你应该在重写equals方法的同时去重写hashcode方法,保证equals相等的对象其hash code也相等 (万一这个类的对象被别人放入基于散列表的容器类,也就不会出问题了)。


[b]重写了equals&hashcode方法的JKD类有:[/b]
String:串一样的String对象都equals相等、有相同的hash code;
所有的基本类型包装类:数值基本类型包装类,数字值一样的本类对象都equals相等、hash code相同;Boolean布尔值一样就equals相等、hash code相同;
Math包下的BigDecimal等。

String s1= new String("abc");
String s2 = new String("abc");
String s3 = "abc";
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s2.equals(s3));
System.out.println(s1.hashCode() + " " + s2.hashCode() + " " + s3.hashCode());
输出结果:
true
true
true
96354 96354 96354

Srcs:
Integer Hash Function:
[url]http://www.concentric.net/~ttwang/tech/inthash.htm[/url]
[/quote]参考 : Java Collections | 容器:
[url]http://wuaner.iteye.com/blog/1672580[/url]


[color=red][b]Comparable<T> 's compareTo(T o):[/b][/color]
[url]http://download.java.net/jdk7/archive/b123/docs/api/java/lang/Comparable.html[/url]
你可能已经注意到,Java里基本类型的包装类、String、BigDecimal等类都实现了Comparable接口。这个接口是干什么的那?
正如其名,实现Comparable接口,是为了告诉外界该类的对象之间是“可以比较的”。“可以比较的”这里比较的意义在于,当你在Array及有序的容器类如ArrayList、LinkedList、Vector、TreeMap、TreeSet中放入的是实现了Comparable接口的类的对象时,你可以基于该类中对compareTo(T o)方法的实现,对容器类中的元素对象做排序等操作。Comparable接口为它的实现类所提供的排序方式,我们习惯上称其为自然排序(natural ordering),类所重写的compareTo(T o)方法我们称为它的自然比较方法(natural comparison method)。JDK里几个重要的类的自然排序如下:
[img]http://dl.iteye.com/upload/attachment/0073/8836/3d46f9c3-7172-3dfe-8958-c8e0f91ec9e9.png[/img]
关于Comparable接口的int compareTo(T o)方法:[quote]以x.compareTo(y)为例,其int类型返回值与比较结果的关系是:
若返回值小于0,表示 x < y;
若返回值等于0,表示 x = y;
若返回值大于0,表示 x > y。
Comparable接口的实现类必须保证其重写的compareTo方法满足以下四个重要的限制条件:
1. sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
2. (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.
3. x.compareTo(y)==0 implies sgn(x.compareTo(z)) == sgn(y.compareTo(z))
4. (x.compareTo(y)==0) == (x.equals(y))[i](IOW, [b]Natural ordering should be consistent with equals.[/b] - Not required,But strongly recommended)[/i]
[/quote]
Comparable<T>接口是参数化的(泛型),这在一定程度上意味着,实现该接口的类,可以去做跨类的比较(即Class A implements Cmparable<B>);但跨类比较这种特性仔细想想既没必要,又使compareTo方法的四个限制条件难以被保证。在Java默认提供的类的自然排序中没有跨类的比较。

JDK中依赖于元素对象所在类的自然排序的类有:
工具类Arrays和Collections;
有序容器类TreeSet和TreeMap;
用来做自然比较的两个元素对象,[b]必须是“可以互相比较的”(mutually comparable)[/b]。“可以互相比较”从两个方面理解:
1 容器内元素对象都来自一个类的实例的情况下:如果作为元素的对象其类没有实现Comparable接口,则元素间肯定是无法相互比较的,所以会出现:对于Arrays和Collections,会在调用它们的sort()方法时报ClassCastException; 对于TreeSet和TreeMap,如果你使用的是默认的无参构造方法构建这两个容器类的实例,它们元素对象的“有序”正是通过元素或key的自然排序来做的,在试图添加第二个元素时,这种自然排序的比较就会被用到,从而报ClassCastException。所以,元素所在的类必须实现Comparable接口,否则报ClassCastException;
2 如果放入一个有序容器类里的元素对象来自不同的类,此时哪怕这几个类都实现了Comparable接口定义了(针对本类对象的)自己的自然排序,跨类的比较仍然是无法做的,还是会报ClassCastException。
总之,一句话:[b]使用自然排序时只能向集合中加入同类型的对象,并且这些对象的类必须实现Comparable接口[/b]。

Srcs:
[url]http://www.coderanch.com/t/557926/java-programmer-SCJP/certification/Collections-sort-throws-ClassCastException[/url]


[b][color=red]Comparator<T> 's compare(T o1, T o2):[/color][/b]
[url]http://docs.oracle.com/javase/6/docs/api/java/util/Comparator.html[/url]
Comparator接口是个比较器,它同样适用于工具类Arrays和Collections、有序容器类TreeSet和TreeMap。当你需要一个不是基于类的自然排序来做的排序时(比如,你想单独基于Person的name,id,age分别做排序),可以用它来做。
和Comparable实现的自然排序相比,基于比较器的排序更加的灵活。
int compare(T o1, T o2)方法:[quote]和Comparable接口的compareTo方法很像:
若o1 < o2, 则返回值 < 0;
若o1 = o2, 则返回值 = 0;
若o1 > o2, 则返回值 > 0;[/quote][b]注意:[/b][quote]不论是 Comparable<T> 's compareTo(T o) 还是 Comparator<T> 's compare(T o1, T o2),当使用 java 排序 API(如 Collections.sort) 做排序时,都是按照 asc 正序,即 from smaller (o1) to greater (o2)。参见:
[url]http://stackoverflow.com/a/17641949/1635855[/url][/quote]


[b][color=red]Keyword - instanceof:[/color][/b]
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.20.2[/url]
用来比较一个对象是否是指定类型(类或接口)的实例。格式:
ref instanceof ReferenceType
ref 必须是引用类型变量,ReferenceType必须是引用类型;当且仅当ref不为null并且可以强制转换为ReferenceType类型时,返回true。
需要注意的是,不要在代码中使用instanceof做一些诸如条件判断之类的事情,因为那样做违反里氏替换原则,使代码变的不易维护,难以扩展。事实上,instanceof唯一正确的使用场景就是在重写equals方法时,其他时候都应该尽量避免使用它。


Sources:
[b]Java Tutorials - Collections - Object Ordering:
[url]http://docs.oracle.com/javase/tutorial/collections/interfaces/order.html[/url]
==, .equals(), compareTo(), and compare()
[url]http://leepoint.net/notes-java/data/expressions/22compareobjects.html[/url]
[/b]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值