`

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

阅读更多
    
Equality Operator == :
http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.21
一 基本数字类型之间、基本数字类型和其包装类对象之间使用 “==”,比较的是它们的数字值。
引用
称为 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
二 boolean与boolean、boolean与Boolean之间使用 “==”,比较的是它们的布尔值。
三 两个引用类型之间使用 “==”,比较的是他们是否指向同一个对象。
或者表述为:两个对象之间使用 “==”,比较的是这两个对象的内存地址值。
注意:
1 当通过Autoboxing,而不是new的方式创建包装类 Byte / Short / Integer / Long / Character / Boolean 的实例时,由于Java对整数包装类常用区间上包装类实例的缓存和对Boolean取值的常量化,将会导致出现一些意想不到的结果:
       http://wuaner.iteye.com/blog/1668172





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

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

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

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

为什么equals()相等的对象其hash code必须相同?
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不同)的对象时,是无法获取到的。

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

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


重写了equals&hashcode方法的JKD类有:
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:
http://www.concentric.net/~ttwang/tech/inthash.htm
参考 : Java Collections  |  容器:
http://wuaner.iteye.com/blog/1672580



Comparable<T> 's compareTo(T o):
http://download.java.net/jdk7/archive/b123/docs/api/java/lang/Comparable.html
你可能已经注意到,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里几个重要的类的自然排序如下:

关于Comparable接口的int compareTo(T o)方法:
引用
以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))(IOW, Natural ordering should be consistent with equals. - Not required,But strongly recommended)

Comparable<T>接口是参数化的(泛型),这在一定程度上意味着,实现该接口的类,可以去做跨类的比较(即Class A implements Cmparable<B>);但跨类比较这种特性仔细想想既没必要,又使compareTo方法的四个限制条件难以被保证。在Java默认提供的类的自然排序中没有跨类的比较。

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

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




Comparator<T> 's compare(T o1, T o2):
http://docs.oracle.com/javase/6/docs/api/java/util/Comparator.html
Comparator接口是个比较器,它同样适用于工具类Arrays和Collections、有序容器类TreeSet和TreeMap。当你需要一个不是基于类的自然排序来做的排序时(比如,你想单独基于Person的name,id,age分别做排序),可以用它来做。
和Comparable实现的自然排序相比,基于比较器的排序更加的灵活。
int  compare(T o1, T o2)方法:
引用
和Comparable接口的compareTo方法很像:
若o1 < o2, 则返回值 < 0;
若o1 = o2, 则返回值 = 0;
若o1 > o2, 则返回值 > 0;
注意:
引用
不论是 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)。参见:
http://stackoverflow.com/a/17641949/1635855




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



Sources:
Java Tutorials - Collections - Object Ordering:
http://docs.oracle.com/javase/tutorial/collections/interfaces/order.html
==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html
  • 大小: 15.1 KB
分享到:
评论

相关推荐

    java中equals和==的区别

    Java 中 equals 和 == 的区别 Java 中的 equals 和 == 是两个不同的概念,很多开发者容易混淆它们。理解这两个概念的区别是非常重要的,因为它们对编程的正确性和性能都有很大的影响。 首先,我们需要了解 Java ...

    java基础之 “==”与“equals”区别详解

    java基础之“==”与equals()的应用场景非常广泛,例如字符串比较、对象比较、集合比较等等。 10. java基础之“==”与equals()的学习建议: 学习java基础之“==”与equals()需要充分理解它们的区别和应用场景,并且...

    java中equals和==的区别.doc

    Java 中 equals 和 == 的区别 Java 中的 equals 和 == 是两个不同的运算符,它们之间的区别是非常重要的。 首先,我们需要了解 Java 中的内存模型。在 Java 中,变量可以分为两种:基本类型(primitive type)和...

    Java中的==和equals区别

    在Java编程中,比较对象的相等性是一个常见的需求,但很多初学者对于`==`运算符与`equals`方法的区别容易混淆。本文将深入探讨两者之间的差异,以及它们在不同场景下的应用。 #### `==` 运算符 `==`运算符主要用于...

    ==和equals的区别

    在 Java 中,比较值大小有两种方法:== 和 equals,这两个方法的使用场景和比较规则不同,下面我们将详细探讨它们的区别。 基本数据类型和引用数据类型 在 Java 中,有两种数据类型:基本数据类型和引用数据类型。...

    3 Java中关于==和equal的区别 以及equals()方法重写

    在 Java 中,== 操作符和 equals() 方法都是用于比较两个对象是否相等的,但它们的比较规则不同。== 操作符比较的是对象的引用,而 equals() 方法比较的是对象的内容。因此,在实际开发中,需要根据具体情况选择使用...

    字符串比较之 “==”和 “equals”

    在Java编程语言中,字符串是比较常见的数据类型,用于存储文本信息。在处理字符串时,我们经常需要比较两个字符串是否相等。"=="和"equals()"是两种常用的字符串比较方法,但它们之间存在一定的区别和使用场景。这篇...

    java中的==和equals()方法1

    在Java编程语言中,了解如何正确使用`==`和`equals()`方法是非常关键的,因为它们在比较对象和基本类型时有不同的行为。下面将详细解释这两个方法的工作原理、使用场景以及一些常见误区。 首先,`==`运算符主要用于...

    java中&quot;==&quot; 与equals方法的使用

    在Java编程语言中,"=="运算符和equals()方法都用于比较,但它们在比较对象时有着不同的含义和用法。本文将深入探讨这两个概念的区别以及何时应该使用它们。 首先,"=="运算符主要用于比较基本数据类型的值,如int...

    ==和equals方法究竟有什么区别

    在Java编程语言中,`==`和`equals()`方法是用来比较对象之间关系的两种常见方式,但它们在使用上有着显著的区别。 首先,`==`运算符主要用于比较基本类型(如int、char、byte等)的值是否相等,或者比较引用类型...

    java中的比较运算符== 与 equals()方法 ..doc

    ### Java中的比较运算符==与equals()方法 在Java编程语言中,经常需要对变量进行比较,这涉及到了两种常见的比较方式:`==`运算符和`equals()`方法。这两种方式有着本质的区别,并且适用于不同类型的数据。下面将...

    ==与equals的比较

    "Java中的==和equals方法比较" 在Java中,`==`和`equals`是两种不同的比较方法,前者比较引用地址,而后者比较对象的实际内容。 首先,让我们来看看`==`运算符的用法。`==`运算符可以用来比较基本数据类型和引用...

    Java常见笔试、面试题目深度剖析 相等性(==及equals方法)详解

    本篇文章将深入剖析“==”运算符和equals()方法的区别与联系,帮助你在Java的笔试和面试中更好地应对相关问题。 首先,“==”运算符在Java中用于比较基本类型变量的值是否相等,例如int、char或boolean。对于引用...

    简单介绍java中的“==”和equals

    简单介绍java中的“==”和equals

    java读取配置文件

    if (key == null || key.equals("") || key.equals("null")) { return ""; } String result = ""; try { result = resourceBundle.getString(key); } catch (MissingResourceException e) { e....

    java 资料 equals 与== 的区别

    Java 中的equals和==的区别 Java 是一种面向对象的编程语言,其中有两种数据类型:基本数据类型和引用数据类型。基本数据类型包括整数类型、浮点数类型、字符类型、布尔类型等,共有八种;而引用数据类型则包括 ...

    java String 类的一些理解 关于==、equals、null

    在理解`==`和`equals()`方法在字符串比较中的作用时,我们需要深入理解Java内存模型以及对象引用的概念。 1. **`==` 与 `equals()` 的区别** `==` 运算符在Java中用于比较基本类型变量的值或者对象的引用。对于...

    ==运算符和Equals()方法区别

    `==`运算符和`Equals()`方法都是用来比较两个对象是否相等的,但是它们之间存在着很大的区别,`==`运算符比较的是两个对象的引用是否相等,而`Equals()`方法比较的是两个对象的内容是否相等。在编写代码时,我们需要...

    java中equals和==的比较.pdf

    在Java编程语言中,`equals()` 和 `==` 都用于比较对象,但它们的用法和含义有所不同。本文将详细解析这两个操作符在Java中的区别,并通过具体的例子进行阐述。 1. `==` 操作符: `==` 在Java中主要用来比较基本...

Global site tag (gtag.js) - Google Analytics