Object对象
equals()
public boolean equals(Object obj) {
return (this == obj);
}
我们可以看出,Object类的默认实现是比较对象的引用是否指向同一个对象。
public class EqualTest {
public static void main(String[] args) {
Object a=new Object();
Object b=new Object();
Object c=a;
Object d=null;
System.out.println(a.equals(b));
System.out.println(a.equals(c));
System.out.println(a.equals(null));
System.out.println(d.equals(null));
}
}
输出
true
false
Exception in thread "main" java.lang.NullPointerException
at javabase.EqualTest.main(EqualTest.java:13)
......
private final int value;
public int intValue() {
return value;
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
......
public class IntegerEqualsTest {
public static void main(String[] args) {
Integer a=new Integer(666);
Integer b=new Integer(666);
System.out.println(a==b);
System.out.println(a.equals(b));
}
}
输出:
true
虽然a和b不是指向同一个对象,但是a和b是逻辑上相等的(值相等)。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
其中,if (e.hash == hash && ((k = e.key) == key ||key.equals(k)))语句里面用到了equals方法来判定key是否已经存在了。
public class EqualsInHashMap {
//直接用Object类默认的equals行为
static class Index{
int value;
public Index(int value){
this.value=value;
}
public int getValue(){
return value;
}
@Override
public int hashCode(){
return value;
}
}
public static void main(String[] args) {
Integer[] nums={1,2,2,4,3,3,7,3,9};
System.out.println("用Integer类为key");
Map<Integer,Integer> countMap=new HashMap<Integer,Integer>();
for(Integer i:nums){
if(countMap.containsKey(i)){
countMap.put(i, countMap.get(i)+1);
}else{
countMap.put(i, 1);
}
}
for(Map.Entry<Integer, Integer> entry:countMap.entrySet()){
System.out.println("元素"+entry.getKey()+"出现了"+entry.getValue()+"次");
}
System.out.println("用Index类为key");
Map<Index,Integer> indexMap=new HashMap<Index,Integer>();
for(Integer i:nums){
Index index=new Index(i);
if(indexMap.containsKey(index)){
indexMap.put(index, indexMap.get(index)+1);
}else{
indexMap.put(index, 1);
}
}
for(Map.Entry<Index, Integer> entry:indexMap.entrySet()){
System.out.println("元素"+entry.getKey().getValue()+"出现了"+entry.getValue()+"次");
}
}
}
元素1出现了1次
元素2出现了2次
元素3出现了3次
元素4出现了1次
元素7出现了1次
元素9出现了1次
用Index类为key
元素7出现了1次
元素2出现了1次
元素9出现了1次
元素1出现了1次
元素4出现了1次
元素2出现了1次
元素3出现了1次
元素3出现了1次
元素3出现了1次
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,只要equals 的比较操作在对象中所用的信息没有被修改,那么多次调用 x.equals(y) 始终返回 true 或始终返回 false。
非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
首先,比较是不是同一个对象。然后在两个字符串长度相等的情况下,比较每个位置上的字符是否相同。
public class StringEqualsTest {
public static void main(String[] args) {
String a=new String("abc");
String b=new String("ab");
String c=new String("abc");
String d="abc";
System.out.println(a.equals(a));//自反性
System.out.println(a.equals(b));//对称性性
System.out.println(b.equals(a));//对称性性
System.out.println(a.equals(d));//传递性
System.out.println(c.equals(d));//传递性
System.out.println(a.equals(c));//传递性
String sub=a.substring(1);
System.out.println(a.equals(c));//一致性
System.out.println(a.equals(null));//非空性
}
}
输出:
false
false
true
true
true
true
false
如何实现高质量的equals方法
1、使用==操作符检查”参数是否为当前对象的引用“。
hashCode()
public native int hashCode();
Object类默认的实现是采用本地方法,返回的是对象的地址。什么时候需要覆盖hashCode方法
3,如果两对象的equals()方法比较不相等,那么两对象的调用hashCode()方法,不要求产生两个不同的整数结果 。但是,给不相同的对象产生截然不同的整数结果,有可能提高散列表(Hash Table)的性能。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
int hash = hash(key.hashCode());语句显示,散列函数是根据key对象hashCode()方法的返回值来进行哈希的。 private final int value;
public int hashCode() {
return value;
}
public class HashCodeInHashMap {
//直接用Object类默认的hashCode行为
static class Index{
int value;
public Index(int value){
this.value=value;
}
public int getValue(){
return value;
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj instanceof Index){
Index anotherIndex=(Index)obj;
if(this.getValue()==anotherIndex.getValue()){
return true;
}
}
return false;
}
}
public static void main(String[] args) {
Integer[] nums={1,2,2,4,3,3,7,3,9};
System.out.println("用Integer类为key");
Map<Integer,Integer> countMap=new HashMap<Integer,Integer>();
for(Integer i:nums){
if(countMap.containsKey(i)){
countMap.put(i, countMap.get(i)+1);
}else{
countMap.put(i, 1);
}
}
for(Map.Entry<Integer, Integer> entry:countMap.entrySet()){
System.out.println("元素"+entry.getKey()+"出现了"+entry.getValue()+"次");
}
System.out.println("用Index类为key");
Map<Index,Integer> indexMap=new HashMap<Index,Integer>();
for(Integer i:nums){
Index index=new Index(i);
if(indexMap.containsKey(index)){
indexMap.put(index, indexMap.get(index)+1);
}else{
indexMap.put(index, 1);
}
}
for(Map.Entry<Index, Integer> entry:indexMap.entrySet()){
System.out.println("元素"+entry.getKey().getValue()+"出现了"+entry.getValue()+"次");
}
}
}
输出:
元素1出现了1次
元素2出现了2次
元素3出现了3次
元素4出现了1次
元素7出现了1次
元素9出现了1次
用Index类为key
元素7出现了1次
元素2出现了1次
元素9出现了1次
元素1出现了1次
元素4出现了1次
元素2出现了1次
元素3出现了1次
元素3出现了1次
元素3出现了1次
显然,我们没能合理覆盖hashCode方法产生了非预期的效果。
public class EqualsAndHashCode {
//将equals和hashCode根据约定联合实现
static class Index{
int value;
public Index(int value){
this.value=value;
}
public int getValue(){
return value;
}
//联合实现
@Override
public int hashCode(){
return value;
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj instanceof Index){
Index anotherIndex=(Index)obj;
if(this.getValue()==anotherIndex.getValue()){
return true;
}
}
return false;
}
}
public static void main(String[] args) {
Integer[] nums={1,2,2,4,3,3,7,3,9};
System.out.println("用Integer类为key");
Map<Integer,Integer> countMap=new HashMap<Integer,Integer>();
for(Integer i:nums){
if(countMap.containsKey(i)){
countMap.put(i, countMap.get(i)+1);
}else{
countMap.put(i, 1);
}
}
for(Map.Entry<Integer, Integer> entry:countMap.entrySet()){
System.out.println("元素"+entry.getKey()+"出现了"+entry.getValue()+"次");
}
System.out.println("用Index类为key");
Map<Index,Integer> indexMap=new HashMap<Index,Integer>();
for(Integer i:nums){
Index index=new Index(i);
if(indexMap.containsKey(index)){
indexMap.put(index, indexMap.get(index)+1);
}else{
indexMap.put(index, 1);
}
}
for(Map.Entry<Index, Integer> entry:indexMap.entrySet()){
System.out.println("元素"+entry.getKey().getValue()+"出现了"+entry.getValue()+"次");
}
}
}
元素1出现了1次
元素2出现了2次
元素3出现了3次
元素4出现了1次
元素7出现了1次
元素9出现了1次
用Index类为key
元素1出现了1次
元素2出现了2次
元素3出现了3次
元素4出现了1次
元素7出现了1次
元素9出现了1次
getClass
public final native Class<?> getClass();
返回一个对象的运行时类
的 java.lang.Class 对象。
clone
protected native Object clone() throws CloneNotSupportedException;
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。
Object 类的 clone 方法执行特定的克隆操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupported Exception。 注意: 此方法执行的是该对象的“浅复制”,而不“深复制” 操作。
toString
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂。建议所有子类始终重写此方法。
notify
public final native void notify();
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,并且选择是任意性的。线程通过调用其中一个 wait 方法,在对象的监视器上等待。直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:
通过执行此对象的同步 (Sychronized) 实例方法。
通过执行在此对象上进行同步的 synchronized 语句的正文。
对于 Class 类型的对象,可以通过执行该类的同步静态方法。
一次只能有一个线程拥有对象的监视器。
抛出:
IllegalMonitorStateException - 如果当前的线程不是此对象监视器的所有者。
另请参见:
notifyAll(), wait()
notifyAll
public final native void notifyAll();
唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;
此方法只应由作为此对象监视器的所有者的线程来调用。请参阅 notify 方法,了解线程能够成为监视器所有者的方法的描述。
抛出:
IllegalMonitorStateException - 如果当前的线程不是此对象监视器的所有者。
另请参见:
notify(), wait()
wait
public final native void wait(long timeout) throws InterruptedException;
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
当前的线程必须拥有此对象监视器。
public final void wait(long timeout, int nanos) throws InterruptedException
此方法类似于一个参数的 wait 方法,但它允许更好地控制在放弃之前等待通知的时间量。用毫微秒度量的实际时间量可以通过以下公式计算出来:1000000*timeout+nanos
在其他所有方面,此方法执行的操作与带有一个参数的 wait(long) 方法相同。需要特别指出的是,wait(0, 0) 与 wait(0) 相同。
public final void wait() throws InterruptedException {
wait(0);
}
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
public class SingleProduceConsumer {
static boolean empty=true;
static Object plate=new Object();//盘子
static class Consumer implements Runnable{
@Override
public void run() {
//加锁
synchronized(plate){
//当条件不满足时,继续wait
while(empty){
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//条件满足时,完成工作
System.out.println("从盘子里拿一个苹果!");
}
}
}
static class Produce implements Runnable{
@Override
public void run() {
//加锁
synchronized(plate){
//改变条件
System.out.println("向盘子里放入一个苹果!");
empty=false;
plate.notifyAll();
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread consumerThread=new Thread(new Consumer());
consumerThread.start();
Thread produceThread=new Thread(new Produce());
produceThread.start();
Thread.sleep(100);
consumerThread.interrupt();
produceThread.interrupt();
}
}
输出:
从盘子里拿一个苹果!
finalize
protected void finalize() throws Throwable { }
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize 方法,以配置系统资源或执行其他清除。该方法的执行通常是不可预测的,也是非常危险的,不推荐使用。
native关键字
为什么要使用Native Method
有时java应用需要与java外面的环境交互。这是本地方法存在的主要原因,你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节。
与操作系统交互:
通过使用本地方法,我们得以用java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用C写的,还有,如果我们要使用一些java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。
Sun's Java
Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互。本地方法的具体实现由JVM直接提供,更多的情况是本地方法由外部的动态链接库(external dynamic link library)提供,然后被JVM调用。
JVM怎样使Native Method跑起来
我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的访问类型(public之类)等等。如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system. loadLibrary()实现的。
最后需要提示的是,使用本地方法是有开销的,它丧失了java的很多好处。如果别无选择,我们才会选择使用本地方法。
DLL(动态链接库)文件中存放的是各类程序的函数(子过程)实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从DLL中取出。另外,使用DLL文件还可以减小程序的体积。
Comparable接口
public interface Comparable<T> {
public int compareTo(T o);
}
compareTo是Comparable接口中的唯一方法。compareTo方法不但允许进行简单的等同性,而且允许执行
顺序比较。
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
........
}
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
public int compareTo(String anotherString) {
int len1 = count;
int len2 = anotherString.count;
int n = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
if (i == j) {
int k = i;
int lim = n + i;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
} else {
while (n-- != 0) {
char c1 = v1[i++];
char c2 = v2[j++];
if (c1 != c2) {
return c1 - c2;
}
}
}
return len1 - len2;
}
......
}
实例:
public class ComparableTest {
public static void main(String[] args) {
Integer[] nums={1,34,2,1};
String[] strs={"ab","Ab","CC","abc"};
Arrays.sort(nums);
Arrays.sort(strs);
for(Integer i:nums){
System.out.print(i+" ");
}
System.out.println();
for(String s:strs){
System.out.print(s+" ");
}
}
}
输出:
Ab CC ab abc
一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。你付出很小的努力就可以获得非常强大的功能。事实上,Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或年代顺序等,那你就应该坚决实现这个接口。
2、实现者必须确保这个关系是可传递的,(x.compareTo(y)>0&&y.compareTo(z)>0)暗示着x.compareTo(z)>0。
实现compareTo方法
Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
/**
* Indicates whether some other object is "equal to" thiscomparator.
*/
boolean equals(Object obj);
}
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
}
......
}
public class ComparatorTest {
public static void main(String[] args) {
String[] strs={"ab","Ab","CC","abc"};
Arrays.sort(strs,String.CASE_INSENSITIVE_ORDER);
for(String s:strs){
System.out.print(s+" ");
}
}
}
输出:
利用Comparator我们可以更加灵活的对类应用不同的排序规则。Comparator可以看做是将算法与数据的分离,与C++ STL中的仿函数(functor)类似。