江苏 无锡 缪小东
在博客其它文章中提到Java Collections Framework――java中面向对象的数据结构的框架!几乎任何学习java的朋友都要对此包(java.util)非常熟悉。
该框架主要包含接口、实现和算法三部分。在本博客关于FailFast机制的文章中纵向研究了List接口和List的实现。关于算法一点都没有提及。本系列主要讲述该框架中的算法!
Java Collections Framework中的算法基本都包含在Arrays和Collections类中。这两个类有一个共同的特点:1.构造方法都是私有的――即不可以实例化、不可以继承;2.其中的方法都是static的――即静态的。因此他们完全是个工具类。他们两联系了整个Collections Framework――不明白吗!!任何Collections Framework中的实现都有一个toArray方法,该方法将任何数据结构转变为数组,转变为数组后可以通过Arrays完成一系列的操作。Collections类本身可以对任何数据结构进行一系列的操作。在Arrays中提供了asList方法,从而使Arrays可以向List转换。下面是他们的关系图!
关于数组和集合,他们之间是有较大的区别的:数组表示一组同一类型的“元素”!这些“元素”可以是基本数据类型,也可以是引用类型,数组最大的特点是:一旦创建数组,其容量是不可以变化的。而集合的特点是:一般只可以包含对象,并且集合中元素的数目是可以变化的!
下面是Arrays类的类图:
从上图可以看出Arrays类主要包括一些排序、查找、相等和赋值的操作,当然啦还包含一个asList的方法。
下图为Collections类的类图:
Collections类中主要包含:排序、查找、翻转、填充、拷贝、最大值、最小值、查序等等方法。还包含一些引用相关的方法如创建某些不可变的集合、同步的集合、单例的集合,以上三类方法是典型的Decorator模式的应用!
下面的文章会仔细介绍他们的源代码!
本篇主要讲述如何使用Collections Framework中的算法类Collections。从一个极其简单的Student类开始――主要满足一些初学者和一些always提议没有例子看不懂的朋友!其实在你看懂Sourcecode后完全可以自己写例子的!关键是你有没有读懂、吃透!看本篇文章的时候你是新手就从头看起吧!你比初学者好点就从后面看吧!你是高手就不用看了!
这个例子主要会说明System.out.println打印自定义对象的原理,以及Object对象的toString方法的意义,最后就是怎么覆盖了哦!
public class Student {
int age ;
String name ;
public Student(String name , int age ){
this.age = age ;
this.name = name ;
}
public static void main(String[] args){
System.out.println(new Student("master24",28));
}
}
看到这个简单的类了吧!我想打印时打印的是我的姓名和年龄!这么打印的结果却是:
Student@9304b1
不急啊!首先我们知道java中一切对象只能有一个父类,即只能从一个父类继承是吧!并且java中所有类的父类(java中的万类之源哦)为java.lang.Object。下面是Object的类图:
看到了吧!万类之主就是这个样子哦!我简单提一下吧!
1. getClass方法得到该类的类对象(类的类,就是一般讲的类的元素据――MetaData)主要和java语言的反射(Reflection)机制联系紧密,关于Reflection 的书籍很少,Manning出版社有一本《Java Reflection in Action》看过了讲的只能说可以,有实力还是研究其SourceCode吧!看一下具体的方法声明吧!
public final native Class getClass();
公共的、本地方法就是系统通过JNI实现的方法哦!其它类都可以调用哦!并且还是终极(final)的就是你不可以在其任何子类中覆盖(重写)这个方法。其实这样也好我们只要知道其意义,以后用到就调用就可以了!!――实用主义的想法!!
2. hashCode方法得到这个对象的hash值。公共的、本地的方法。有意思的是他可不是终极的哦!就是在必要的时候你可以覆盖他哦!具体在使用HashTable、HashSet等与hash相关的集合是储存这些自定义的对象时85%以上都要覆盖该方法!讲到Java Collections Framework中的Map方向时我会提到怎么写hash。下面是hashCode的方法:
public native int hashCode();
3. equals方法主要用于确认两个对象是否相等。(很多读者在使用Java Collections Framework时都会自定义一些对象然后放入某一个集合中,如具体的List、Set或者Map中但是却取不到该对象为什么呢!)以下是他的方法极其声明!可以揭开你的部分谜底哦!
public boolean equals(Object obj) {
return (this == obj);
}
看到了吧!很有意思的方法,返回当前对象的是否和传入对象完全相等,完全相等就意味着两者的内存空间、内存地址是完全相等的。现在我们创建两个Student的对象:
Student stu1 =new Student("master24",28);
Student stu2 =new Student("master24",28);
等吗?绝对不等的!为什么啊?他们的内存地址根本就不同!那我再创建一个:
Student stu3 =stu1;
那stu3.equal(stu2);相等吗?肯定相等了!stu3,stu2的引用指向内存中的同一个对象。
我现在要找一个叫master24的人,并且他的年龄是28,怎么办啊?!!覆盖这个方法吗!!
public boolean equals(Object o){
if(!(o instanceof Student)){
return false ;
}
return ((Student)o).name==this.name && ((Student)o).age==this.age;
}
这样你再试试吧!使用上面的stu1,stu2的比较方法: System.out.println(stu1.equals(stu2));
判断一下相等吗??呵呵,我结果都出来了true相等吧!在使用其它自定义类时,放入集合中然后再去找他,有问题就知道出错在哪里了吧!(关键原因集合中查找某个对象,是将集合中的各个元素与你要查找的对象使用他们中任何一个equals方法比较的!你没有覆盖这个方法,当然有可能不对了啊!多看看java.util包中的源代码吧!)
4. clone 方法,是将当前对象克隆,生成一个克隆的对象,它可不是当前对象哦!下面是克隆的方法:
protected native Object clone() throws CloneNotSupportedException;
本地的方法哦!所有其它的子类都会调用这个方法的哦!不能理解吗?看看《Think in java》中关于java中之类加载时的一些细节!当我们需要为这个对象提供一个可以克隆的对象时,我们必须覆盖此方法!
5. notify、notifyAll和两个wait方法,主要在多线程中使用!他们是多线程中wait/notify机制的全部。讲到多线程提及一下,多线程的东西其实不是很多,主要有Thread、ThreadGroup、ThreadLocal等这几个类和上面Object对象提供的wait/notify机制。简单吧!就是这几个类构成了多线程!是否你完全了解了这几个类就可以对多线程了解了呢!早呢!2万5千里长征你才开始了几步呢!由此几个简单的类Doug Lea创建了java.util.concurrent包,他是java为我们提供的多线程编程中一致性编程的工具包!厉害吧!会写几个代码,会什么RUP不是高手,真正的高手是有思想的!没有思想的CodeMaker仅仅是IT行业的黑领罢了!学吧,读者们,JDK本身就是可宝藏,其中很多设计理念、设计方法等待你的挖掘!本博客会和大家共同挖掘!呵呵!好像现在都是我master24挖掘了给大家哦!期望大家共同挖掘、共同探讨!(想到中国IT我就郁闷、很想骂人!一个13-14亿的人口大国整个IT行业都是替老外外包――你知道外包是什么吗?在上面提到的一个简单的程序中,外包就是类似与别人写底层的数据结构,我们写GUI,甚至连这个都不是!开源社区里有几个是中国人写的啊!?J2EE服务器有几个是中国人的!!中国人有几个自己的OS啊!简单的单片机有有几个中国厂商能开发啊!人家的硅谷80%以上都用java、.net或者C##,我们用什么啊!再看看国内的家伙用J2EE都干什么啊!?IT发展了才多少年啊,我们就比别人落后了至少10年!还有,你看看中国的高等教育吧!那个王八蛋想出来高校扩招的啊!今年毕业400万大学生,明年480万,一年增加20%啊!发达国家人家的名校50年才增加10%啊!除了为了钱,还会为什么啊!教育是民族之本知道吧!一个国家的搞定教育可以几年就搞垮,你有没有想想要花多少年才可以搞好啊!在中国高校里出现的不仅仅是学生,高等教育产业化的人在乎的是什么啊!!是可以每一个学生人头得到的钞票,你看到了吗――中国高校的人头其实就是钞票,一个人头一年至少3000的培养费,还有至少5000的书费、生活费!再看看我们的房地产吧!一个有9亿农民的农业大国,竟然将不会为社会带来额外价值的房地产作为国家的支柱产业!荒唐到了极点!房地产是什么啊!房子能带来额外社会价值吧!政治经济学上好像只有劳动人民在土地上付出自己的劳动,将这种与社会无关的价值凝结到具体的农业产品中才形成价值,只有工人通过辛勤劳动,将劳动凝结与工业产品中才会形成价值!土地是劳动和价值转换的载体、生产工具是将劳动转变为价值凝结与产品中的工具!房子不会再创造价值的,不会将原先的价值增加的,不会将人类的劳动转移的!某些专家、学着大谈供求关系,你知道吗?价值规律明显的一条:“价格不会偏离价值太远,价格总是围绕价值上下波动”!我也不知道现在的经济杠杆究竟干什么,不是调控吗?怎么越调控越高啊!为什么其它发达国家都会认为房价超过工资收入的3-6倍就已经产生房地产泡沫了!我们呢,部分城市是10倍以上啊,还没有泡沫!还是健康的!无锡的房价竟然和珠海的房价差不了多少!涨吧!涨吧!将中国的经济绑在房地产的战车上吧!美国人工薪阶层的薪水以美元计和中国工薪阶层的收入差不多的!美国人买房子一般都是以Block计算的,买栋房子两层一共6间左右,花30-40万dollar!人家已经大叫房地产发展太快了!持续上涨了近20年的美国房地产、俄罗斯房地产不是也跌了吗!只要你有泡沫,你就不可能不跌!临近的香港、对我们虎视眈眈的小日本又不是没有先例!有些所谓的专家学者,一直以这样的逻辑说话:“房价是高,但是将下来,银行会受到冲击,社会会不稳定!”,典型的脑子进水!那就按照你的涨呗!看是涨到一个高位跌下惨还是今天适可而止的惨!中国房地产只会有两种情况:1.继续上涨,然后暴跌!这样一部分人可以捞到相当数目的社会财富,而社会的绝大多数人的财产会严重缩水!你有没有注意到社会发展太快,可能是一年比一年争得多,但是等你老得时候,最现实的就是你年轻时候交的养老金到你年老的时候根本就不能为此你的生活!今天你每年交一头牛,你退休后你的钱只能买根针!好玩吗?!2.立刻煞车,逐渐回归本来的价值!这就得等等看了!前几年的社会调查表明:社会上20%的人占有社会财富的80%,现在呢!恐怕是15%的人占有社会85%的财产了吧!中国的高等教育、房地产和医疗在社会重分配方面起了多少作用呢!!可以不夸赞地说:“收费的高等教育导致来自农村的学生的家庭20-30%进入赤贫”。再看看我们的食品吧!什么有毒的都有,媒体从不否认我们步入一个伦理逐渐衰退的时期,有多少人会昧着良心赚钱呢!看看我们央视的《每日调查》就可以了!1962年蕾切尔·卡逊那本《寂静的春天》(Silent Spring)中作者花费了四年时间,搜集了大量的事实,证明由于对剧毒性农药DDT的滥用,已经让人类春天变得寂静无声。40多年后的今天我们会发现:癌症、艾滋病……的蔓延。事实证明了我们正在受到祖先埋下的恶果的报应。人类贪婪地向自然界索取,非可程序的发展正在受到自然的报复!《谁谋杀了男性精子?》中披露:在过去的10年中男性精子减少了1/3,精子数量下降超出想像,畸形劣质精子比例增大。这又是什么造成的呢!是我们自己、我们的祖先!任何违背规律,无论是自然规律还是经济规律,都会受到规律的惩罚的!在经济规律面前无论政府的对经济的调控能力有多强,很多事件还是不能办到的!珍重吧!各位读者!我罗嗦太多了!总之,做一个有良心的、有道德、有水平的程序员!要改变别人、甚至改变社会先从改变自己开始!好好读书吧!多读书、读好书!下面继续吧!下面是这几个方法:
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException ;
关于线程,在讲解完Collections后,我们会详细讨论,希望大家关注!
6. finalize方法,是供虚拟机调用的!他主要是在虚拟机垃圾回收该对象时,使用用户定义的finalize方法清除一些资源,如:数据库连接、网络连接等等。讲到VM时会举个例子!!下面是其方法:
protected void finalize() throws Throwable { }
这是一个protected的方法,任何子类都可以覆盖此方法!看到了吧!他可不是一个抽象方法或者final或者native的方法!你可以在必要的时候覆盖他!
7. toString是我们研究Object类的最后一个方法,也是我们发现例子中对象被打印为Student@9304b1的原因!先看看System.out.println(Object)吧!首先out是System类的一个静态的成员变量,它的类型为PrintStream,其实就是PrintStream.println(Object),下面看看这个方法吧!
public void println(Object x) { //PrintStream
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
哦!该方法调用String类的ValueOf,就再看看String.ValueOf(Object)吧!
public static String valueOf(Object obj) { //String
return (obj == null) ? "null" : obj.toString();
}
看到了吧!其实System.out.println(Object)就是调用Object对象的toString方法,然后交给PrintStream打印而已!绕到这里,就是我们Object.toString方法了!下面是Object对象的toString方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
看到了吧!先得到对象的类的名字,加上一个"@",最后加上该对象的hash值的十六进制的形式!懂了吧!Student@9304b1就是Student类的类的名称加"@",再加上该类的hash值的十六进制!!OK!
所有类的父类Object就有这么多学问!有多少你以前明白呢!想研究更多,知道java中各个主要类的来龙去脉请经常关注本博客吧!
还是会到例子吧!要打印学生记录!简单啊!就是覆盖父类的toString方法啊!刚才的学生类现在改为:
public class Student {
int age ;
String name ;
public Student(String name , int age ){
this.age = age ;
this.name = name ;
}
public String toString(){
return name + " 同学 年龄为 "+ age ;
}
}
现在,你可以创建一个Student对象!然后System.out.println看看!
下面是一个使用Collections.sort的例子,Student类就是刚才的例子!看看下面的例子吧!写了三个静态的辅助方法,分别完成产生8个字符的学生姓名、产生15-25之间学生年龄以及一个打印所有学生集合的方法!
import java.util.Random;
import java.util.*;
import java.io.*;
public class StuCompExample{
//这是一个主方法
public static void main(String[] args)throws IOException{
List stus = new ArrayList(); //创建一个List对象,用它来包含学生元素
for(int i = 0 ; i < 15 ; i++){
Student stu = new Student(generateStuName(),generateStuAge());
stus.add(stu);
} //产生15个学生记录,将他们放入集合中
System.out.println(" 该集合刚刚被创建 !");
printCollection(stus); //打印这个集合
System.out.println("该集合将要被排序 !");
try{ //为了得到错误,我将sort包含在try/catch语句块中
Collections.sort(stus); //核心哦!排序!不过肯定有问题!!!
}catch(Exception e){
//System.out.println(e);
new PrintStream("aaa.txt").println(e); //将问题打印到文件aaa.txt中
e.printStackTrace(new PrintStream("bbb.txt")); //将问题打印到文件bbb.txt中
//呵呵,能理解他们的差别吗???
}
printCollection(stus);
}
public static String generateStuName(){ //产生8个字符的随机学生姓名
char[] nc = new char[8];
for(int i = 0 ; i < nc.length ; i ++){
Random rand = new Random();
int j = 0 ;
if(rand.nextBoolean()){
j = rand.nextInt( (char)'Z'-(char)'A')+(char)'A';
}else{
j = rand.nextInt( (char)'z'-(char)'a')+(char)'a';
}
nc[i] = (char)j;
}
return new String(nc);
}
public static int generateStuAge(){ //产生10-25之间的学生年龄
Random rand = new Random();
return rand.nextInt(10)+15 ;
}
public static void printCollection(Collection c){ //打印学生记录
System.out.println("------------------------------");
Iterator it = c.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("###############################");
System.out.println();
}
}
以下是运行结果:
//aaa.txt
java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
上面是aaa.txt中的内容!意思是Student对象不能被cast到Comparable!为什么啊??看下面吧!更具体的!
//bbb.txt
java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
at java.util.Arrays.mergeSort(Arrays.java:1147)
at java.util.Arrays.mergeSort(Arrays.java:1158)
at java.util.Arrays.mergeSort(Arrays.java:1158)
at java.util.Arrays.sort(Arrays.java:1082)
at java.util.Collections.sort(Collections.java:118)
at StuCompExample.main(StuCompExample.java:18)
上面的错误看出什么问题了吗?首先是Student不能cast到Comparable。然后是和Arrays相关的错误!为什么啊?后面我们会在分析源代码的时候看到Collections.sort方法时就是调用Arrays.sort方法!关注吧!好像有一句在Collections.java的第118句有问题,那我们打开Collections看看吧!118句为Arrays.sort(a);a是一个数组!那看看Arrays的第1082句吧!第1082句是mergeSort(aux, a, 0, a.length, 0);再查这个方法看看第1158句,看到了吧! mergeSort(dest, src, low, mid, -off);没什么进展,看看1147句吧,((Comparable) dest[j-1]).compareTo(dest[j])>0; j--);哦!找到的所有问题的症结所在!在Arrays中将第j-1个元素cast为Comparable,然后调用其compareTo方法与第j个元素比较!好了,就是我们的Student类必须实现Comparable接口!(对于aaa.txt和bbb.txt为什么会有差异可以自己研究哦!要么等待博客的其它文章介绍吧!)
下面是我们实现Comparable接口的Student类!
public class Student implements Comparable {
int age ;
String name ;
public Student(String name , int age ){
this.age = age ;
this.name = name ;
}
public String toString(){
return name + " 同学 年龄为 "+ age ;
}
public int compareTo(Object obj){
return this.name.compareTo(((Student)obj).name);
}
public boolean equals(Object o){
if(!(o instanceof Student)){
return false ;
}
return ((Student)o).name==this.name && ((Student)o).age==this.age;
}
}
看到了吧!当前学生实例的名字排在要比较的对象前面就返回1,相等就返回0――表示相等,比当前小返回-1。简单吧!明白吗?主要是我们学生的名字是String类型的,看看String类吧!
public final class String implements java.io.Serializable, Comparable
String类实现了Comparable接口,肯定就有compareTo方法了吧!自己仔细研究吧!
下面是一个比较详细的例子,包含了排序,反序、打乱次序、最大值、最小值等等。
import java.util.Random;
import java.util.*;
import java.io.*;
public class StuCompExample{
public static void main(String[] args)throws IOException{
List stus = new ArrayList();
for(int i = 0 ; i < 15 ; i++){
Student stu = new Student(generateStuName(),generateStuAge());
stus.add(stu);
}
System.out.println(" 该集合刚刚被创建 !");
printCollection(stus);
System.out.println("该集合将要被排序 !");
try{
Collections.sort(stus);
}catch(Exception e){
//System.out.println(e);
new PrintStream("aaa.txt").println(e);
e.printStackTrace(new PrintStream("bbb.txt"));
}
//Collections.sort(stus);
printCollection(stus);
System.out.println("下面要反序了 ");
Collections.reverse(stus);
printCollection(stus);
System.out.println("下面要打乱顺序了 ");
Collections.shuffle(stus);
printCollection(stus);
System.out.println("下面是最大值");
System.out.println( Collections.max(stus));
System.out.println("下面是最小值");
System.out.println(Collections.min(stus));
}
public static String generateStuName(){
char[] nc = new char[8];
for(int i = 0 ; i < nc.length ; i ++){
Random rand = new Random();
int j = 0 ;
if(rand.nextBoolean()){
j = rand.nextInt( (char)'Z'-(char)'A')+(char)'A';
}else{
j = rand.nextInt( (char)'z'-(char)'a')+(char)'a';
}
nc[i] = (char)j;
}
return new String(nc);
}
public static int generateStuAge(){
Random rand = new Random();
return rand.nextInt(10)+15 ;
}
public static void printCollection(Collection c){
System.out.println("------------------------------");
Iterator it = c.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("###############################");
System.out.println();
}
}
自己调试吧!看看打印结果!是不是很爽啊!
下面是一个自定义排序的例子!自定义排序往往指不使用加入被排序对象(如我们的Student,一般必须实现Comprable接口)的campareTo方法比较,而是使用自定义的实现java.util.Comparator接口的排序其排序!刚才我们的学生是按照姓名排序的,现在我们按照年龄排序!我们写一个自己的排序器好了!
import java.util.Comparator;
public class StudentAgeComparator implements Comparator{
public int compare(Object obj1 ,Object obj2){
Student stu1 = (Student)obj1 ;
Student stu2 = (Student)obj2 ;
return (stu1.age > stu2.age)?1:((stu1.age== stu2.age)?0:-1) ;
}
}
简单吧!不会有兄弟连?:运算符都不太懂吧!那就干干脆脆地写成:
if(stu1.age > stu2.age) return 1 ;
if(stu1.age == stu2.age) return 0 ;
if(stu1.age <= stu2.age) return -1 ;
会写吧!取代上面程序中的return语句好了!(可惜还是有错误!为什么啊!)简单地改为:
if(stu1.age > stu2.age) return 1 ;
if(stu1.age == stu2.age) return 0 ;
return -1;
为什么,你就自己查相关语法书吧!
现在你可以将上面
StuCompExample例子中的Collections.sort(stus);
改为:
Collections.sort(stus,new StudentAgeComparator());
看看结果吧!是不是不一样啊!肯定是按年龄排序的!不过你要是认为按年龄排序还不是很好,总体按年龄排序,同一个年龄的按姓名排序!那也简单啊:
import java.util.Comparator;
public class StudentAgeAndNameComparator implements Comparator{
public int compare(Object obj1 ,Object obj2){
Student stu1 = (Student)obj1 ;
Student stu2 = (Student)obj2 ;
if(stu1.age > stu2.age){
return 1 ;
}else if(stu1.age < stu2.age){
return -1 ;
}else if(stu1.name.compareTo(stu2.name)>0){
return 1;
}else if(stu1.name.compareTo(stu2.name)<0){
return -1;
}else{
return 0 ;
}
}
}
现在将排序语句改为:Collections.sort(stus,new StudentAgeComparator());看看结果是否一样!
这里我们研究了Object类,以及System.out.println方法!以及Collections的相关例子!写了几个自定义的排序器!
总之,要被集合排序的对象必须实现java.util.Comparable接口,实现它的compareTo方法!要写自定义的方法必须自己写一个排序器,该排序器必须实现java.util.Comparator接口,实现它的Compare方法!注意他们两个接口的名称差别,以及他们比较方法的差别!
关于算法的具体细节!请关注我下面的文章吧!
最后提醒大家一句,看了上面的文字也不要太“愤青”了!埋头好好干,只有改变了自己才能改变别人!