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 方法将会导致在运行时抛出异常。
具体实例可以参看:http://blog.csdn.net/sunxianghuang/article/details/51815076
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》