星期六 2006年07月22日
如何写好一个Java类
可以这么说,每一个java应用程序都是由许许多多的CLASS组成的,这些类在写的时候就直接或间接决定了你应用程序的稳定性,扩展性,重用性以及执行效率,所以作为一名合格java程序员的最基本技能不应该只是知道如何写一个类,而应该是知道如何写好一个类。
1.名字
和人一样,类也有自己的名字,在写一个类前,首先要考虑的是如何使这个类的名字好识别,好记忆,并能充分说明这个类的性质和功能。java中的每一个类都建议首字母大写(当然,除非第一个不是字母),并最好能通过名字识别这个类是Interface,Abstract Class或是Class
比如一个购物车的接口类可以命名为Inter_ShopCart 或则 Inter_ShopBag,购物车的抽象类可以是Abs_ShopCart,一个普通的购物车类可以是ShopCart。对于类中的常量,也就是final static声明的类型的一般是使用全大写字母表示:
比如 final staitc String HOST="www.matrix.org.cn,其他的一般使用小写,由于方法一般表示一个动作,所以方法名常常采用谓语+名词的形式,首字母小写,比如清理日志的方法:clearLog(),当然也有些比较容易理解的可以省略名词,比如init(),destroy(),log();另外,在整个系统的开发过程中,整个系统中使用的命名规则要统一,要是一会儿是Abst_ShopCart,一会儿是Abs_ShopCart,那会是很糟糕的!
(更多可以参考effective java 38条:遵守普遍接受的命名规则)
2.藕合度 粒度(更多是用于模式设计和服务)
虽然我这里说的是针对单一的一个类,但是实际情况下,系统的正常运转是需要许许多多的类支持的,所以在写一个类的时候,也必须要考虑到对其他类以及整个系统产生的影响。针对这个问题,更多的是需要长时间的经验积累,在这里,我只是写下自己的一点认识。对一个类来说,粗粒度的类可以是指一个实现了很多功能的类,而这些功能完全可以分解出去,形成细粒度的类。一个细粒度的类在被调用的时候相对于粗粒度的类要更高效一些,所以对于一个常常要用的功能,可以考虑将其实现为细粒度Class。而且细粒度的类更能提供私有化的服务,因为对一个细粒度类进行管理和操作相比与粗粒度类的要容易许多。然后细粒度也会带来很多问题,一个系统中有太多的细粒度类会降低各个类的独立性,举个例子:如果一个粗粒度中的类要访问一个对象,若分解为三个细粒度类,那么就是三个细粒度类要访问这个对象,若这个对象是个“中介者”,那么原本两根线的关系,可能现在就发展成十几跟线了,这样一来,系统的结构就变得复杂,并行开发的难度就会增加。
比如下面这个例子:Agency是一个中介,用来传递信息,并判断权限,Macro是一个操作者,可以执行Other设定的任务,也可以修改这个任务
class Agency
{
private final static Agency agency=new Agency();
private String cmd_1;
private String cmd_2;
private String cmd_3;
private Agency(){}
//'--------set_Method------------------
public void setCmd_1(String cmd)
{
cmd_1=cmd;
}
public void setCmd_2(String cmd)
{
cmd_2=cmd;
}
public void setCmd_3(String cmd)
{
cmd_3=cmd;
}
//----get_Method--------------------------
public String getCmd_1()
{
return cmd_1;
}
public String getCmd_2()
{
return cmd_2;
}
public String getCmd_3()
{
return cmd_3;
}
//-------static factory method------------------
public static Agency getAgency(String password)
{
if (password.equals("matirx"))
{
return agency;
}else
{
return null;
}
}
};
class Other
{
public static void main(String[] a)
{
Agency agency=Agency.getAgency("matrix");
if (agency!=null)
{
agency.setCmd_1("print");
agency.setCmd_2("copy");
agency.setCmd_3("edit");
}
}
};
public class Macro
{
private Agency agency=Agency.getAgency("matrix");
public void executeCmd(int i)
{
if (agency!=null)
{
switch(i)
{
case 1:System.out.println("execute "+agency.getCmd_1());break;
case 2:System.out.println("execute "+agency.getCmd_2());break;
case 3:System.out.println("execute "+agency.getCmd_3());break;
default:System.out.println("funly");
}
}
}
public void modifyCmd(int i,String cmd)
{
if(agency!=null)
{
switch(i)
{
case 1:agency.setCmd_1(cmd);break;
case 2:agency.setCmd_1(cmd);break;
case 3:agency.setCmd_1(cmd);break;
default:System.out.println("funly");
}
}
}
}
下面将Macro分解为两个Micros
class Micros_1
{
private Agency agency=Agency.getAgency("matrix");
public void executeCmd(int i)
{
if (agency!=null)
{
switch(i)
{
case 1:System.out.println("execute "+agency.getCmd_1());break;
case 2:System.out.println("execute "+agency.getCmd_2());break;
case 3:System.out.println("execute "+agency.getCmd_3());break;
default:System.out.println("funly");
}
}
}
}
class Micros_2
{
private Agency agency=Agency.getAgency("matrix");
public void modifyCmd(int i,String cmd)
{
if(agency!=null)
{
switch(i)
{
case 1:agency.setCmd_1(cmd);break;
case 2:agency.setCmd_1(cmd);break;
case 3:agency.setCmd_1(cmd);break;
default:System.out.println("funly");
}
}
}
}
若Macro为粗粒度的化,Micros就为细粒度,一开始Macro与Other通过Agency联系,现在是两个Micros与Agency联系
1:Macro——Agency——Other
2:
Micros_1
\
Agency-Other
/
Micros_2
若Macro里面还有一些公共方法,分解后可能会各自分散到两个Micros里面,这时如果要调用对方的方法话(比如得到对方动作执行的结果),情况就是下面这样
3:Micros_1
| \
| Agency-Other
| /
Micros_2
(很明显1比3更松散,简单)
当然这个例子十分简单,我只是想说明对于一个类,粒度以及耦合的概念,对于上面一个例子Macro是更合适的选择,无乱是逻辑上还是结构上面都比两个Micros更好。但若我调用executeCmd比较频繁且很少调用modifyCmd,很明显调用Micros_1的开销要小于Macro。一般说来可以对一些重要功能的实现为细粒度的类,将关联度高的类合并成粗粒度类,也可以将细粒度的类组合成粗粒度,而实际上,这也是个不错的选择,既考虑到了松散的耦合结构,也考虑到了细粒度的安全与高效,同时也使每个功能模块更加清晰,并且以后的改良中,这种结构更适合扩展和私有化个别功能。
关于粒度的设计的确是难于说明,这里只是简单提出在写一个class的时候一定要认识到这个问题,更多还是请参考大师的代码以及自己的经验!
3.慎用继承
自从我学java一来,网络上面的大部分言论一直对java的继承持否定态度,而我在写代码的过程当中也尽量使用接口和组合来代替继承,除非要明确表明两个类的层级关系。纵使你一定要使用继承,也请一定要记住,深度继承是无任何意义的,唯一显著的作用就是增加系统的负担,因为初始化子类的时候是要先访问父类的构造函数的。而且过多的继承可能会影响他人的阅读,因为我在子类中使用的方法很可能在子类的代码中连个影子都没有,这犹如看最初的javaAPI文档一样,子类继承而来的方法在子类的文档里面根本看不到!
另外,若你写的这个类是为了继承而设计,那么对这个类的任何变动都可能对子类产生无法预期影响,这需要你在以后的开发过程当中相当小心,你甚至会在以后的开发过程中将这个类看成一个炸弹,随时都可能为你带来麻烦,所以:我的认为就是,尽量不要使用继承,即使万不得已要使用继承也请使用较浅的层次结构,以减轻系统的负担。
4.优先使用集合框架类而非矢量类
在java的里面,使用LinkedList,ArrayList,HashMap等比使用Vector,Hashtable快一点,因为后面的的两个类做了同步处理,而集合框架中的大多没有,除了这里的Hashtable
5.少用Reflection
应该说这个方法本来就比较少会用到,一般说只有实现远程调用如RMI这样的功能的类才会用到反射,或是java开发工具的插件,比如列出一个类的所有方法和构造函数等。尽管在JDK1.4后反射的性能有了明显提高,但我还是建议——没有必要就不要去用。
6.本地变量的访问速度更快
本地变量的访问速度比实力变量快,因为他们存储在栈(stack)中,而非堆(heap)中
7.避免在循环中声明变量
//1.-------------------
String s;
for(int i=0;i<10;i++)
{
s=new String("A");
}
//2.------------------
for(int i=0;i<10;i++)
{
String s=new String("A");
}
第一种方法比第二种将会更高效,因为他不会在每次循环的时候都声明一个新的reference,而这个动作同样要涉及到内存操作
8.最终限定符
若你认定一个值为常量的时候,就用常量的声明方式来声明他,final声明的变量。因为对于java中的final变量,java编译器是进行了优化的。
每个使用了final类型变量的地方都不会通过连接而进行访问。java编译器在编译的时候就将这个常数编译到访问类的指令码或者常量池中,这个访问类在以后对这个常数的调运就不会再通过该常量的外部类来连接,而是直接访问自己保存在类文件的副本
9.考虑使用StringBuffer来代替String+运算
String在java种被设计成立非可变的,也就是说,任何对String的改变都会产生一个新的String,这对涉及到大量字符合并的类是个不小的负担,为了避免这样的情况发生,java设计出了一个辅助类StringBuffer,他是可变的,对他的合并操作不会产生一个新的实例
大量重复myString+="Test"这样的操作的一个优化设计是使用StringBuffer的append方法来替代这里的"+",myStringBuffer.append("test");
10.使用符合运算符
//1.--------
int i=1;
i=i+1;
//2.---------
int i=1;
i+=1;
第二段代码在性能上要由于第一段
11.合理使用lock
在线程中,为了保证共享资源的安全,很多时候要用到锁机制,并使用synchronized来处理那些可以改变共享资源的方法,然而对整个方法使用synchronized是不明智的,因为并不是所有方法中的所有代码都是非线程安全的,我们只要将非线程安全的代码同步化即可。另外一个小技巧是:使用0长度的数组比使用一个对象来做锁要更高效一点。
public synchronized void method()//保守的做法
{
//Thread safe code
//thread unsafe code
}
//----------------------------------
public synchronized void method()//高效的做法
//Thread safe code
synchronized(this)
{
//thread unsafe code
}
}
12.方法的嵌入
把一个方法声明为final static或则public有助于JAVA编译器在编译的过程中不再担心以后的绑定而使代码得到优化
13.使用arrayCopy()进行数组复制
使用java.lang.System类中定义的arrayCopy()方法比使用循环处理源数组来实现复制要快很多,因为这个方法是在本机定义的
14.使用charAt()
如果只比较一个字符的话,charAt()的速度比String的startsWith()方法要快
也就是说"Test".startWith("T")比"Test".charAt(0)="T"要稍微慢那么一些
15.考虑使用静态工厂方法来替代构造函数
这样做有三个好处
(1)静态工程方法可以自己定义名字,这会是其更好理解,不容易出错。而构造函数只能靠参数来区分 同 一个类的不同构造函数,而这往往容易出错
(2)不用每次都创建一个新的实例,我可以通过return一个事先存储起来的实例来提供给调用者
(3)可以返回一个原返回类型的子类型,这样我们在选择被返回对象的类型就有了更大的灵活性。
16.总是改写toString()
相信你不希望System.out.print(yourObject);的时候显示的仅仅是一个名字,一个@符合和一串散列码的无符号16进制表示,所以你应该覆写Object的toString(),从而是你在任何时候都可以返回一串有意义的字符
17.使用非阻塞I/O
JDK1.4提供了非阻塞I/O(NIO)的选项,它可以提高扩展能力和性能
18.考虑克隆
克隆是一种非常有效地生成大量相似对象的办法,比用new运算符号要快很多,但在改写clone的时候一定要注意结果的正确些(具体可以参考effective java 10 条:谨慎地改写clone)
Posted at 11:38下午 七月 22, 2006 by Kaizen in Java | 评论[0]
星期四 2006年07月20日
数学之美 系列 12 - 余弦定理和新闻的分类[google]
余弦定理和新闻的分类似乎是两件八杆子打不着的事,但是它们确有紧密的联系。具体说,新闻的分类很大程度上依靠余弦定理。
Google 的新闻是自动分类和整理的。所谓新闻的分类无非是要把相似的新闻放到一类中。计算机其实读不懂新闻,它只能快速计算。这就要求我们设计一个算法来算出任意两篇新闻的相似性。为了做到这一点,我们需要想办法用一组数字来描述一篇新闻。
我们来看看怎样找一组数字,或者说一个向量来描述一篇新闻。回忆一下我们在“如何度量网页相关性”一文中介绍的TF/IDF 的概念。对于一篇新闻中的所有实词,我们可以计算出它们的单文本词汇频率/逆文本频率值(TF/IDF)。不难想象,和新闻主题有关的那些实词频率高,TF/IDF 值很大。我们按照这些实词在词汇表的位置对它们的 TF/IDF 值排序。比如,词汇表有六万四千个词,分别为
单词编号 汉字词
------------------
1 阿
2 啊
3 阿斗
4 阿姨
...
789 服装
....
64000 做作
在一篇新闻中,这 64,000 个词的 TF/IDF 值分别为
单词编号 TF/IDF 值
==============
1 0
2 0.0034
3 0
4 0.00052
5 0
...
789 0.034
...
64000 0.075
如果单词表中的某个次在新闻中没有出现,对应的值为零,那么这 64,000 个数,组成一个64,000维的向量。我们就用这个向量来代表这篇新闻,并成为新闻的特征向量。如果两篇新闻的特征向量相近,则对应的新闻内容相似,它们应当归在一类,反之亦然。
学过向量代数的人都知道,向量实际上是多维空间中有方向的线段。如果两个向量的方向一致,即夹角接近零,那么这两个向量就相近。而要确定两个向量方向是否一致,这就要用到余弦定理计算向量的夹角了。
余弦定理对我们每个人都不陌生,它描述了三角形中任何一个夹角和三个边的关系,换句话说,给定三角形的三条边,我们可以用余弦定理求出三角形各个角的角度。假定三角形的三条边为 a, b 和 c,对应的三个角为 A, B 和 C,那么角 A 的余弦
如果我们将三角形的两边 b 和 c 看成是两个向量,那么上述公式等价于
其中分母表示两个向量 b 和 c 的长度,分子表示两个向量的内积。举一个具体的例子,假如新闻 X 和新闻 Y 对应向量分别是
x1,x2,...,x64000 和
y1,y2,...,y64000,
那么它们夹角的余弦等于,
当两条新闻向量夹角的余弦等于一时,这两条新闻完全重复(用这个办法可以删除重复的网页);当夹角的余弦接近于一时,两条新闻相似,从而可以归成一类;夹角的余弦越小,两条新闻越不相关。
我们在中学学习余弦定理时,恐怕很难想象它可以用来对新闻进行分类。在这里,我们再一次看到数学工具的用途。
Posted at 09:21下午 七月 20, 2006 by Kaizen in General | 评论[1]
星期日 2006年04月02日
java Thread
在论坛上面常常看到初学者对线程的无可奈何,所以总结出了下面一篇文章,希望对一些正在学习使用java线程的初学者有所帮助。
首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多任务,分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的windows,linux就属于此列。什么是分时操作系统呢,通俗一点与就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还一边看网页呢?但实际上,并不上cpu在同时执行这些程序,cpu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在cpu的高速计算能力,给人的感觉就像是多个程序在同时执行一样。
一般可以在同一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行.(你可以将前面一句话的程序换成进程,进程是程序的一次执行过程,是系统运行程序的基本单位)
线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weight process).一个进程中可以包含多个线程.
多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个程序至少自动拥有一个线程,称为主线程.当程序加载到内存时,启动主线程.
[线程的运行机制以及调度模型]
java中多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。
下面是线程的机制图:
线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之一:
1.创建状态
使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)
2.可运行状态
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
3.运行中状态
Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正执行线程的run()方法.
4.阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)
5.死亡状态
线程结束后是死亡状态(Dead)
同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源.此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则.
线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.
线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行.先占式调度分为:独占式和分时方式.
独占方式下,当前执行线程将一直执行下去,直 到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其他可运行状态的线程执行
分时方式的系统使每个线程工作若干步,实现多线程同时运行
另外请注意下面的线程调度规则(如果有不理解,不急,往下看):
①如果两个或是两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的(Synchronized),如果对象更新影响到只读方法,那么只度方法也应该定义为同步的
②如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait()
③每当一个方法改变某个对象的状态的时候,它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看执行环境是否已发生改变
④记住wait(),notify(),notifyAll()方法属于Object类,而不是Thread类,仔细检查看是否每次执行wait()方法都有相应的notify()或notifyAll()方法,且它们作用与相同的对象 在java中每个类都有一个主线程,要执行一个程序,那么这个类当中一定要有main方法,这个man方法也就是java class中的主线程。你可以自己创建线程,有两种方法,一是继承Thread类,或是实现Runnable接口。一般情况下,最好避免继承,因为java中是单根继承,如果你选用继承,那么你的类就失去了弹性,当然也不能全然否定继承Thread,该方法编写简单,可以直接操作线程,适用于单重继承情况。至于选用那一种,具体情况具体分析。
eg.继承Thread
public class MyThread_1 extends Thread
{
public void run()
{
//some code
}
}
eg.实现Runnable接口
public class MyThread_2 implements Runnable
{
public void run()
{
//some code
}
}
当使用继承创建线程,这样启动线程:
new MyThread_1().start()
当使用实现接口创建线程,这样启动线程:
new Thread(new MyThread_2()).start()
注意,其实是创建一个线程实例,并以实现了Runnable接口的类为参数传入这个实例,当执行这个线程的时候,MyThread_2中run里面的代码将被执行。
下面是完成的例子:
public class MyThread implements Runnable
{
public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
new Thread(new MyThread()).start();
}
}
执行后将打印出:
My Name is Thread-0
你也可以创建多个线程,像下面这样
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
那么会打印出:
My Name is Thread-0
My Name is Thread-1
My Name is Thread-2
看了上面的结果,你可能会认为线程的执行顺序是依次执行的,但是那只是一般情况,千万不要用以为是线程的执行机制;影响线程执行顺序的因素有几点:首先看看前面提到的优先级别
public class MyThread implements Runnable
{
public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级
t1.start();
t2.start();
t3.start();
}
}
再看看结果:
My Name is Thread-1
My Name is Thread-0
My Name is Thread-2
线程的优先级分为10级,分别用1到10的整数代表,默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10)
然后是线程程序本身的设计,比如使用sleep,yield,join,wait等方法(详情请看JDKDocument)
public class MyThread implements Runnable
{
public void run()
{
try
{
int sleepTime=(int)(Math.random()*100);//产生随机数字,
Thread.currentThread().sleep(sleepTime);//让其休眠一定时间,时间又上面sleepTime决定
//public static void sleep(long millis)throw InterruptedException (API)
System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);
}catch(InterruptedException ie)//由于线程在休眠可能被中断,所以调用sleep方法的时候需要捕捉异常
{
ie.printStackTrace();
}
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t1.start();
t2.start();
t3.start();
}
}
执行后观察其输出:
Thread-0 睡了 11
Thread-2 睡了 48
Thread-1 睡了 69
上面的执行结果是随机的,再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句,当线程休眠的时候就会让出cpu,cpu将会选择执行处于runnable状态中的其他线程,当然也可能出现这种情况,休眠的Thread立即进入了runnable状态,cpu再次执行它。
[线程组概念]
线程是可以被组织的,java中存在线程组的概念,每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等.Java的线程组由java.lang包中的Thread——Group类实现.
ThreadGroup类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等.线程组还可以包含线程组.在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之间的树状继承关系。像上面创建的线程都是属于main这个线程组的。
借用上面的例子,main里面可以这样写:
public static void main(String[] args)
{
/***************************************
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent, String name)
*****************************************/
ThreadGroup group1=new ThreadGroup("group1");
ThreadGroup group2=new ThreadGroup(group1,"group2");
Thread t1=new Thread(group2,new MyThread());
Thread t2=new Thread(group2,new MyThread());
Thread t3=new Thread(group2,new MyThread());
t1.start();
t2.start();
t3.start();
}
线程组的嵌套,t1,t2,t3被加入group2,group2加入group1。
另外一个比较多就是关于线程同步方面的,试想这样一种情况,你有一笔存款在银行,你在一家银行为你的账户存款,而你的妻子在另一家银行从这个账户提款,现在你有1000块在你的账户里面。你存入了1000,但是由于另一方也在对这笔存款进行操作,人家开始执行的时候只看到账户里面原来的1000元,当你的妻子提款1000元后,你妻子所在的银行就认为你的账户里面没有钱了,而你所在的银行却认为你还有2000元。
看看下面的例子:
class BlankSaving //储蓄账户
{
private static int money=10000;
public void add(int i)
{
money=money+i;
System.out.println("Husband 向银行存入了 [¥"+i+"]");
}
public void get(int i)
{
money=money-i;
System.out.println("Wife 向银行取走了 [¥"+i+"]");
if(money<0)
System.out.println("余额不足!");
}
public int showMoney()
{
return money;
}
}
class Operater implements Runnable
{
String name;
BlankSaving bs;
public Operater(BlankSaving b,String s)
{
name=s;
bs=b;
}
public static void oper(String name,BlankSaving bs)
{
if(name.equals("husband"))
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.add(1000);
}
}catch(InterruptedException e){}
}else
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.get(1000);
}
}catch(InterruptedException e){}
}
}
public void run()
{
oper(name,bs);
}
}
public class BankTest
{
public static void main(String[] args)throws InterruptedException
{
BlankSaving bs=new BlankSaving();
Operater o1=new Operater(bs,"husband");
Operater o2=new Operater(bs,"wife");
Thread t1=new Thread(o1);
Thread t2=new Thread(o2);
t1.start();
t2.start();
Thread.currentThread().sleep(500);
}
}
下面是其中一次的执行结果:
-----------first--------------
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
看到了吗,这可不是正确的需求,在husband还没有结束操作的时候,wife就插了进来,这样很可能导致意外的结果。解决办法很简单,就是将对数据进行操作方法声明为synchronized,当方法被该关键字声明后,也就意味着,如果这个数据被加锁,只有一个对象得到这个数据的锁的时候该对象才能对这个数据进行操作。也就是当你存款的时候,这笔账户在其他地方是不能进行操作的,只有你存款完毕,银行管理人员将账户解锁,其他人才能对这个账户进行操作。
修改public static void oper(String name,BlankSaving bs)为public static synchronized void oper(String name,BlankSaving bs),再看看结果:
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
当丈夫完成操作后,妻子才开始执行操作,这样的话,对共享对象的操作就不会有问题了。
[wait and notify]
你可以利用这两个方法很好的控制线程的执行流程,当线程调用wait方法后,线程将被挂起,直到被另一线程唤醒(notify)或则是如果wait方法指定有时间得话,在没有被唤醒的情况下,指定时间时间过后也将自动被唤醒。但是要注意一定,被唤醒并不是指马上执行,而是从组塞状态变为可运行状态,其是否运行还要看cpu的调度。
事例代码:
class MyThread_1 extends Thread
{
Object lock;
public MyThread_1(Object o)
{
lock=o;
}
public void run()
{
try
{
synchronized(lock)
{
System.out.println("Enter Thread_1 and wait");
lock.wait();
System.out.println("be notified");
}
}catch(InterruptedException e){}
}
}
class MyThread_2 extends Thread
{
Object lock;
public MyThread_2(Object o)
{
lock=o;
}
public void run()
{
synchronized(lock)
{
System.out.println("Enter Thread_2 and notify");
lock.notify();
}
}
}
public class MyThread
{
public static void main(String[] args)
{
int[] in=new int[0];//notice
MyThread_1 t1=new MyThread_1(in);
MyThread_2 t2=new MyThread_2(in);
t1.start();
t2.start();
}
}
执行结果如下:
Enter Thread_1 and wait
Enter Thread_2 and notify
Thread_1 be notified
可能你注意到了在使用wait and notify方法得时候我使用了synchronized块来包装这两个方法,这是由于调用这两个方法的时候线程必须获得锁,也就是上面代码中的lock[],如果你不用synchronized包装这两个方法的得话,又或则锁不一是同一把,比如在MyThread_2中synchronized(lock)改为synchronized(this),那么执行这个程序的时候将会抛出java.lang.IllegalMonitorStateException执行期异常。另外wait and notify方法是Object中的,并不在Thread这个类中。最后你可能注意到了这点:int[] in=new int[0];为什么不是创建new Object而是一个0长度的数组,那是因为在java中创建一个0长度的数组来充当锁更加高效。
Thread作为java中一重要组成部分,当然还有很多地方需要更深刻的认识,上面只是对Thread的一些常识和易错问题做了一个简要的总结,若要真正的掌握java的线程,还需要自己多做总结
Posted at 06:44上午 四月 02, 2006 by Kaizen in Java | 评论[5]
星期四 2006年03月09日
Builder model [Jdon]
Builder模式定义:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.
Builder模式是一步一步创建一个复杂的对象,它允许用户可以只通过指定复杂对象的类型和内容就可以构建它们.用户不知道内部的具体构建细节.Builder模式是非常类似抽象工厂模式,细微的区别大概只有在反复使用中才能体会到.
为何使用?
是为了将构建复杂对象的过程和它的部件解耦.注意: 是解耦过程和部件.
因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮 方向盘 发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开.
如何使用?
首先假设一个复杂对象是由多个部件组成的,Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示.
首先,需要一个接口,它定义如何创建复杂对象的各个部件:
public interface Builder {
//创建部件A 比如创建汽车车轮
void buildPartA();
//创建部件B 比如创建汽车方向盘
void buildPartB();
//创建部件C 比如创建汽车发动机
void buildPartC();
//返回最后组装成品结果 (返回最后装配好的汽车)
//成品的组装过程不在这里进行,而是转移到下面的Director类中进行.
//从而实现了解耦过程和部件
Product getResult();
}
用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:
public class Director {
private Builder builder;
public Director( Builder builder ) {
this.builder = builder;
}
// 将部件partA partB partC最后组成复杂对象
//这里是将车轮 方向盘和发动机组装成汽车的过程
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
Builder的具体实现ConcreteBuilder:
通过具体完成接口Builder来构建或装配产品的部件;
定义并明确它所要创建的是什么具体东西;
提供一个可以重新获取产品的接口:
public class ConcreteBuilder implements Builder {
Part partA, partB, partC;
public void buildPartA() {
//这里是具体如何构建partA的代码
};
public void buildPartB() {
//这里是具体如何构建partB的代码
};
public void buildPartC() {
//这里是具体如何构建partB的代码
};
public Product getResult() {
//返回最后组装成品结果
};
}
复杂对象:产品Product:
public interface Product { }
复杂对象的部件:
public interface Part { }
我们看看如何调用Builder模式:
ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();
Builder模式的应用
在Java实际使用中,我们经常用到"池"(Pool)的概念,当资源提供者无法提供足够的资源,并且这些资源需要被很多用户反复共享时,就需要使用池.
"池"实际是一段内存,当池中有一些复杂的资源的"断肢"(比如数据库的连接池,也许有时一个连接会中断),如果循环再利用这些"断肢",将提高内存使用效率,提高池的性能.修改Builder模式中Director类使之能诊断"断肢"断在哪个部件上,再修复这个部件.
Posted at 08:28下午 三月 09, 2006 by Kaizen in General | 评论[0]
GoF设计模式[转自J道]
GoF设计模式
A.创建模式
设计模式之Factory(工厂模式)
使用工厂模式就象使用new一样频繁.2002/10/9更新
设计模式之Prototype(原型模式)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
设计模式之Builder
汽车由车轮 方向盘 发动机很多部件组成,同时,将这些部件组装成汽车也是一件复杂的工作,Builder模式就是将这两种情况分开进行。
设计模式之Singleton(单态模式)
保证一个类只有一个实例,并提供一个访问它的全局访问点 2002/10/9更新
B.结构模式
设计模式之Facade
可扩展的使用JDBC针对不同的数据库编程,Facade提供了一种灵活的实现.
设计模式之Proxy
以Jive为例,剖析代理模式在用户级别授权机制上的应用
设计模式之Adapter
使用类再生的两个方式:组合(new)和继承(extends),这个已经在"thinking in java"中提到过.
设计模式之Composite
就是将类用树形结构组合成一个单位.你向别人介绍你是某单位,你是单位中的一个元素,别人和你做买卖,相当于和单位做买卖。文章中还对Jive再进行了剖析。
设计模式之Decorator
Decorator是个油漆工,给你的东东的外表刷上美丽的颜色.
设计模式之Bridge
将"牛郎织女"分开(本应在一起,分开他们,形成两个接口),在他们之间搭建一个桥(动态的结合)
设计模式之Flyweight
提供Java运行性能,降低小而大量重复的类的开销.
C.行为模式
设计模式之Template
实际上向你介绍了为什么要使用Java 抽象类,该模式原理简单,使用很普遍.
设计模式之Memento
很简单一个模式,就是在内存中保留原来数据的拷贝.
设计模式之Observer
介绍如何使用Java API提供的现成Observer
设计模式之Chain of Responsibility
各司其职的类串成一串,好象击鼓传花,当然如果自己能完成,就不要推委给下一个.
设计模式之Command
什么是将行为封装,Command是最好的说明.
设计模式之State
状态是编程中经常碰到的实例,将状态对象化,设立状态变换器,便可在状态中轻松切换.
设计模式之Strategy
不同算法各自封装,用户端可随意挑选需要的算法.
设计模式之Mediator
Mediator很象十字路口的红绿灯,每个车辆只需和红绿灯交互就可以.
设计模式之Interpreter
主要用来对语言的分析,应用机会不多.
设计模式之Visitor
访问者在进行访问时,完成一系列实质性操作,而且还可以扩展.
设计模式之Iterator
这个模式已经被整合入Java的Collection.在大多数场合下无需自己制造一个Iterator,只要将对象装入Collection中,直接使用Iterator进行对象遍历。
Posted at 05:01下午 三月 09, 2006 by Kaizen in General | 评论[0]
星期二 2006年02月21日
java回调(网摘)
回调的定义:
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回, 它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。
一般在 API 中使用回调。
回调允许调用者主动调用客户程序的代码。
它的触发机制类似抛出事件。
举个例子:当你要系统枚举系统中的所有窗口,使用回调后,系统每找到一个窗口就把控制权交给你,由你的回调程序处理这个窗口,这样大大简化了程序。
再比如:一个端口监听程序,如果使用回调,当有数据时,才需要运行主程序的代码。而不用回调,则主程序每隔一定时间都要去判断是否有数据到达,程序不但复杂,而且浪费资源。
回调程序在API的地位相当于DOS的中断处理程序。
回调程序的实质是提供一个参数作为回调函数的入口地址。由调用的函数根据地址反过来调用客户程序。
下面是John D. Mitchell的举例说明:
在MS-Windows或者X-Window系统的事件驱动模型中,当某些事件发生的时候,开发人员已经熟悉通过传递函数指针来调用处理方法。而在Java的面向对象的模型中,不能支持这种方法,因而看起来好像排除了使用这种比较舒服的机制,但事实并非如此。
Java的接口提供了一种很好的机制来让我们达到和回调相同的效果。这个诀窍就在于定一个简单的接口,在接口之中定义一个我们希望调用的方法。
举个例子来说,假设当一个事件发生的时候,我们想它被通知,那么我们定义一个接口:
|
public interface InterestingEvent { // This is just a regular method so it can return something or // take arguments if you like. public void interestingEvent (); } |
这就给我们一个控制实现了该接口的所有类的对象的控制点。因此,我们不需要关心任何和自己相关的其它外界的类型信息。这种方法比C函数更好,因为在C++风格的代码中,需要指定一个数据域来保存对象指针,而Java中这种实现并不需要。
发出事件的类需要对象实现InterestingEvent接口,然后调用接口中的interestingEvent ()方法。
|
public class EventNotifier { private InterestingEvent ie; private boolean somethingHappened; public EventNotifier (InterestingEvent event) { // Save the event object for later use. ie = event; // Nothing to report yet. somethingHappened = false; } //... public void doWork () { // Check the predicate, which is set elsewhere. if (somethingHappened) { // Signal the even by invoking the interface's method. ie.interestingEvent (); } //... } // ... } |
在这个例子中,我们使用了somethingHappened这个标志来跟踪是否事件应该被激发。在许多事例中,被调用的方法能够激发interestingEvent()方法才是正确的。
希望收到事件通知的代码必须实现InterestingEvent接口,并且正确的传递自身的引用到事件通知器。
|
public class CallMe implements InterestingEvent { private EventNotifier en; public CallMe () { // Create the event notifier and pass ourself to it. en = new EventNotifier (this); } public void interestingEvent () { // Wow! Something really interesting must have occurred! // Do something... } //... } |
Posted at 09:26上午 二月 21, 2006 by Kaizen in General | 评论[2]
星期二 2006年01月10日
更好的办法
在论坛上面发现的一个问题,下面是我的解决方法,但是不够安全高效。
import javax.swing.*;
public class Progress extends JFrame
{
JProgressBar pb=new JProgressBar();
int count=0;
class Task extends java.util.TimerTask
{
public void run()
{
pb.setValue(count++);//执行的时候为JProgressBar赋值
}
}
下面是一个网友的更好的办法:
不能用TimerTask,存取Swing组件的内容必须考虑线程问题。
最好用Swing中的Timer。在事件处理线程中直接执行
javax.swing.Timer timer=new Timer(200,new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
pb.setValue(count++);
}
});
Posted at 03:25下午 一月 10, 2006 by Kaizen in Java | 评论[0]
星期六 2006年01月07日
JTable 显示图片
今天在matrix上面看到的一个关于jtable显示图片的问题,以前我也没有实现过,今天在网上查找了半天,终于是有所了解。原来最基础的AbstractTableModel的getColumnClass不管你列中的类是什么都返回Object.class,而DefaultTableModel并没有覆盖这个方法,JTable中的DefaultTableCellRenderer对Object类的显示处理就是取Object.toString,然后将内容显示,所以不处理的话,程序将会显示图片路径.
所以需要继承AbstractTableModel 并覆写getColumnClass()
public Class getColumnClass(int c) //关键所在,取得对象类
{
return getValueAt(0, c).getClass();
}
Posted at 02:00下午 一月 07, 2006 by Kaizen in General | 评论[1]
星期二 2005年12月20日
尽量减少模块间的耦合程度
[Effective Java]
要想区别一个设计良好的模块与一个设计不好的模块,最重要的因素是,这个模块对于外部的其他模块而言,是否隐藏了内部的数据和其他的实现细节。
上面的关于细节隐藏的概念既是:封装(encapsulation)/信息隐藏(information hiding)
社会分工越来越细是必然的,在软件开发的过程中也是一样,现在为了提高软件的开发效率,就必须使各个模块间的耦合程度尽可能的减小,解除了耦合关系的模块可以被独立的开发、测试、优化、使用、理解和修改,其主要目的是为了达到并行开发,提高项目开发速度。在java中由访问控制来决定类、接口和成员的可访问性。你需要对你开发的类、接口负责,如果你开发的是一个公有的类或接口,你在改写它的时候将要考虑每一个使用这个类的或接口的用户,然而如果你的类或接口是私有的,那么你进行修改,替换甚至去处都不用担心会伤害到现有的用户。
[EffectiveJava]
1.经验表明:你应该尽可能地使每一个类或成员不被外界访问。
2.具有公有的静态final数组域几乎总是错的。
总结:
总是尽可能地降低可访问性。在设计了一个最小的公有API之后,应该防止把任何杂散的类、接口和成员变成API的一部分。除了公有静态final域的特殊情况外,公有类不应该包含公有域。并确保公有静态final域所引用的对象是不可变的。
Posted at 10:35下午 十二月 20, 2005 by Kaizen in Java | 评论[0]
星期日 2005年12月11日
Comparable问题
code的时候发现了这样一个问题,很细小但是却值得重视。问题是发生在实现compareTo的时候,规则我还是比较清楚,自反性,对称性,传递性和非空性,不过缺漏了一点!我写一个class,并在将其产生的n个Instance add到一个TreeSet后却发现这个TreeSet里面只有一个Instance!但是将TreeSet换成HashSet后,n个Instance全都添加进去了,why?解释只有一个,在TreeSet和HashSet里面比较对象所使用的方法是不一样的。
后面我又在Effective Java上面找到了答案——“有序集合使用了由compareTo方法而不是equlas方法施加的相等测试”,上面虽然说了这样的情况不会出现灾难性的后果,但是我想尽量避免出现这样的情况是必要的。
不知道有没有人也遇到过这个问题,却还不清楚到底怎么回事或是根本没有发现,希望下面的例子大家看了后有点收获;
CODE
import java.util.*;
class ComMem implements Comparable
{
int id;
int count;
public ComMem(int id,int count)
{
this.id=id;
this.count=count;
}
public int compareTo(Object o)
{
ComMem cm=(ComMem)o;
int result=this.id-cm.id;//将其注释,去掉下面的注释再看看执行结果
//int result=this.count-cm.count;
return result;
}
public boolean equals(Object o)
{
ComMem cm=(ComMem)o;
if(this.count==cm.count)
return true;
else
return false;
}
public int hashCode()
{
return count;
}
public String toString()
{
return "(id:"+id+"|count:"+count+")";
}
}
public class CompareTo
{
public static void main(String[] args)
{
ComMem cm1=new ComMem(1,1);
ComMem cm2=new ComMem(1,2);
ComMem cm3=new ComMem(1,3);
ComMem cm4=new ComMem(1,4);
ComMem cm5=new ComMem(1,5);
TreeSet ts=new TreeSet();
ts.add(cm1);
ts.add(cm2);
ts.add(cm3);
ts.add(cm4);
ts.add(cm5);
System.out.println("TreeSet ts: "+ts);//只添加进了一个cm1
HashSet hs=new HashSet();
hs.add(cm1);
hs.add(cm2);
hs.add(cm3);
hs.add(cm4);
hs.add(cm5);
System.out.println("HashSet hs: "+hs);//全都添加了进来
}
}
要是我的解释还不够清楚,可以看看Effective Java的11条。
最后强调一点,“基础很重要”,不要在专研J2EE的时候或是大谈struct、spring的时候却忘记了如何合理的重写一个equals。
Posted at 10:13下午 十二月 11, 2005 by Kaizen in Java | 评论[0]
星期五 2005年11月25日
爱因斯坦谜题
最近喜欢上了做智力题,所以收集了不少这方面的题目,其中爱因斯坦谜题是非常有趣的一道。
题目:
在一条街上,有5座房子,喷了5种颜色。每个房里住着不同国籍的人。每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物。
问题是:谁养鱼?
提示:
1、英国人住红色房子
2、瑞典人养狗
3、丹麦人喝茶
4、绿色房子在白色房子左面
5、绿色房子主人喝咖啡
6、抽PallMall香烟的人养鸟
7、黄色房子主人抽Dunhill香烟
8、住在中间房子的人喝牛奶
9、挪威人住第一间房
10、抽Blends香烟的人住在养猫的人隔壁
11、养马的人住抽Dunhill香烟的人隔壁
12、抽BlueMaster的人喝啤酒
13、德国人抽Prince香烟
14、挪威人住蓝色房子隔壁
15、抽Blends香烟的人有一个喝水的邻居
网络上面对这道题目大肆吹捧,说全世界只有百分之几的人能做出来,其实也没有那么难,只要作题的时候能大胆的猜想。这道题目还是比较好解决的。作题时,我在岔口上面错了两次,改成后很快答案就出来了。不能说只有这一种答案,但是这种一定是对的。人的逻辑思维能力是很强的,但是计算能力赶计算机就差了不少,所以如果使用计算机高速的计算能力对这道题目进行暴力“破解”也是一种不错的办法。
下面就是这道题目的java版解法:
EinsteinQuestion CODE
public class EinsteinQuestion
{
/****************************************
*声明数组,每一个数组对应题目中的一种元素
*****************************************/
int[] nation=new int[5];
int[] color=new int[5];
int[] drink=new int[5];
int[] cigarette=new int[5];
int[] pet=new int[5];
/*************************************************
[1] [2] [3] [4] [5]
nation Norway Danmark Germany England Sweden
color yellow blue green red white
drink water beer milk tea coffee
cigare PallMall Dunhill Blends BlueMaster Prince
pet cat bird fish horse dog
**************************************************/
/******************************************************
将1到5进行全排列,并将其全部排列存储到一个二维数组里面
*******************************************************/
public int[][] getArrange()
{
int temp[]=new int[5];
int[][] arrange=new int[120][5];
int count=0;
int k1=0;
while(5-k1>0)
{
for(int i=0;i<5;i++)
temp[i]=0;
k1++;
for(int i=0,j=0;i<5;i++)
{
if(temp[i]==0){j++;if(j==k1){temp[i]=1;i=5;}}
}
int k2=0;
while(4-k2>0)
{
for(int i=0;i<5;i++)
if(temp[i]!=1)
temp[i]=0;
k2++;
for(int i=0,j=0;i<5;i++)
{
if(temp[i]==0){j++;if(j==k2){temp[i]=2;i=5;}}
}
int k3=0;
while(3-k3>0)
{
for(int i=0;i<5;i++)
if(temp[i]==3||temp[i]==4||temp[i]==5)
temp[i]=0;
k3++;
for(int i=0,j=0;i<5;i++)
{
if(temp[i]==0){j++;if(j==k3){temp[i]=3;i=5;}}
}
int k4=0;
while(2-k4>0)
{
for(int i=0;i<5;i++)
if(temp[i]==4||temp[i]==5)
temp[i]=0;
k4++;
for(int i=0,j=0;i<5;i++)
{
if(temp[i]==0){j++;if(j==k4){temp[i]=4;i=5;}}
}
for(int i=0;i<5;i++)
if(temp[i]==0){temp[i]=5;i=5;}
for(int i=0;i<5;i++)
arrange[count][i]=temp[i];
count++;
}
}
}
}
return arrange;
}
/***********************************
将上面得到的二维数组的元素进行组合,
然后进行暴力计算,在组合的过程总利用
给出的已知条件排除不符合的项目。
***********************************/
public void resolve()
{
int[][] all=getArrange();
int p1=0;
while(120-p1>0)
{
if(all[p1][0]!=1){p1++;continue;}
for(int i=0;i<5;i++)
nation[i]=all[p1][i];
p1++;
int p2=0;
/*************************
*进行组合破解
****************************/
while(120-p2>0)
{
if(all[p2][1]!=2){p2++;continue;}
for(int i=0;i<5;i++)
color[i]=all[p2][i];
p2++;
int p3=0;
while(120-p3>0)
{
if(all[p3][2]!=3){p3++;continue;}
for(int i=0;i<5;i++)
drink[i]=all[p3][i];
p3++;
int p4=0;
while(120-p4>0)
{
for(int i=0;i<5;i++)
cigarette[i]=all[p4][i];
p4++;
int p5=0;
while(120-p5>0)
{
for(int i=0;i<5;i++)
pet[i]=all[p5][i];
p5++;
/*********************************
将得出的结果与以知条件做比较,若是
不符合,则终止程序向下进行,返回到上面
的循环
***********************************/
&