架构师面试笔试题_第1页
架构师面试笔试题_第2页
架构师面试笔试题_第3页
架构师面试笔试题_第4页
架构师面试笔试题_第5页
已阅读5页,还剩122页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

JDK1.7新增的功能,1.6和1.7的不同之处首先是模块化特性:现在的Java7也是采用了模块的划分方式来提速,一些不是必须的模块并没有下载和安装,因此在使用全新的Java7的虚拟机的时候会发现真的很快,当虚拟机需要用到某些功能的时候,再下载和启用相应的模块,这样使得最初需要下载的虚拟机大小得到了有效的控制。同时对启动速度也有了很大的改善。如果你对OpenJDK的架构比拟熟悉,你甚至可以定制JDK的模块。其次是多语言支持:这里的多语言不是指中文英文之类的语言,而是说Java7的虚拟机对多种动态程序语言增加了支持,比方:Rubby、Python等等。对这些动态语言的支持极大地扩展了Java虚拟机的能力。对于那些熟悉这些动态语言的程序员而言,在使用Java虚拟机的过程中同样可以使用它们熟悉的语言进行功能的编写,而这些语言是跑在功能强大的JVM之上的。再有是开发者的开发效率得到了改善:Java7通过多种特性来增强开发效率。比方对语言本身做了一些细小的改变来简化程序的编写,在多线程并发与控制方面:轻量级的别离与合并框架,一个支持并发访问的HashMap等等。通过注解增强程序的静态检查。提供了一些新的API用于文件系统的访问、异步的输入输出操作、Socket通道的配置与绑定、多点数据包的传送等等。最后是执行效率的提高,也是给人感觉最真切体验的特性:压缩了64位的对象指针,Java7通过对对象指针由64位压缩到与32位指针相匹配的技术使得内存和内存带块的消耗得到了很大的降低因而提高了执行效率。此外还提供了新的垃圾回收机制〔G1〕来降低垃圾回收的负载和增强垃圾回收的效果。G1垃圾回收机制拥有更低的暂停率和更好的可预测性。字符流和字节流的区别,使用场景,相关类Java流在处理上分为字符流和字节流。字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。Java内用Unicode编码存储字符,字符流处理类负责将外部的其他编码的字符流和java内Unicode字符流之间的转换。而类InputStreamReader和OutputStreamWriter处理字符流和字节流的转换。字符流〔一次可以处理一个缓冲区〕一次操作比字节流〔一次一个字节〕效率高。(一)以字节为导向的streamInputStream/OutputStreamInputStream和OutputStream是两个abstact类,对于字节为导向的stream都扩展这两个鸡肋〔基类^_^〕;1、InputStream1.1ByteArrayInputStream--把内存中的一个缓冲区作为InputStream使用.construct(A)ByteArrayInputStream(byte[])创立一个新字节数组输入流〔ByteArrayInputStream〕,它从指定字节数组中读取数据〔使用byte作为其缓冲区数组〕(B)ByteArrayInputStream(byte[],int,int)创立一个新字节数组输入流,它从指定字节数组中读取数据。mark::该字节数组未被复制。1.2StringBufferInputStream--把一个String对象作为InputStream.constructStringBufferInputStream(String)据指定串创立一个读取数据的输入流串。注释:不推荐使用StringBufferInputStream方法。此类不能将字符正确的转换为字节。同JDK1.1版中的类似,从一个串创立一个流的最正确方法是采用StringReader类。1.3FileInputStream--把一个文件作为InputStream,实现对文件的读取操作construct(A)FileInputStream(Filename)创立一个输入文件流,从指定的File对象读取数据。(B)FileInputStream(FileDescriptor)创立一个输入文件流,从指定的文件描述器读取数据。(C)-FileInputStream(String

name)创立一个输入文件流,从指定名称的文件读取数据。methodread()从当前输入流中读取一字节数据。read(byte[])将当前输入流中b.length个字节数据读到一个字节数组中。read(byte[],int,int)将输入流中len个字节数据读入一个字节数组中。1.4PipedInputStream:实现了pipe的概念,主要在线程中使用.管道输入流是指一个通讯管道的接收端。一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯。constructPipedInputStream()创立一个管道输入流,它还未与一个管道输出流连接。PipedInputStream(PipedOutputStream)创立一个管道输入流,它已连接到一个管道输出流。1.5SequenceInputStream:把多个InputStream合并为一个InputStream.“序列输入流”类允许应用程序把几个输入流连续地合并起来,并且使它们像单个输入流一样出现。每个输入流依次被读取,直到到达该流的末尾。然后“序列输入流”类关闭这个流并自动地切换到下一个输入流。constructSequenceInputStream(Enumeration)创立一个新的序列输入流,并用指定的输入流的枚举值初始化它。SequenceInputStream(InputStream,InputStream)创立一个新的序列输入流,初始化为首先读输入流s1,然后读输入流s2。2、OutputSteam

2.1ByteArrayOutputStream:把信息存入内存中的一个缓冲区中.该类实现一个以字节数组形式写入数据的输出流。当数据写入缓冲区时,它自动扩大。用toByteArray()和toString()能检索数据。constructor(A)ByteArrayOutputStream()创立一个新的字节数组输出流。(B)ByteArrayOutputStream()创立一个新的字节数组输出流。(C)ByteArrayOutputStream(int)创立一个新的字节数组输出流,并带有指定大小字节的缓冲区容量。toString(String)根据指定字符编码将缓冲区内容转换为字符串,并将字节转换为字符。write(byte[],int,int)将指定字节数组中从偏移量off开始的len个字节写入该字节数组输出流。write(int)将指定字节写入该字节数组输出流。writeTo(OutputStream)用out.write(buf,0,count)调用输出流的写方法将该字节数组输出流的全部内容写入指定的输出流参数。2.2

FileOutputStream:文件输出流是向File或FileDescriptor输出数据的一个输出流。constructor(A)FileOutputStream(File

name)创立一个文件输出流,向指定的File对象输出数据。(B)FileOutputStream(FileDescriptor)创立一个文件输出流,向指定的文件描述器输出数据。(C)FileOutputStream(String

name)创立一个文件输出流,向指定名称的文件输出数据。(D)FileOutputStream(String,boolean)用指定系统的文件名,创立一个输出文件。2.3PipedOutputStream:管道输出流是指一个通讯管道的发送端。一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯。constructor(A)PipedOutputStream()创立一个管道输出流,它还未与一个管道输入流连接。(B)PipedOutputStream(PipedInputStream)创立一个管道输出流,它已连接到一个管道输入流。(二)以字符为导向的streamReader/Writer以Unicode字符为导向的stream,表示以Unicode字符为单位从stream中读取或往stream中写入信息。Reader/Writer为abstact类以Unicode字符为导向的stream包括下面几种类型:1.Reader1.1

CharArrayReader:与ByteArrayInputStream对应此类实现一个可用作字符输入流的字符缓冲区constructorCharArrayReader(char[])用指定字符数组创立一个CharArrayReader。CharArrayReader(char[],int,int)用指定字符数组创立一个CharArrayReader1.2StringReader:与StringBufferInputStream对应其源为一个字符串的字符流。StringReader(String)创立一新的串读取者。1.3FileReader:与FileInputStream对应1.4PipedReader:与PipedInputStream对应2.

Writer2.1

CharArrayWrite:与ByteArrayOutputStream对应2.2

StringWrite:无与之对应的以字节为导向的stream2.3

FileWrite:与FileOutputStream对应2.4

PipedWrite:与PipedOutputStream对应3、两种不同导向的stream之间的转换3.1InputStreamReader和OutputStreamReader:把一个以字节为导向的stream转换成一个以字符为导向的stream。InputStreamReader类是从字节流到字符流的桥梁:它读入字节,并根据指定的编码方式,将之转换为字符流。使用的编码方式可能由名称指定,或平台可接受的缺省编码方式。InputStreamReader的read()方法之一的每次调用,可能促使从根本字节输入流中读取一个或多个字节。为了到达更高效率,考虑用BufferedReader封装InputStreamReader,BufferedReaderin=newBufferedReader(newInputStreamReader(System.in));例如://实现从键盘输入一个整数viewplaincopytoclipboardprint?

Strings=null;

InputStreamReaderre=newInputStreamReader(System.in);

BufferedReaderbr=newBufferedReader(re);

try{

s=br.readLine();

System.out.println("s="+Integer.parseInt(s));

br.close();

}

catch(IOExceptione)

{

e.printStackTrace();

}

catch(NumberFormatExceptione)//当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

{

System.out.println("输入的不是数字");

}

Strings=null;

InputStreamReaderre=newInputStreamReader(System.in);

BufferedReaderbr=newBufferedReader(re);

try{

s=br.readLine();

System.out.println("s="+Integer.parseInt(s));

br.close();

}

catch(IOExceptione)

{

e.printStackTrace();

}

catch(NumberFormatExceptione)//当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

{

System.out.println("输入的不是数字");

}InputStreamReader(InputStream)用缺省的字符编码方式,创立一个InputStreamReader。InputStreamReader(InputStream,String)用已命名的字符编码方式,创立一个InputStreamReader。OutputStreamWriter将多个字符写入到一个输出流,根据指定的字符编码将多个字符转换为字节。每个OutputStreamWriter合并它自己的CharToByteConverter,因而是从字符流到字节流的桥梁。〔三〕JavaIO的一般使用原那么:一、按数据来源〔去向〕分类:1、是文件:FileInputStream,FileOutputStream,(字节流)FileReader,FileWriter(字符)2、是byte[]:ByteArrayInputStream,ByteArrayOutputStream(字节流)3、是Char[]:CharArrayReader,CharArrayWriter(字符流)4、是String:StringBufferInputStream,StringBufferOuputStream(字节流)StringReader,StringWriter(字符流)5、网络数据流:InputStream,OutputStream,(字节流)Reader,Writer(字符流)二、按是否格式化输出分:1、要格式化输出:PrintStream,PrintWriter三、按是否要缓冲分:1、要缓冲:BufferedInputStream,BufferedOutputStream,(字节流)BufferedReader,BufferedWriter(字符流)四、按数据格式分:1、二进制格式〔只要不能确定是纯文本的〕:InputStream,OutputStream及其所有带Stream结束的子类2、纯文本格式〔含纯英文与汉字或其他编码方式〕;Reader,Writer及其所有带Reader,Writer的子类五、按输入输出分:1、输入:Reader,InputStream类型的子类2、输出:Writer,OutputStream类型的子类六、特殊需要:1、从Stream到Reader,Writer的转换类:InputStreamReader,OutputStreamWriter2、对象输入输出:ObjectInputStream,ObjectOutputStream3、进程间通信:PipeInputStream,PipeOutputStream,PipeReader,PipeWriter4、合并输入:SequenceInputStream5、更特殊的需要:PushbackInputStream,PushbackReader,LineNumberInputStream,LineNumberReader决定使用哪个类以及它的构造进程的一般准那么如下〔不考虑特殊需要〕:首先,考虑最原始的数据格式是什么:原那么四第二,是输入还是输出:原那么五第三,是否需要转换流:原那么六第1点第四,数据来源〔去向〕是什么:原那么一第五,是否要缓冲:原那么三〔特别注明:一定要注意的是readLine()是否有定义,有什么比read,write更特殊的输入或输出方法〕第六,是否要格式化输出:原那么二线程平安的概念,实现线程平安的几种方法

Java编程语言为编写多线程应用程序提供强大的语言支持。但是,编写有用的、没有错误的多线程程序仍然比拟困难。本文试图概述几种方法,程序员可用这几种方法来创立高效的线程平安类。并发性只有当要解决的问题需要一定程度的并发性时,程序员才会从多线程应用程序中受益。例如,如果打印队列应用程序仅支持一台打印机和一台客户机,那么不应该将它编写为多线程的。一般说来,包含并发性的编码问题通常都包含一些可以并发执行的操作,同时也包含一些不可并发执行的操作。例如,为多个客户机和一个打印机提供效劳的打印队列可以支持对打印的并发请求,但向打印机的输出必须是串行形式的。多线程实现还可以改善交互式应用程序的响应时间。Synchronized关键字虽然多线程应用程序中的大多数操作都可以并行进行,但也有某些操作〔如更新全局标志或处理共享文件〕不能并行进行。在这些情况下,必须获得一个锁来防止其他线程在执行此操作的线程完成之前访问同一个方法。在Java程序中,这个锁是通过synchronized关键字提供的。清单1说明了它的用法。清单1.使用synchronized关键字来获取锁publicclassMaxScore{intmax;publicMaxScore(){max=0;}publicsynchronizedvoidcurrentScore(ints){if(s>max){max=s;}}publicintmax(){returnmax;}}这里,两个线程不能同时调用currentScore()方法;当一个线程工作时,另一个线程必须阻塞。但是,可以有任意数量的线程同时通过max()方法访问最大值,因为max()不是同步方法,因此它与锁定无关。试考虑在MaxScore类中添加另一个方法的影响,该方法的实现如清单2所示。清单2.添加另一个方法publicsynchronizedvoidreset(){max=0;}这个方法〔当被访问时〕不仅将阻塞reset()方法的其他调用,而且也将阻塞MaxScore类的同一个实例中的currentScore()方法,因为这两个方法都访问同一个锁。如果两个方法必须不彼此阻塞,那么程序员必须在更低的级别使用同步。清单3是另一种情况,其中两个同步的方法可能需要彼此独立。清单3.两个独立的同步方法importjava.util.*;publicclassJury{Vectormembers;Vectoralternates;publicJury(){members=newVector(12,1);alternates=newVector(12,1);}publicsynchronizedvoidaddMember(Stringname){members.add(name);}publicsynchronizedvoidaddAlt(Stringname){alternates.add(name);}publicsynchronizedVectorall(){Vectorretval=newVector(members);retval.addAll(alternates);returnretval;}}此处,两个不同的线程可以将members和alternates添加到Jury对象中。请记住,synchronized关键字既可用于方法,更一般地,也可用于任何代码块。清单4中的两段代码是等效的。清单4.等效的代码synchronizedvoidf(){voidf(){//执行某些操作synchronized(this){}//执行某些操作}}所以,为了确保addMember()和addAlt()方法不彼此阻塞,可按清单5所示重写Jury类。清单5.重写后的Jury类importjava.util.*;publicclassJury{Vectormembers;Vectoralternates;publicJury(){members=newVector(12,1);alternates=newVector(12,1);}publicvoidaddMember(Stringname){synchronized(members){members.add(name);}}publicvoidaddAlt(Stringname){synchronized(alternates){alternates.add(name);}}publicVectorall(){Vectorretval;synchronized(members){retval=newVector(members);}synchronized(alternates){retval.addAll(alternates);}returnretval;}}请注意,我们还必须修改all()方法,因为对Jury对象同步已没有意义。在改写后的版本中,addMember()、addAlt()和all()方法只访问与members和alternates对象相关的锁,因此锁定Jury对象毫无用处。另请注意,all()方法本来可以写为清单6所示的形式。清单6.将members和alternates用作同步的对象publicVectorall(){synchronized(members){synchronized(alternates){Vectorretval;retval=newVector(members);retval.addAll(alternates);}}returnretval;}但是,因为我们早在需要之前就获得members和alternates的锁,所以这效率不高。清单5中的改写形式是一个较好的例如,因为它只在最短的时间内持有锁,并且每次只获得一个锁。这样就完全防止了当以后增加代码时可能产生的潜在死锁问题。同步方法的分解正如在前面看到的那样,同步方法获取对象的一个锁。如果该方法由不同的线程频繁调用,那么此方法将成为瓶颈,因为它会对并行性造成限制,从而会对效率造成限制。这样,作为一个一般的原那么,应该尽可能地少用同步方法。尽管有这个原那么,但有时一个方法可能需要完成需要锁定一个对象几项任务,同时还要完成相当耗时的其他任务。在这些情况下,可使用一个动态的“锁定-释放-锁定-释放”方法。例如,清单7和清单8显示了可按这种方式变换的代码。清单7.最初的低效率代码publicsynchonizedvoiddoWork(){unsafe1();write_file();unsafe2();}清单8.重写后效率较高的代码publicvoiddoWork(){synchonized(this){unsafe1();}write_file();synchonized(this){unsafe2();}}清单7和清单8假定第一个和第三个方法需要对象被锁定,而更耗时的write_file()方法不需要对象被锁定。如您所见,重写此方法以后,对此对象的锁在第一个方法完成以后被释放,然后在第三个方法需要时重新获得。这样,当write_file()方法执行时,等待此对象的锁的任何其他方法仍然可以运行。将同步方法分解为这种混合代码可以明显改善性能。但是,您需要注意不要在这种代码中引入逻辑错误。嵌套类内部类在Java程序中实现了一个令人关注的概念,它允许将整个类嵌套在另一个类中。嵌套类作为包含它的类的一个成员变量。如果定期被调用的的一个特定方法需要一个类,就可以构造一个嵌套类,此嵌套类的唯一任务就是定期调用所需的方法。这消除了对程序的其他局部的相依性,并使代码进一步模块化。清单9,一个图形时钟的根底,使用了内部类。清单9.图形时钟例如publicclassClock{protectedclassRefresherextendsThread{intrefreshTime;publicRefresher(intx){super("Refresher");refreshTime=x;}publicvoidrun(){while(true){try{sleep(refreshTime);}catch(Exceptione){}repaint();}}}publicClock(){Refresherr=newRefresher(1000);r.start();}privatevoidrepaint(){//获取时间的系统调用//重绘时钟指针}}清单9中的代码例如不靠任何其他代码来调用repaint()方法。这样,将一个时钟并入一个较大的用户界面就相当简单。事件驱动处理当应用程序需要对事件或条件〔内部的和外部的〕作出反映时,有两种方法或用来设计系统。在第一种方法〔称为轮询〕中,系统定期确定这一状态并据此作出反映。这种方法〔虽然简单〕也效率不高,因为您始终无法预知何时需要调用它。第二种方法〔称为事件驱动处理〕效率较高,但实现起来也较为复杂。在事件驱动处理的情况下,需要一种发信机制来控制某一特定线程何时应该运行。在Java程序中,您可以使用wait()、notify()和notifyAll()方法向线程发送信号。这些方法允许线程在一个对象上阻塞,直到所需的条件得到满足为止,然后再次开始运行。这种设计减少了CPU占用,因为线程在阻塞时不消耗执行时间,并且可在notify()方法被调用时立即唤醒。与轮询相比,事件驱动方法可以提供更短的响应时间。创立高效的线程平安类的步骤编写线程平安类的最简单的方法是用synchronized声明每个方法。虽然这种方案可以消除数据损坏,但它同时也会消除您预期从多线程获得的任何收益。这样,您就需要分析并确保在synchronized块内部仅占用最少的执行时间。您必须格外关注访问缓慢资源―文件、目录、网络套接字和数据库

―的方法,这些方法可能降低您的程序的效率。尽量将对这类资源的访问放在一个单独的线程中,最好在任何synchronized代码之外。一个线程平安类的例如被设计为要处理的文件的中心储存库。它与使用getWork()和finishWork()与WorkTable类对接的一组线程一起工作。本例旨在让您体验一下全功能的线程平安类,该类使用了helper线程和混合同步。请注意继续添加要处理的新文件的Refresherhelper线程的用法。本例没有调整到最正确性能,很明显有许多地方可以改写以改善性能,比方将Refresher线程改为使用wait()/notify()方法事件驱动的,改写populateTable()方法以减少列出磁盘上的文件〔这是高本钱的操作〕所产生的影响。小结通过使用可用的全部语言支持,Java程序中的多线程编程相当简单。但是,使线程平安类具有较高的效率仍然比拟困难。为了改善性能,您必须事先考虑并谨慎使用锁定功能。抽象类和接口的区别,使用场景在Java语言中,abstractclass和interafce是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstractclass和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstractclass和interface的选择显得比拟随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。理解抽象类abstractclass和interface在Java语言中都是用来进行抽象类〔本文中的抽象类并非从abstractclass翻译而来,它表示的是一个抽象体,而abstractclass为Java语言中用于定义抽象类的一种方法,请读者注意区分〕定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比方:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现那么表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原那么OCP(Open-ClosedPrinciple),抽象类是其中的关键所在。从语法定义层面看abstractclass和interface在语法层面,Java语言对于abstractclass和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。使用abstractclass的方式定义Demo抽象类的方式如下:abstractclassDemo{

abstractvoidmethod1();

abstractvoidmethod2();

}使用interface的方式定义Demo抽象类的方式如下:interfaceDemo{

voidmethod1();

voidmethod2();

}在abstractclass方式中,Demo可以有自己的数据成员,也可以有非abstract的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员〔也就是必须是staticfinal的,不过在interface中一般不定义数据成员〕,所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstractclass。从编程的角度来看,abstractclass和interface都可以用来实现"designbycontract"的思想。但是在具体的使用上面还是有一些区别的。首先,abstractclass在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系(因为Java不支持多继承--转注)。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。其次,在abstractclass的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会增加一些复杂性,有时会造成很大的麻烦。在抽象类中不能定义默认行为还存在另一个比拟严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面〔一般通过abstractclass或者interface来表示〕以适应新的情况〔比方,添加新的方法或者给已用的方法中添加新的参数〕时,就会非常的麻烦,可能要花费很多的时间〔对于派生类很多的情况,尤为如此〕。但是如果界面是通过abstractclass来实现的,那么可能就只需要修改定义在abstractclass中的默认行为就可以了。同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"onerule,oneplace"原那么,造成代码重复,同样不利于以后的维护。因此,在abstractclass和interface间进行选择时要非常的小心。从设计理念层面看abstractclass和interface上面主要从语法定义和编程的角度论述了abstractclass和interface的区别,这些层面的区别是比拟低层次的、非本质的。本小节将从另一个层面:abstractclass和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。前面已经提到过,abstractclass在Java语言中表达了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a"关系,即父类和派生类在概念本质上应该是相同的。对于interface来说那么不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstractclass或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:使用abstractclass方式定义Door:abstractclassDoor{

abstractvoidopen();

abstractvoidclose();

}使用interface方式定义Door:interfaceDoor{

voidopen();

voidclose();

}其他具体的Door类型可以extends使用abstractclass方式定义的Door或者implements使用interface方式定义的Door。看起来好似使用abstractclass和interface没有大的区别。如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢〔在本例中,主要是为了展示abstractclass和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略〕?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。解决方案一:简单的在Door的定义中增加一个alarm方法,如下:abstractclassDoor{

abstractvoidopen();

abstractvoidclose();

abstractvoidalarm();

}或者interfaceDoor{

voidopen();

voidclose();

voidalarm();

}那么具有报警功能的AlarmDoor的定义方式如下:classAlarmDoorextendsDoor{

voidopen(){…}

voidclose(){…}

voidalarm(){…}

}或者classAlarmDoorimplementsDoor{

voidopen(){…}

voidclose(){…}

voidalarm(){…}

}这种方法违反了面向对象设计中的一个核心原那么ISP(InterfaceSegregationPrinciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变〔比方:修改alarm方法的参数〕而改变,反之依然。解决方案二:既然open、close和alarm属于两个不同的概念,根据ISP原那么应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstractclass方式定义;两个概念都使用interface方式定义;一个概念使用abstractclass方式定义,另一个概念使用interface方式定义。显然,由于Java语言不支持多重继承,所以两个概念都使用abstractclass方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比方:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上〔均使用interface方式定义〕反映不出上述含义。如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstractclass在Java语言中表示一种继承关系,而继承关系在本质上是"is-a"关系。所以对于Door这个概念,我们应该使用abstarctclass方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:abstractclassDoor{

abstractvoidopen();

abstractvoidclose();

}

interfaceAlarm{

voidalarm();

}

classAlarmDoorextendsDoorimplementsAlarm{

voidopen(){…}

voidclose(){…}

voidalarm(){…}

}这种实现方式根本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstractclass表示的是"is-a"关系,interface表示的是"like-a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比方:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。小结1.abstractclass在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。2.在abstractclass中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员〔也就是必须是staticfinal的,不过在interface中一般不定义数据成员〕,所有的成员方法都是abstract的。3.abstractclass和interface所反映出的设计理念不同。其实abstractclass表示的是"is-a"关系,interface表示的是"like-a"关系。4.实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中那么不能有实现方法。5.接口中定义的变量默认是publicstaticfinal型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。6.抽象类中的变量默认是friendly型,其值可以在子类中重新定义,也可以重新赋值。7.接口中的方法默认都是public,abstract类型的。结论abstractclass和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系〔虽然都能够实现需求的功能〕。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会.hash算法的实现原理,hashcode的实现原理1.引言哈希表〔HashTable〕的应用近两年才在NOI中出现,作为一种高效的数据结构,它正在竞赛中发挥着越来越重要的作用。哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比拟多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比拟容易也是它的特点之一。哈希表又叫做散列表,分为“开散列”和“闭散列”。考虑到竞赛时多数人通常防止使用动态存储结构,本文中的“哈希表”仅指“闭散列”,关于其他方面读者可参阅其他书籍。

2.根底操作

2.1根本原理我们使用一个下标范围比拟大的数组来存储元素。可以设计一个函数〔哈希函数,也叫做散列函数〕,使得每个元素的关键字都与一个函数值〔即数组下标〕相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方。但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。2.2函数构造构造函数的常用方法〔下面为了表达简洁,设h(k)表示关键字为k的元素所对应的函数值〕:

a)除余法:选择一个适当的正整数p,令h(k)=kmodp,这里,p如果选取的是比拟大的素数,效果比拟好。而且此法非常容易实现,因此是最常用的方法。

b)数字选择法:如果关键字的位数比拟多,超过长整型范围而无法直接运算,可以选择其中数字分布比拟均匀的假设干位,所组成的新的值作为关键字或者直接作为函数值。2.3冲突处理线性重新散列技术易于实现且可以较好的到达目的。令数组元素个数为S,那么当h(k)已经存储了元素的时候,依次探查(h(k)+i)modS,i=1,2,3……,直到找到空的存储单元为止〔或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围防止的〕。2.4支持运算哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x))、插入元素(insert)、查找元素(member)。设插入的元素的关键字为x,A为存储的数组。初始化比拟容易,例如:

constempty=maxlongint;//用非常大的整数代表这个位置没有存储元素

p=9997;//表的大小

proceduremakenull;

vari:integer;

begin

fori:=0top-1do

A[i]:=empty;

End;

哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:

functionh(x:longint):Integer;

begin

h:=xmodp;

end;

我们注意到,插入和查找首先都需要对这个元素定位,即如果这个元素假设存在,它应该存储在什么位置,因此参加一个定位的函数locate

functionlocate(x:longint):integer;

varorig,i:integer;

begin

orig:=h(x);

i:=0;

while(i<S)and(A[(orig+i)modS]<>x)and(A[(orig+i)modS]<>empty)do

inc(i);

//当这个循环停下来时,要么找到一个空的存储单元,要么找到这个元

//素存储的单元,要么表已经满了

locate:=(orig+i)modS;

end;

插入元素

procedureinsert(x:longint);

varposi:integer;

begin

posi:=locate(x);//定位函数的返回值ifA[posi]=emptythenA[posi]:=x

elseerror;//error即为发生了错误,当然这是可以防止的

end;

查找元素是否已经在表中

proceduremember(x:longint):boolean;

varposi:integer;

begin

posi:=locate(x);

ifA[posi]=xthenmember:=true

elsemember:=false;

end;

这些就是建立在哈希表上的常用根本运算。初步结论:当数据规模接近哈希表上界或者下界的时候,哈希表完全不能够表达高效的特点,甚至还不如一般算法。但是如果规模在中央,它高效的特点可以充分表达。试验说明当元素充满哈希表的90%的时候,效率就已经开始明显下降。这就给了我们提示:如果确定使用哈希表,应该尽量使数组开大,但对最太大的数组进行操作也比拟费时间,需要找到一个平衡点。通常使它的容量至少是题目最大需求的120%,效果比拟好〔这个仅仅是经验,没有严格证明〕。4.应用举例

4.1应用的简单原那么什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:“某个元素是否在集合中?”,也就是需要高效的数据存储和查找,那么使用哈希表是最好不过的了!那么,在应用哈希表的过程中,值得注意的是什么呢?哈希函数的设计很重要。一个不好的哈希函数,就是指造成很多冲突的情况,从前面的例子已经可以看出来,解决冲突会浪费掉大量时间,因此我们的目标就是尽力防止冲突。前面提到,在使用“除余法”的时候,h(k)=kmodp,p最好是一个大素数。这就是为了尽力防止冲突。为什么呢?假设p=1000,那么哈希函数分类的标准实际上就变成了按照末三位数分类,这样最多1000类,冲突会很多。一般地说,如果p的约数越多,那么冲突的几率就越大。简单的证明:假设p是一个有较多约数的数,同时在数据中存在q满足**(p,q)=d>1,即有p=a*d,q=b*d,那么有qmodp=q–p*[qdivp]=q–p*[bdiva].①其中[bdiva]的取值范围是不会超过[0,b]的正整数。也就是说,[bdiva]的值只有b+1种可能,而p是一个预先确定的数。因此①式的值就只有b+1种可能了。这样,虽然mod运算之后的余数仍然在[0,p-1]内,但是它的取值仅限于①可能取到的那些值。也就是说余数的分布变得不均匀了。容易看出,p的约数越多,发生这种余数分布不均匀的情况就越频繁,冲突的几率越高。而素数的约数是最少的,因此我们选用大素数。记住“素数是我们的得力助手”。另一方面,一味的追求低冲突率也不好。理论上,是可以设计出一个几乎完美,几乎没有冲突的函数的。然而,这样做显然不值得,因为这样的函数设计很浪费时间而且编码一定很复杂,与其花费这么大的精力去设计函数,还不如用一个虽然冲突多一些但是编码简单的函数。因此,函数还需要易于编码,即易于实现。综上所述,设计一个好的哈希函数是很关键的。而“好”的标准,就是较低的冲突率和易于实现。另外,使用哈希表并不是记住了前面的根本操作就能以不变应万变的。有的时候,需要按照题目的要求对哈希表的结构作一些改良。往往一些简单的改良就可以带来巨大的方便。这些只是一般原那么,真正遇到试题的时候实际情况千变万化,需要具体问题具体分析才行。error和exception的区别,RuntimeException和非RuntimeException的区别1.异常机制异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的平安通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。传统的处理异常的方法是,函数返回一个特殊的结果来表示出现异常〔通常这个特殊结果是大家约定俗称的〕,调用该函数的程序负责检查并分析函数返回的结果。这样做有如下的弊端:例如函数返回-1代表出现异常,但是如果函数确实要返回-1这个正确的值时就会出现混淆;可读性降低,将程序代码与处理异常的代码混爹在一起;由调用函数的程序来分析错误,这就要求客户程序员对库函数有很深的了解。异常处理的流程:①遇到错误,方法立即结束,并不返回一个值;同时,抛出一个异常对象。②调用该方法的程序也不会继续执行下去,而是搜索一个可以处理该异常的异常处理器,并执行其中的代码。2异常的分类异常的分类:①异常的继承结构:基类为Throwable,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception,具体的RuntimeException继承RuntimeException。②Error和RuntimeException及其子类成为未检查异常〔unchecked〕,其它异常成为已检查异常〔checked〕。每个类型的异常的特点

Error体系:

Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形。应用程序不应该抛出这种类型的对象〔一般是由虚拟机抛出〕。如果出现这种错误,除了尽力使程序平安退出外,在其他方面是无能为力的。所以,在进行程序设计时,应该更关注Exception体系。Exception体系包括RuntimeException体系和其他非RuntimeException的体系:①RuntimeException:RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等。处理RuntimeException的原那么是:如果出现RuntimeException,那么一定是程序员的错误。例如,可以通过检查数组下标和数组边界来防止数组越界访问异常。②其他非RuntimeException〔IOException等等〕:这类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误。与C++异常分类的不同:①Java中RuntimeException这个类名起的并不恰当,因为任何异常都是运行时出现的。〔在编译时出现的错误并不是异常,换句话说,异常就是为了解决程序运行时出现的的错误〕。②C++中logic_error与Java中的RuntimeException是等价的,而runtime_error与Java中非RuntimeException类型的异常是等价的。3异常的使用方法声明方法抛出异常①语法:throws〔略〕②为什么要声明方法抛出异常?方法是否抛出异常与方法返回值的类型一样重要。假设方法抛出异常确没有声明该方法将抛出异常,那么客户程序员可以调用这个方法而且不用编写处理异常的代码。那么,一旦出现异常,那么这个异常就没有适宜的异常控制器来解决。③为什么抛出的异常一定是已检查异常?

RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。而已检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数〔库函数的异常由库程序员抛出〕;客户程序员自己使用throw语句抛出异常。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改〔相当于调试的一种方法〕;只有已检查异常才是程序员所关心的,程序应该且仅应该抛出或处理已检查异常。注意:覆盖父类某方法的子类方法不能抛出比父类方法更多的异常,所以,有时设计父类的方法时会声明抛出异常,但实际的实现方法的代码却并不抛出异常,这样做的目的就是为了方便子类方法覆盖父类方法时可以抛出异常。如何抛出异常①语法:throw〔略〕②抛出什么异常?对于一个异常对象,真正有用的信息时异常的对象类型,而异常对象本身毫无意义。比方一个异常对象的类型是ClassCastException,那么这个类名就是唯一有用的信息。所以,在选择抛出什么异常时,最关键的就是选择异常的类名能够明确说明异常情况的类。③异常对象通常有两种构造函数:一种是无参数的构造函数;另一种是带一个字符串的构造函数,这个字符串将作为这个异常对象除了类型名以外的额外说明。④创立自己的异常:当Java内置的异常都不能明确的说明异常情况的时候,需要创立自己的异常。需要注意的是,唯一有用的就是类型名这个信息,所以不要在异常类的设计上花费精力。捕获异常如果一个异常没有被处理,那么,对于一个非图形界面的程序而言,该程序会被中止并输出异常信息;对于一个图形界面程序,也会输出异常的信息,但是程序并不中止,而是返回用错误页面。语法:try、catch和finally〔略〕,控制器模块必须紧接在try块后面。假设掷出一个异常,异常控制机制会搜寻参数与异常类型相符的第一个控制器随后它会进入那个catch从句,并认为异常已得到控制。一旦catch从句结束对控制器的搜索也会停止。捕获多个异常〔注意语法与捕获的顺序〕〔略〕

finally的用法与异常处理流程〔略〕异常处理做什么?对于Java来说,由于有了垃圾收集,所以异常处理并不需要回收内存。但是依然有一些资源需要程序员来收集,比方文件、网络连接和图片等资源。应该声明方法抛出异常还是在方法中捕获异常?原那么:捕捉并处理哪些知道如何处理的异常,而传递哪些不知道如何处理的异常。再次抛出异常①为什么要再次抛出异常?在本级中,只能处理一局部内容,有些处理需要在更高一级的环境中完成,所以应该再次抛出异常。这样可以使每级的异常处理器处理它能够处理的异常。②异常处理流程:对应与同一try块的catch块将被忽略,抛出的异常将进入更高的一级。4关于异常的其他问题①过度使用异常:首先,使用异常很方便,所以程序员一般不再愿意编写处理错误的代码,而仅仅是简简单单的抛出一个异常。这样做是不对的,对于完全的错误,应该编写处理这种错误的代码,增加程序的鲁棒性。另外,异常机制的效率很差。②将异常与普通错误区分开:对于普通的完全一致的错误,应该编写处理这种错误的代码,增加程序的鲁棒性。只有外部的不能确定和预知的运行时错误才需要使用异常。③异常对象中包含的信息:一般情况下,异常对象唯一有用的信息就是类型信息。但使用异常带字符串的构造函数时,这个字符串还可以作为额外的信息。调用异常对象的getMessage()、toString()或者printStackTrace()方法可以分别得到异常对象的额外信息、类名和调用堆栈的信息。并且后一种包含的信息是前一种的超集。继承与组合的区别,使用场景1、继承假设我们有一个名为Insect〔昆虫〕的类,这个类包含两个方法:1〕移动move();2〕攻击attack()。

代码如下:12345678910111213141516171819202122232425262728293031323334classInsect{

privateintsize;

privateStringcolor;

publicInsect(intsize,Stringcolor){

this.size=size;

this.color=color;

}

publicintgetSize(){

returnsize;

}

publicvoidsetSize(intsize){

this.size=size;

}

publicStringgetColor(){

returncolor;

}

publicvoidsetColor(Stringcolor){

this.color=color;

}

publicvoidmove(){

System.out.println("Move");

}

publicvoidattack(){

move();

//假设昆虫在攻击前必须要先移动一次

System.out.println("Attack");

}}现在,你想要定义一个名为Bee〔蜜蜂〕的类。Bee〔蜜蜂〕是Insect〔昆虫〕的一种,但实现了不同于Insect〔昆虫〕的attack()和move方法。这时候我们可以用继承的设计机制来实现Bee类,就像下面的代码一样:1234567891011121314classBeeextendsInsect{

publicBee(intsize,Stringcolor){

super(size,color);

}

publicvoidmove(){

System.out.println("Fly");

}

publicvoidattack(){

move();

super.attack();

}}123456publicclassInheritanceVSComposition{

publicstaticvoidmain(String[]args){

Insecti=newBee(1,"red");

i.attack();

}}InheritanceVSComposition作为一个测试类,在其main方法中生成了一个Bee类的实例,并赋值给Insect类型的引用变量i。所以调用i的attack方法时,对应的是Bee类实例的attack方法,也就是调用了Bee类的attack方法。类的继承结构图如下,非常简单:输出:123FlyFlyAttackFly被打印了两次,也就是说move方法被调用了两次。但按理来讲,move方法只应当被调用一次,因为无论是昆虫还是蜜蜂,一次攻击前只移动一次。问题出在子类〔即Bee类〕的attack方法的重载代码中,也就是super.attack()这一句。因为在父类〔即Insect类〕中,调用attack方法时会先调用move方法,所以当子类〔Bee〕调用super.attack()时,相当于也同时调用了被重载的move方法〔注意是子类被重载的move方法,而不是父类的move方法〕。为了解决这个问题,我们可以采取以下方法:删除子类的attack方法。这么做会使得子类的attack方法的实现完全依赖于父类对于该方法的实现〔因为子类继承了父类的attack方法〕。如果父类的attack方法不受控制而产生了变更。比方说,父类的attack方法中调用了另外的move方法,那么子类的attack方法也会产生相应的变化,这是一种很糟糕的封装。也可以重写子类的attack方法,像下面这样:1234publicvoidattack(){

move();

System.out.println("Attack");}这样保证了结果的正确性,因为子类的attack方法不再依赖于父类。但是,子类attack方法的代码与父类产生了重复〔重复的attack方法会使得很多事情变得复杂,不仅仅是多打印了一条输出语句〕。所以第二种方法也不行,它不符合软件工程中关于重用的思想。如此看来,继承机制是有缺点的:子类依赖于父类的实现细节,如果父类产生了变更,子类的后果将不堪设想。2、组合在上面的例子中,可以用组合的机制来替代继承。我们先看一下运用组合如何实现。attack这一功能不再是一个方法,而是被抽象为一个接口。1234interfaceAttack{

publicvoidmove();

publicvoidattack();}通过对Attack接口的实现,就可以在实现类当中定义不同

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论