Object对象深入理解及通用接口

Object对象

java.lang.Object

java.lang包在使用的时候无需显示导入,编译时由编译器自动导入。Object类是类层次结构的根,Java中所有的类都继承自这个类。

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));  
	} 
}
输出
false
true
false
Exception in thread "main" java.lang.NullPointerException
at javabase.EqualTest.main(EqualTest.java:13)

那么,什么时候我们应该覆盖Object.equals方法呢?
1、如果类具有自己独特的”逻辑相等“概念(不同于对象等同的概念),而且超类还没有覆盖equals方法以实现期望的行为,这时我们就需要覆盖equals方法。这通常属于”值类“的场景,值类仅仅是一个表示值的类,例如Integer和Date。对于这样的类,我们希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象。
Integer类的equals实现:
......
    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));
	}
}
输出:
false
true
虽然a和b不是指向同一个对象,但是a和b是逻辑上相等的(值相等)。
2、此外,为了使得类的实例对象可以被用作映射表(map)的键值(key)、或者集合(Set)的元素,使映射表或集合表现出预期的行为。
例如,HashMap的put方法
    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是否已经存在了。
默认equals引起的错误
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()+"次");
		} 
	}
}
输出:
用Integer类为key
元素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次

不难发现,当我们没有合理的覆盖equals方法,产生了非预期的结果。
那么,什么时候我们不需要覆盖equals()方法呢?
1、当类的每个实例本质上都是唯一的,也就是说逻辑相同与对象等同是一回事。
2、超类已经覆盖了equals,从超类继承过来的行为对于子类也适合。
3、我们并不关心逻辑相等的测试,或者我们能确保equals方法永远不会被调用(例如,将equals方法私有化)。

在覆盖equals方法的时候,必须遵循它的通用约定。
equals 方法实现了等价关系:
自反性:对于任何非空引用值 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。

例如:String类的equals实现
    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));//非空性
	} 
}
输出:
true
false
false
true
true
true
true
false
如何实现高质量的equals方法
1、使用==操作符检查”参数是否为当前对象的引用“。
2、使用instanceof操作符检查”参数是否为正确的类型“
3、把参数转换成正确的类型
4、对于每个关键的域,检查参数对象中的域是否与当前对象中的对应域相匹配

hashCode()

    public native int hashCode();
Object类默认的实现是采用本地方法,返回的是对象的地址。
什么时候需要覆盖hashCode方法
在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运转,这样的集合包括HashMap、HashSet和HashTable。
hashCode通用约定:
1,在程序的执行期间,只要对象的equals方法比较操作所用到的信息没有被修改,那么对该对象调用多次hashCode方法都必须始终如一的返回同一个整数。在程序的多次执行中,如果equals方法比较操作所用到的信息被修改,每次执行返回的整数可以不一致。
2,如果两个对象的equals()方法比较是相等的,那么两对象调用hashCode()方法,必须产生同样的结果。 
3,如果两对象的equals()方法比较不相等,那么两对象的调用hashCode()方法,不要求产生两个不同的整数结果 。但是,给不相同的对象产生截然不同的整数结果,有可能提高散列表(Hash Table)的性能。
我们还是拿HashMap的put函数为例
    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()方法的返回值来进行哈希的。
Integer的hashCode方法
    private final int value;
    public int hashCode() {
        return value;
    }
默认hashCode引起的错误
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()+"次");
			} 
		}
}
输出:
用Integer类为key
元素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方法产生了非预期的效果。
将equals和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()+"次");
		} 
	}
}
输出:
用Integer类为key
元素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次
从结果可以看出,我们通过将equals和hashCode联合按照约定覆盖后,得到的正确的行为。

getClass

    public final native Class<?> getClass();
返回一个对象的运行时类 的 java.lang.Class 对象
该 Class 对象是由所表示类的 static synchronized 方法锁定的对象。

clone

    protected native Object clone() throws CloneNotSupportedException;
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。
Object 类的 clone 方法执行特定的克隆操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupported Exception。 注意: 此方法执行的是该对象的“浅复制”,而不“深复制” 操作。
浅拷贝:对于基本数据类型,复制其数据。对于对象引用,只是复制对象引用,而不是复制对象引用所指向的对象。
Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。

toString

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂。建议所有子类始终重写此方法。
Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())

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 
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
此方法类似于一个参数的 wait 方法,但它允许更好地控制在放弃之前等待通知的时间量。用毫微秒度量的实际时间量可以通过以下公式计算出来:1000000*timeout+nanos

在其他所有方面,此方法执行的操作与带有一个参数的 wait(long) 方法相同。需要特别指出的是,wait(0, 0) 与 wait(0) 相同。
    public final void wait() throws InterruptedException {
        wait(0);
    }
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
(wait/notify)等待/通知实例
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关键字

我们不难发现,Object类中很多方法都是Native实现。 那什么事Native方法呢?
native:native是方法修饰符,native是由另外一种语言实现的本地方法。本地方法非常有用,因为它有效地扩充了jvm.事实上,我们所写的java代码已经用到了本地方法,在sun的java的并发(多线程)的机制实现中,许多与操作系统的接触点都用到了本地方法,这使得java程序能够超越java运行时的界限。有了本地方法,java程序可以做任何应用层次的任务。
java使用起来非常方便,然而有些层次的任务用java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。

为什么要使用Native Method

与java环境外交互:
有时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方法不但允许进行简单的等同性,而且允许执行 顺序比较
类实现了Comparable接口,就表明它的实例具有内在的排序关系。
为实现Comparable接口的对象数组进行排序将会变得非常简单:Arrays.sort(arr);
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+" ");
		}
	}
}
输出:
1 1 2 34 
Ab CC ab abc 
一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。你付出很小的努力就可以获得非常强大的功能。事实上,Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或年代顺序等,那你就应该坚决实现这个接口。
compareTo方法的通用约定:
将这个对象与执行对象进行比较,当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。
注:符号sgn表示数学中的signum函数,它根据表达式的值为负数、零或者整数,分别返回-1、0、1。
1、实现者必须保证所有的x和y都满足sgn(x.compareTo(y))== - sgn(y.compareTo(x))。(这个也暗示着,当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)抛出异常
2、实现者必须确保这个关系是可传递的,(x.compareTo(y)>0&&y.compareTo(z)>0)暗示着x.compareTo(z)>0
3、最后,实现者必须确保x.compareTo(y)==0为真时,所有的z都满足sgn(x.compareTo(z))==sgn(y.compareTo(z))
4、强烈建议(x.compareTo(y)==0)==(x.equals(y))

实现compareTo方法

compareTo方法的实现与equals方法的实现非常相似,但也存在几处重大的区别。因为Comparable接口是参数化的,因此不必进行类型检查,也不必对它的参数进行类型转换。如果参数的类型不合适,这个调用甚至无法编译。如果参数为null,这个调用应该抛出NullPointerException异常,这就是有些集合不支持null的原因。

如果你需要一个非标准的排序关系,你可以使用Comparator。

Comparator

public interface Comparator<T> {

    int compare(T o1, T o2);

    /**
     * Indicates whether some other object is "equal to" thiscomparator.  
     */
    boolean equals(Object obj);
}

例如,对于默认的String排序规则是大小写敏感的。如果我们需要对String排序时忽略大小写或者自定义我们的排序规则,我们就可自定义自己的Comparator。
例如,String类的内部就有一个静态内部类CaseInsensitiveComparator,实现了忽略大小写的比较器。
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+" ");
		} 
	}
}
输出:
ab Ab abc CC 
利用Comparator我们可以更加灵活的对类应用不同的排序规则。Comparator可以看做是将算法与数据的分离,与C++ STL中的仿函数(functor)类似。

序列化与反序列化(Serializable接口

对象序列化API,它提供了一个框架,用来将对象编码成字节流(序列化),并从字节流编码中重新构建对象(反序列化)。
一旦对象被序列化,它的编码就可以从一台正在运行的虚拟机传递到另一台虚拟上(远程方法调用,RMI),或者被存储在磁盘上(Tomcat Session管理),供以后反序列化使用。
序列化还可以用于对象的深拷贝,先将对象序列化为字节流,再从字节流重建对象,就实现了对象的深拷贝。


参考:
http://lavasoft.blog.51cto.com/62575/15456/
http://blog.csdn.net/sjw890821sjw/article/details/8058843
《Effective Java》

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深入java虚拟机第二版 第1章 Java体系结构介绍 1.1 为什么使用Java 1.2 网络带来的挑战和机遇 1.3 体系结构 1.3.1 Java虚拟机 1.3.2 类装载器的体系结构 1.3.3 Java class文件 1.3.4 Java API 1.3.5 Java程序设计语言 1.4 Java体系结构的代价 1.5 结论 1.6 资源页 第2章 平台无关 2.1 为什么要平台无关 2.2 Java的体系结构对平台无关的支持 2.2.1 Java平台 2.2.2 Java语言 2.3.3 Java class文件 . 2.2.4 可伸缩性 2.3 影响平台无关性的因素 2.3.1 Java平台的部署 2.3.2 Java平台的版本 2.3.3 本地方法 2.3.4 非标准运行时库 2.3.5 对虚拟机的依赖 2.3.6 对用户界面的依赖 2.3.7 Java平台实现中的bug 2.3.8 测试 2.4 平台无关的七个步骤 2.5 平台无关性的策略 2.6 平台无关性和网络移动对象 2.7 资源页 第3章 安全 3.1 为什么需要安全性 3.2 基本沙箱 3.3 类装载器体系结构 3.4 class文件检验器 3.4.1 第一趟:class文件的结构检查 3.4.2 第二趟:类型数据的语义检查 3.4.3 第三趟:字节码验证 3.4.4 第四趟:符号引用的验证 3.4.5 二进制兼容 3.5 Java虚拟机中内置的安全特性 3.6 安全管理器和Java API 3.7 代码签名和认证 3.8 一个代码签名示例 3.9 策略 3.10 保护域 3.11 访问控制器 3.11.1 implies()方法 3.11.2 栈检查示例 3.11.3 一个回答“是”的栈检查 3.11.4 一个回答“不”的栈检查 3.11.5 doPrivileged()方法 3.11.6 doPrivileged()的一个无效使用 3.12 Java安全模型的不足和今后的发展 方向 3.13 和体系结构无关的安全性 3.14 资源页 第4章 网络移动性 4.1 为什么需要网络移动性 4.2 一种新的软件模式 4.3 Java体系结构对网络移动性的支持 4.4 applet:网络移动性代码的示例 4.5 Jini服务对象:网络移动对象的示例 4.5.1 Jini是什么 4.5.2 Jini如何工作 4.5.3 服务对象的优点 4.6 网络移动性:Java设计的中心 4.7 资源页 第5章 Java虚拟机 5.1 Java虚拟机是什么 5.2 Java虚拟机的生命周期 5.3 Java虚拟机的体系结构 5.3.1 数据类型 5.3.2 字长的考量 5.3.3 类装载器子系统 5.3.4 方法区 5.3.5 堆 5.3.6 程序计数器 5.3.7 Java栈 5.3.8 栈帧 5.3.9 本地方法栈 5.3.10 执行引擎 5.3.11 本地方法接口 5.4 真实机器 5.5 一个模拟:“Eternal Math” 5.6 随书光盘 5.7 资源页 第6章 Java class文件 6.1 Java class文件是什么 6.2 class文件的内容 6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 CONSTANT_Utf8_info表 6.4.2 CONSTANT_Integer_info表 6.4.3 CONSTANT_Float_info表 6.4.4 CONSTANT_Long_info表 6.4.5 CONSTANT_Double_info表 6.4.6 CONSTANT_Class_info表 6.4.7 CONSTANT_String_info表 6.4.8 CONSTANT_Fieldref_info表 6.4.9 CONSTANT_Methodref_info表 6.4.10 CONSTANT_InterfaceMethodref_ info表 6.4.11 CONSTANT_NameAndType_info 表 6.5 字段 6.6 方法 6.7 属性 6.7.1 属性格式 6.7.2 Code属性 6.7.3 ConstantValue属性 6.7.4 Deprecated属性 6.7.5 Exceptions属性 6.7.6 InnerClasses属性 6.7.7 LineNumberTable属性 6.7.8 LocalVariableTable属性 6.7.9 SourceFile属性 6.7.10 Synthetic属性 6.8 一个模拟:“Getting Loaded” 6.9 随书光盘 6.10 资源页 第7章 类型的生命周期 7.1 类型装载、连接与初始化 7.1.1 装载 7.1.2 验证 7.1.3 准备 7.1.4 解析 7.1.5 初始化 7.2 对象的生命周期 7.2.1 类实例化 7.2.2 垃圾收集和对象的终结 7.3 卸载类型 7.4 随书光盘 7.5 资源页 第8章 连接模型 8.1 动态连接和解析 8.1.1 解析和动态扩展 8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析CONSTANT_Class_info入口 8.1.5 解析CONSTANT_Fieldref_info 入口 S.1.6 解析CONSTANT_Methodref_info 入口 8.1.7 解析CONSTANT_Interface- Methodref_info入口 8.1.8 解析CONSTANT_String_info入口 8.1.9 解析其他类型的入口 8.1.10 装载约束 8.1.11 编译时常量解析 8.1.12 直接引用 8.1.13 _quick指令 8.1.14 示例:Salutation程序的连接 8.1.15 示例:Greet程序的动态扩展 8.1.16 使用1.1版本的用户自定义类装 载器 8.1.17 使用1.2版本的用户自定义类装 载器 8.1.18 示例:使用forName()的动态扩展 8.1.19 示例:卸载无法触及的greeter类 8.1.20 示例:类型安全性与装载约束 8.2 随书光盘 8.3 资源页 第9章 垃圾收集 9.1 为什么要使用垃圾收集 9.2 垃圾收集算法 9.3 引用计数收集器 9.4 跟踪收集器 9.5 压缩收集器 9.6 拷贝收集器 9.7 按代收集的收集器 9.8 自适应收集器 9.9 火车算法 9.9.1 车厢、火车和火车站 9.9.2 车厢收集 9.9.3 记忆集合和流行对象 9.10 终结 9.11 对象可触及性的生命周期 9.11.1 引用对象 9.11.2 可触及性状态的变化 9.11.3 缓存、规范映射和临终清理 9.12 一个模拟:“Heap of Fish” 9.12.1 分配鱼 9.12.2 设置引用 9.12.3 垃圾收集 9.12.4 压缩堆 9.13 随书光盘 9.14 资源页 第10章 栈和局部变量操作 10.1 常量入栈操作 10.2 通用栈操作 10.3 把局部变量压入栈 10.4 弹出栈顶部元素,将其赋给局部变量 10.5 wide指令 10.6 一个模拟:“Fibonacci Forever” 10.7 随书光盘 10.8 资源页 第11章 类型转换 11.1 转换操作码 11.2 一个模拟:“Conversion Diversion” 11.3 随书光盘 11.4 资源页 第12章 整数运算 12.1 二进制补码运算 12.2 Inner Int:揭示Java int类型内部性质 的applet 12.3 运算操作码 12.4 一个模拟:“Prime Time” 12.5 随书光盘 12.6 资源页 第13章 逻辑运算 13.1 逻辑操作码 13.2 一个模拟:“Logical Results” 13.3 随书光盘 13.4 资源页 第14章 浮点运算 14.1 浮点数 14.2 Inner Float:揭示Java float类型内部 性质的applet 14.3 浮点模式 14.3.1 浮点值集合 14.3.2 浮点值集的转换 14.3.3 相关规则的本质 14.4 浮点操作码 14.5 一个模拟:“Circle of Squares” 14.6 随书光盘 14.7 资源页 第15章 对象和数组 15.1 关于对象和数组的回顾 15.2 针对对象的操作码 15.3 针对数组的操作码 15.4 一个模拟:“Three—Dimensional Array” 15.5 随书光盘 15.6 资源页 第16章 控制流 16.1 条件分支 16.2 五条件分支 16.3 使用表的条件分支 16.4 一个模拟:“Saying Tomato” 16.5 随书光盘 16.6 资源页 第17章 异常 17.1 异常的抛出与捕获 17.2 异常表 17.3 一个模拟:“Play Ball!” 17.4 随书光盘 17.5 资源页 第18章 finally子句 18.1 微型子例程 18.2 不对称的调用和返回 18.3 一个模拟:“Hop Around” 18.4 随书光盘 18.5 资源页 第19章 方法的调用与返回 19.1 方法调用 19.1.1 Java方法的调用 19.1.2 本地方法的调用 19.2 方法调用的其他形式 19.3 指令invokespecial 19.3.1 指令invokespecial和[init]()方法 19.3.2 指令invokespecial和私有方法 19.3.3 指令invokespecial和super关键字 19.4 指令invokeinterface 19.5 指令的调用和速度 19.6 方法调用的实例 19.7 从方法中返回 19.8 随书光盘 19.9 资源页 第20章 线程同步 20.1 监视器 20.2 对象锁 20.3 指令集中对同步的支持 20.3.1 同步语句 20.3.2 同步方法 20.4 Object类中的协调支持 20.5 随书光盘 20.6 资源页 附录A 按操作码助记符排列的指令集 附录B 按功能排列的操作码助记符 附录C 按操作码字节值排列的操作码助

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值