Java 面试经典指南(1)

Java 面试经典指南

进程间通信
用于进程间通讯(IPC)的四种不同技术:
1. 消息传递(管道,FIFO,posix和system v消息队列)
2. 同步(互斥锁,条件变量,读写锁,文件和记录锁,Posix和System V信号灯)
3. 共享内存区(匿名共享内存区,有名Posix共享内存区,有名System V共享内存区)
4. 过程调用(Solaris门,Sun RPC)
消息队列和过程调用往往单独使用,也就是说它们通常提供了自己的同步机制.相反,共享内存区通常需要由应用程序提供的某种同步形式才能正常工作.解决某个特定问题应使用哪种IPC不存在简单的判定,应该逐渐熟悉各种IPC形式提供的机制,然后根据特定应用的要求比较它们的特性.

必须考虑的四个前提:
1. 联网的还是非联网的.IPC适用于单台主机上的进程或线程间的.如果应用程序有可能分布到多台主机上,那就要考虑使用套接字代替IPC,从而简化以后向联网的应用程序转移的工作.
2. 可移植性.
3. 性能,在具体的开发环境下运行测试程序,比较几种IPC的性能差异.
4. 实时调度.如果需要这一特性,而且所用的系统也支持posix实时调度选项,那就考虑使用Posix的消息传递和同步函数.

各种IPC之间的一些主要差异:
1. 管道和FIFO是字节流,没有消息边界.Posix消息和System V消息则有从发送者向接受者维护的记录边界(eg:TCP是没有记录边界的字节流,UDP则提供具有记录边界的消息).
2. 当有一个消息放置到一个空队列中时,Posix消息队列可向一个进程发送一个信号,或者启动一个新的线程.System V则不提供类似的通知形式.
3. 管道和FIFO的数据字节是先进先出的.Posix消息和System V消息具有由发送者赋予的优先级.从一个Posix消息队列读出时,首先返回的总是优先级最高的消息.从一个System V消息队列读出时,读出者可以要求想要的任意优先级的消息.
4. 在众多的消息传递技术—管道,FIFO,Posix消息队列和System V消息队列—中,可从一个信号处理程序中调用的函数只有read和write(适用于管道和FIFO).

比较不同形式的消息传递时,我们感兴趣的有两种测量尺度:
1. 带宽(bandwidth):数据通过IPC通道转移的速度.为测量该值,我们从一个进程向另一个进程发送大量数据(几百万字节).我们还给不同大小的I/O操作(例如管道和FIFO的write和read操作)测量该值,期待发现带宽随每个I/O操作的数据量的增长而增长的规律.
2. 延迟(latency):一个小的IPC消息从一个进程到令一个进程再返回来所花的时间.我们测量的是只有一个1个字节的消息从一个进程到令一个进程再回来的时间(往返时间)

在现实世界中,带宽告诉我们大块数据通过一个IPC通道发送出去需花多长时间,然而IPC也用于传递小的控制信息,系统处理这些小消息所需的时间就由延迟提供.这两个数都很重要.

session的工作原理
一、术语session
在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。

然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义,“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者“一个POP3 session”③。

而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。

鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。
在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥

二、HTTP协议与状态保持
HTTP协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

三、理解cookie机制
cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。

正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。

而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。

cookie的内容主要包括:名字,值,过期时间,路径和域。
其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。
路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。
路径与域合在一起就构成了cookie的作用范围。
如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。

下面就是一个goolge设置cookie的响应头的例子
HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649M=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html

这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分
浏览器在再次访问goolge的资源时自动向外发送cookie
使用Firefox可以很容易的观察现有的cookie的值
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。
IE也可以设置在接受cookie前询问
这是一个询问接受cookie的对话框。

四、理解session机制
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。

保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。

由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK ... 99zWpBng!-145788764
另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK ... 99zWpBng!-145788764
这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。
为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
<form name="testform" action="/xxx">
<input type="text">
</form>
在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
实际上这种技术可以简单的用对action应用URL重写来代替。

在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

五、理解javax.servlet.http.HttpSession
HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域,cookie的生存时间等。

一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用,Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。

cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

关于session的设置参考[5]
http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

六、HttpSession常见问题
(在本小节中session的含义为⑤和⑥的混合)

1、session在何时被创建
一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <%@page session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。

由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

2、session何时被删除
综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

3、如何做到在浏览器关闭时删除session
严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

4、有个HttpSessionListener是怎么回事
你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。

5、存放在session中的对象必须是可序列化的吗
不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

6、如何才能正确的应付客户端禁止cookie的可能性
对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

8、如何防止用户打开两个浏览器窗口操作导致的session混乱
这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

10、为什么session不见了
排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。

七、跨应用程序的session共享

常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。

然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。

首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。

根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。

笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。

iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
<session-info>
<path>/NASApp</path>
</session-info>

需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。

在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。

我们再看一下Weblogic Server是如何处理session的。

从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下

对于这样一种结构,在session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

应用程序A
context.setAttribute("appA", session);

应用程序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");

值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。

那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。

八、总结
session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。

ClassLoader原理
http://jamesdu.blogchina.com/349567.html
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。



一. ClassLoader基本概念
1.ClassLoader分类
类装载器是用来把类(class)装载进JVM的。
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。


JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。
AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。

Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。

例1,测试你所使用的JVM的ClassLoader
/*LoaderSample1.java*/
public class LoaderSample1 {
public static void main(String[] args) {
Class c;
ClassLoader cl;
cl = ClassLoader.getSystemClassLoader();
System.out.println(cl);
while (cl != null ) {
cl = cl.getParent();
System.out.println(cl);
}
try {
c = Class.forName( " java.lang.Object " );
cl = c.getClassLoader();
System.out.println( " java.lang.Object's loader is " + cl);
c = Class.forName( " LoaderSample1 " );
cl = c.getClassLoader();
System.out.println( " LoaderSample1's loader is " + cl);
} catch (Exception e) {
e.printStackTrace();
}
}
}

在我的机器上(Sun Java 1.4.2)的运行结果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null
java.lang.Object's loader is null
LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f
第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
第三行表示,系统类装载器parent的parent为bootstrap
第四行表示,核心类java.lang.Object是由bootstrap装载的
第五行表示,用户类LoaderSample1是由系统类装载器装载的


二.parent delegation模型
从1.2版本开始,Java引入了双亲委托模型,从而更好的保证Java平台的安全。在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载。

图 1 parent delegation模型
如图1所示,loader2的parent为loader1,loader1的parent为system class loader。假设loader2被要求装载类MyClass,在parent delegation模型下,loader2首先请求loader1代为装载,loader1再请求系统类装载器去装载MyClass。若系统装载器能成功装载,则将MyClass所对应的Class对象的reference返回给loader1,loader1再将reference返回给loader2,从而成功将类MyClass装载进虚拟机。若系统类装载器不能装载MyClass,loader1会尝试装载MyClass,若loader1也不能成功装载,loader2会尝试装载。若所有的parent及loader2本身都不能装载,则装载失败。

若有一个能成功装载,实际装载的类装载器被称为定义类装载器,所有能成功返回Class对象的装载器(包括定义类装载器)被称为初始类装载器。如图1所示,假设loader1实际装载了MyClass,则loader1为MyClass的定义类装载器,loader2和loader1为MyClass的初始类装载器。

需要指出的是,Class Loader是对象,它的父子关系和类的父子关系没有任何关系。

那么parent delegation模型为什么更安全了?因为在此模型下用户自定义的类装载器不可能装载应该由父亲装载器装载的可靠类,从而防止不可靠甚至恶意的代码代替由父亲装载器装载的可靠代码。实际上,类装载器的编写者可以自由选择不用把请求委托给parent,但正如上所说,会带来安全的问题。


三.命名空间及其作用
每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。

例2演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以访问LoaderSampl3中公共的成员(如age)。
例2不同命名空间的类的访问
/*LoaderSample2.java*/
import java.net. * ;
import java.lang.reflect. * ;
public class LoaderSample2 {
public static void main(String[] args) {
try {
String path = System.getProperty( " user.dir " );
URL[] us = { new URL( " file:// " + path + " /sub/ " )};
ClassLoader loader = new URLClassLoader(us);
Class c = loader.loadClass( " LoaderSample3 " );
Object o = c.newInstance();
Field f = c.getField( " age " );
int age = f.getInt(o);
System.out.println( " age is " + age);
} catch (Exception e) {
e.printStackTrace();
}
}
}

/*sub/Loadersample3.java*/
public class LoaderSample3 {
static {
System.out.println( " LoaderSample3 loaded " );
}
public int age = 30 ;
}
编译:javac LoaderSample2.java; javac sub/LoaderSample3.java
运行:java LoaderSample2
LoaderSample3 loaded
age is 30
从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以访问其公共成员age。
运行时包(runtime package)
由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.*由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能访问核心类库java.lang中类的包可见的成员。

总结
命名空间并没有完全禁止属于不同空间的类的互相访问,双亲委托模型加强了Java的安全,运行时包增加了对包可见成员的保护。

二. 扩展ClassLoader方法
我们目的是从本地文件系统使用我们实现的类装载器装载一个类。为了创建自己的类装载器我们应该扩展ClassLoader类,这是一个抽象类。我们创建一个FileClassLoader extends ClassLoader。我们需要覆盖ClassLoader中的findClass(String name)方法,这个方法通过类的名字而得到一个Class对象。
public Class findClass(String name)
{
byte [] data = loadClassData(name);
return defineClass(name, data, 0 , data.length);
}

我们还应该提供一个方法loadClassData(String name),通过类的名称返回class文件的字
节数组。然后使用ClassLoader提供的defineClass()方法我们就可以返回Class对象了。
public byte [] loadClassData(String name)
{
FileInputStream fis = null ;
byte [] data = null ;
try
{
fis = new FileInputStream( new File(drive + name + fileType));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch = 0 ;
while ((ch = fis.read()) != - 1 )
{
baos.write(ch);

}
data = baos.toByteArray();
} catch (IOException e)
{
e.printStackTrace();
}

return data;
}

ORACLE 中ROWNUM用法总结
转自:http://www.itpub.net/showthread.php?s=&postid=8169093#post8169093
对于 Oracle 的 rownum 问题,很多资料都说不支持>,>=,=,between...and,只能用以上符号(<、<=、!=),并非说用>,>=,=,between..and 时会提示SQL语法错误,而是经常是查不出一条记录来,还会出现似乎是莫名其妙的结果来,其实您只要理解好了这个 rownum 伪列的意义就不应该感到惊奇,同样是伪列,rownum 与 rowid 可有些不一样,下面以例子说明

假设某个表 t1(c1) 有 20 条记录

如果用 select rownum,c1 from t1 where rownum < 10, 只要是用小于号,查出来的结果很容易地与一般理解在概念上能达成一致,应该不会有任何疑问的。

可如果用 select rownum,c1 from t1 where rownum > 10 (如果写下这样的查询语句,这时候在您的头脑中应该是想得到表中后面10条记录),你就会发现,显示出来的结果要让您失望了,也许您还会怀疑是不谁删了一些记录,然后查看记录数,仍然是 20 条啊?那问题是出在哪呢?

先好好理解 rownum 的意义吧。因为ROWNUM是对结果集加的一个伪列,即先查到结果集之后再加上去的一个列 (强调:先要有结果集)。简单的说 rownum 是对符合条件结果的序列号。它总是从1开始排起的。所以你选出的结果不可能没有1,而有其他大于1的值。所以您没办法期望得到下面的结果集:

11 aaaaaaaa
12 bbbbbbb
13 ccccccc
.................

rownum >10 没有记录,因为第一条不满足去掉的话,第二条的ROWNUM又成了1,所以永远没有满足条件的记录。或者可以这样理解:

ROWNUM是一个序列,是oracle数据库从数据文件或缓冲区中读取数据的顺序。它取得第一条记录则rownum值为1,第二条为2,依次类推。如果你用>,>=,=,between...and这些条件,因为从缓冲区或数据文件中得到的第一条记录的rownum为1,则被删除,接着取下条,可是它的rownum还是1,又被删除,依次类推,便没有了数据。

有了以上从不同方面建立起来的对 rownum 的概念,那我们可以来认识使用 rownum 的几种现像

1. select rownum,c1 from t1 where rownum != 10 为何是返回前9条数据呢?它与 select rownum,c1 from tablename where rownum < 10 返回的结果集是一样的呢?
因为是在查询到结果集后,显示完第 9 条记录后,之后的记录也都是 != 10,或者 >=10,所以只显示前面9条记录。也可以这样理解,rownum 为9后的记录的 rownum为10,因条件为 !=10,所以去掉,其后记录补上,rownum又是10,也去掉,如果下去也就只会显示前面9条记录了

2. 为什么 rownum >1 时查不到一条记录,而 rownum >0 或 rownum >=1 却总显示所以的记录
因为 rownum 是在查询到的结果集后加上去的,它总是从1开始

3. 为什么 between 1 and 10 或者 between 0 and 10 能查到结果,而用 between 2 and 10 却得不到结果
原因同上一样,因为 rownum 总是从 1 开始

从上可以看出,任何时候想把 rownum = 1 这条记录抛弃是不对的,它在结果集中是不可或缺的,少了rownum=1 就像空中楼阁一般不能存在,所以你的 rownum 条件要包含到 1

但如果就是想要用 rownum > 10 这种条件的话话就要用嵌套语句,把 rownum 先生成,然后对他进行查询。
select *
from (selet rownum as rn,t1.* from a where ...)
where rn >10

一般代码中对结果集进行分页就是这么干的。

另外:rowid 与 rownum 虽都被称为伪列,但它们的存在方式是不一样的,rowid 可以说是物理存在的,表示记录在表空间中的唯一位置ID,在DB中唯一。只要记录没被搬动过,rowid是不变的。rowid 相对于表来说又像表中的一般列,所以以 rowid 为条件就不会有 rownum那些情况发生。
另外还要注意:rownum不能以任何基表的名称作为前缀。

ORACLE 中ROWNUM用法总结!
对于 Oracle 的 rownum 问题,很多资料都说不支持>,>=,=,between...and,只能用以上符号(<、<=、!=),并非说用>,>=,=,between..and 时会提示SQL语法错误,而是经常是查不出一条记录来,还会出现似乎是莫名其妙的结果来,其实您只要理解好了这个 rownum 伪列的意义就不应该感到惊奇,同样是伪列,rownum 与 rowid 可有些不一样,下面以例子说明
假设某个表 t1(c1) 有 20 条记录
如果用 select rownum,c1 from t1 where rownum < 10, 只要是用小于号,查出来的结果很容易地与一般理解在概念上能达成一致,应该不会有任何疑问的。
可如果用 select rownum,c1 from t1 where rownum > 10 (如果写下这样的查询语句,这时候在您的头脑中应该是想得到表中后面10条记录),你就会发现,显示出来的结果要让您失望了,也许您还会怀疑是不谁删了一些记录,然后查看记录数,仍然是 20 条啊?那问题是出在哪呢?
先好好理解 rownum 的意义吧。因为ROWNUM是对结果集加的一个伪列,即先查到结果集之后再加上去的一个列 (强调:先要有结果集)。简单的说 rownum 是对符合条件结果的序列号。它总是从1开始排起的。所以你选出的结果不可能没有1,而有其他大于1的值。所以您没办法期望得到下面的结果集:
11 aaaaaaaa
12 bbbbbbb
13 ccccccc
.................
rownum >10 没有记录,因为第一条不满足去掉的话,第二条的ROWNUM又成了1,所以永远没有满足条件的记录。或者可以这样理解:
ROWNUM是一个序列,是oracle数据库从数据文件或缓冲区中读取数据的顺序。它取得第一条记录则rownum值为1,第二条为2,依次类推。如果你用>,>=,=,between...and这些条件,因为从缓冲区或数据文件中得到的第一条记录的rownum为1,则被删除,接着取下条,可是它的rownum还是1,又被删除,依次类推,便没有了数据。
有了以上从不同方面建立起来的对 rownum 的概念,那我们可以来认识使用 rownum 的几种现像
1. select rownum,c1 from t1 where rownum != 10 为何是返回前9条数据呢?它与 select rownum,c1 from tablename where rownum < 10 返回的结果集是一样的呢?
因为是在查询到结果集后,显示完第 9 条记录后,之后的记录也都是 != 10,或者 >=10,所以只显示前面9条记录。也可以这样理解,rownum 为9后的记录的 rownum为10,因条件为 !=10,所以去掉,其后记录补上,rownum又是10,也去掉,如果下去也就只会显示前面9条记录了
2. 为什么 rownum >1 时查不到一条记录,而 rownum >0 或 rownum >=1 却总显示所以的记录
因为 rownum 是在查询到的结果集后加上去的,它总是从1开始
3. 为什么 between 1 and 10 或者 between 0 and 10 能查到结果,而用 between 2 and 10 却得不到结果
原因同上一样,因为 rownum 总是从 1 开始
从上可以看出,任何时候想把 rownum = 1 这条记录抛弃是不对的,它在结果集中是不可或缺的,少了rownum=1 就像空中楼阁一般不能存在,所以你的 rownum 条件要包含到 1
但如果就是想要用 rownum > 10 这种条件的话话就要用嵌套语句,把 rownum 先生成,然后对他进行查询。
select *
from (selet rownum as rn,t1.* from a where ...)
where rn >10
一般代码中对结果集进行分页就是这么干的。
另外:rowid 与 rownum 虽都被称为伪列,但它们的存在方式是不一样的,rowid 可以说是物理存在的,表示记录在表空间中的唯一位置ID,在DB中唯一。只要记录没被搬动过,rowid是不变的。rowid 相对于表来说又像表中的一般列,所以以 rowid 为条件就不会有 rownum那些情况发生。
另外还要注意:rownum不能以任何基表的名称作为前缀。

装箱(Boxing)和拆箱(Unboxing) 收藏
首先介绍装箱(Boxing)和拆箱(Unboxing)这两个名词。.Net的类型分为两种,一种是值类型,另一种是引用类型。这两个类型的本质区别,值类型数据是分配在栈中,而引用类型数据分配在堆上。那么如果要把一个值类型数据放到堆上,就需要装箱操作;反之,把一个放在堆上的值类型数据取出来,则需要进行拆箱操作。

例如,对于如下简单的装箱和拆箱操作语句。
int i = 123;
object obj = i;//Boxing

if( obj is int )
int j = (int) obj;//Unboxing

为了,更好的诠释装箱和拆箱操作,我借用MSDN关于“Boxing”的解释图,具体如下。


明白了这两名词的意思,现在说说为什么要减少装箱和拆箱操作。
原因有两个,主要是关于效率:一个就是对于堆的操作效率比较低;另一个就是对于堆上分配的内存资源,需要GC来回收,从而降低程序效率。

考虑到这两点因素,那么需要在程序中减少装箱和拆箱操作。
如何减少呢,涉及到这两个操作比较多的是,格式化输出操作,例如:String.Format,Console.WriteLine之类的语句。
例如:
Console.WriteLine( "Number list:{0}, {1}, {2}",
1,2,3 );

对于“1,2,3”来说,相当于前面的“123”一样,需要经过装箱和拆箱两个操作。那么如何避免呢,其实只要向WriteLine传递引用类型数据即可,也就是按照如下的方式。
Console.WriteLine( "Number list:{0}, {1}, {2}",
1.ToString(),2.ToString(),3.ToString() );

由于“1.ToString()”的结果是String类型,属于引用类型,因此不牵扯装箱和拆箱操作。

其次,牵扯到装箱和拆箱操作比较多的就是在集合中,例如:ArrayList或者HashTable之类。
把值类型数据放到集合中,可能会出现潜在错误。例如:
public struct Person
{
private string _Name;
public string Name
{
get{ return _Name; }
set{ _Name = value; }
}

public Person( string PersonName )
{
_Name = PersonName;
}

public override string ToString()
{
return _Name;
}

}

ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );
arrPersons.Add( p );


p = ( Person ) arrPersons[0] ;
p.Name = "NewName";

Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "OldName"

这个问题其实在前面的文章中已经讲过了。有人可能会说,是否可以按照如下的方式去修改呢。
( (Person ) arrPersons[0] ).Name = "NewName";//Can't be compiled

很不幸,如上操作不能通过编译。为什么呢,对于“( (Person ) arrPersons[0] )”来说,是系统用一个临时变量来接收拆箱后的值类型数据,那么由于值类型是分配在栈上,那么操作是对实体操作,可是系统不允许对一个临时值类型数据进行修改操作。

ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );
arrPersons.Add( p );

// Try to change the name
p = ( Person ) arrPersons[0] ;
p.Name = "NewName";

arrPersons.RemoveAt( 0 );//Remove old data first
arrPersons.Insert( 0, p );//Add new data
Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "NewName"

其实,这样操作会产生过多装箱和拆箱操作。那么更好的方法,可以通过接口来完成,从而减少装箱和拆箱操作。对于这个例子的接口实现应该如下。
public interface IPersonName
{
string Name{ get;set;}
}

public struct Person:IPersonName
{
private string _Name;
public string Name
{
get{ return _Name; }
set{ _Name = value; }
}

public Person( string PersonName )
{
_Name = PersonName;
}

public override string ToString()
{
return _Name;
}
}


ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );
arrPersons.Add( p );

// Change the name
( (IPersonName)arrPersons[0] ).Name = "NewName";

Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "NewName"

很多人就问,为什么值类型不能修改,即
( (Person ) arrPersons[0] ).Name = "NewName";//Can't be compiled
而如上的接口类型就能修改呢,即
( (IPersonName)arrPersons[0] ).Name = "NewName";
这是由于产生的临时变量的类型不同,前者已经在前面进行说明了,后者由于产生的临时变量的类型为IPersonName,属于引用类型,那么相当于临时变量就是原对象的引用,那么对于对于它的修改会直接修改到原对象,因此是可以的。可以说这里的不同本身在于产生临时对象的类型不同,从而造成本质的区别。

通过接口来改写,这样就减少了装箱和拆箱操作,同时也保证了修改的正确性。不过要注意的是,这里接口对于的是引用类型,如果接口访问的或者返回的是值类型,那么用接口虽说能实现了,但是对于装箱和拆箱操作来说,并没有减少。

对于装箱和拆箱操作来说,基本上就讲完了,只要记住频繁装箱和拆箱操作会降低程序效率,因此在编写的时候要尽量避免。

数据库多表连接查询详解 收藏
通过连接运算符可以实现多个表查询。连接是关系数据库模型的主要特点,也是它区别于其它类型数据库管理系统的一个标志。

在关系数据库管理系统中,表建立时各数据之间的关系不必确定,常把一个实体的所有信息存放在一个表中。当检索数据时,通过连接操作查询出存放在多个表中的不同实体的信息。连接操作给用户带来很大的灵活性,他们可以在任何时候增加新的数据类型。为不同实体创建新的表,尔后通过连接进行查询。

连接可以在SELECT 语句的FROM子句或WHERE子句中建立,似是而非在FROM子句中指出连接时有助于将连接操作与WHERE子句中的搜索条件区分开来。所以,在Transact-SQL中推荐使用这种方法。

SQL-92标准所定义的FROM子句的连接语法格式为:

FROM join_table join_type join_table

[ON (join_condition)]

其中join_table指出参与连接操作的表名,连接可以对同一个表操作,也可以对多表操作,对同一个表操作的连接又称做自连接。

join_type 指出连接类型,可分为三种:内连接、外连接和交叉连接。内连接(INNER JOIN)使用比较运算符进行表间某(些)列数据的比较操作,并列出这些表中与连接条件相匹配的数据行。根据所使用的比较方式不同,内连接又分为等值连接、自然连接和不等连接三种。

外连接分为左外连接(LEFT OUTER JOIN或LEFT JOIN)、右外连接(RIGHT OUTER JOIN或RIGHT JOIN)和全外连接(FULL OUTER JOIN或FULL JOIN)三种。与内连接不同的是,外连接不只列出与连接条件相匹配的行,而是列出左表(左外连接时)、右表(右外连接时)或两个表(全外连接时)中所有符合搜索条件的数据行。

交叉连接(CROSS JOIN)没有WHERE 子句,它返回连接表中所有数据行的笛卡尔积,其结果集合中的数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。

连接操作中的ON (join_condition) 子句指出连接条件,它由被连接表中的列和比较运算符、逻辑运算符等构成。
无论哪种连接都不能对text、ntext和image数据类型列进行直接连接,但可以对这三种列进行间接连接。
(一)内连接

内连接查询操作列出与连接条件匹配的数据行,它使用比较运算符比较被连接列的列值。内连接分三种:

1、等值连接:在连接条件中使用等于号(=)运算符比较被连接列的列值,其查询结果中列出被连接表中的所有列,包括其中的重复列。

2、不等连接: 在连接条件使用除等于运算符以外的其它比较运算符比较被连接的列的列值。这些运算符包括>、>=、<=、<、!>、!<和<>。

3、自然连接:在连接条件中使用等于(=)运算符比较被连接列的列值,但它使用选择列表指出查询结果集合中所包括的列,并删除连接表中的重复列。

例,下面使用等值连接列出authors和publishers表中位于同一城市的作者和出版社:

SELECT *

FROM authors AS a INNER JOIN publishers AS p

ON a.city=p.city


又如使用自然连接,在选择列表中删除authors 和publishers 表中重复列(city和state):

SELECT a.*,p.pub_id,p.pub_name,p.country

FROM authors AS a INNER JOIN publishers AS p

ON a.city=p.city

(二)外连接

内连接时,返回查询结果集合中的仅是符合查询条件( WHERE 搜索条件或 HAVING 条件)和连接条件的行。而采用外连接时,它返回到查询结果集合中的不仅包含符合连接条件的行,而且还包括左表(左外连接时)、右表(右外连接时)或两个边接表(全外连接)中的所有数据行。
外联接可以是左向外联接、右向外联接或完整外部联接。
在 FROM 子句中指定外联接时,可以由下列几组关键字中的一组指定:LEFT JOIN 或 LEFT OUTER JOIN;RIGHT JOIN 或 RIGHT OUTER JOIN;FULL JOIN 或 FULL OUTER JOIN。
(1)左向外联接:左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。
(2)右向外联接:右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
(3)完整外部联接:完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
仅当至少有一个同属于两表的行符合联接条件时,内联接才返回行。内联接消除与另一个表中的任何行不匹配的行。而外联接会返回 FROM 子句中提到的至少一个表或视图的所有行,只要这些行符合任何 WHERE 或 HAVING 搜索条件。将检索通过左向外联接引用的左表的所有行,以及通过右向外联接引用的右表的所有行。完整外部联接中两个表的所有行都将返回。

如下面使用左外连接将论坛内容和作者信息连接起来:

SELECT a.*,b.* FROM luntan LEFT JOIN usertable as b

ON a.username=b.username


下面使用全外连接将city表中的所有作者以及user表中的所有作者,以及他们所在的城市:

SELECT a.*,b.*

FROM city as a FULL OUTER JOIN user as b

ON a.username=b.username

(三)交叉连接

交叉连接不带WHERE 子句,它返回被连接的两个表所有数据行的笛卡尔积,返回到结果集合中的数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。

例,titles表中有6类图书,而publishers表中有8家出版社,则下列交叉连接检索到的记录数将等

于6*8=48行。

SELECT type,pub_name

FROM titles CROSS JOIN publishers

ORDER BY type

JAVA 内部类的简单总结
定义在一个类内部的类叫内部类,包含内部类的类称为外部类。内部类可以声明public、protected、private等访问限制,可以声明为abstract的供其他内部类或外部类继承与扩展,或者声明为static、final的,也可以实现特定的接口。static的内部类行为上象一个独立的类,非static在行为上类似类的属性或方法且禁止声明static的方法。内部类可以访问外部类的所有方法与属性,但static的内部类只能访问外部类的静态属性与方法。

外部类按常规的类访问方式使用内部类,唯一的差别是外部类可以访问内部类的所有方法与属性,包括私有方法与属性。如:
pinner p = new pinner();
p.index = 20;
p.Print();
---- 这种方式适合外部类的非static方法;

pouter po = new pouter();
pinner pi = po.new pinner();
pi.index = 40;
pi.Print();
---- 这种方式适合外部类的static方法;

内部类类似外部类的属性,因此访问内部类对象时总是需要一个创建好的外部类对象。内部类对象通过‘外部类名.this.xxx’的形式访问外部类的属性与方法。如:
System.out.println("Print in inner Outer.index=" + pouter.this.index);
System.out.println("Print in inner Inner.index=" + this.index);

如果需要在其他类中访问内部类,可以使用:
(1)外部类提供创建内部类的方法供其他类使用。如:
// 外部类
pinner getInner()
{
return new pinner();
}

// 其他类
pouter.pinner pi = po.getInner();
pi.Print();

(2)直接创建内部类的对象。如:
pouter po = new pouter();
pouter.pinner pi = po.new pinner();
pi.Print();

内部类可以声明在外部类的方法中或语句块中。如果内部类需要访问包含它的外部类方法或语句块的局部变量或参数,则该局部变量或参数必须是final的。外部类的其他方法、其他类无法访问声明在方法内部或块内部的内部类。

如果一个类继承内部类,则创建该类的对象时需提供一个外部类的对象作为构造方法的参数。如:
class Car
{
class Wheel
{

}
}

class SuperWheel extends Car.Wheel
{
SuperWheel(Car car)
{
car.super();
}

public static void main(String [] args)
{
Car car = new Car();
SuperWheel wl = new SuperWheel(car);
}
}

如果创建命名的内部类没有多少实际意义时,可以创建匿名的内部类。比如使用内部类实现接口的功能(如事件处理器、适配器等),而功能的差异较大,需要根据实际的情况创建相应的内部类时,可以使用匿名内部类。简单的示例如下:
interface WebView
{
void doGet();
}

class A
{
WebView ShowName()
{
return new WebView()
{
void doGet()
{
System.out.println("Name");
}
};
}

WebView ShowCode()
{
return new WebView()
{
void doGet()
{
System.out.println("Code");
}
};
}
}

最后,JAVA 内部类还有一个作用,那就是实现JAVA的多继承。JAVA本身是不允许多继承的,如果我们想一个类继承多个基类,就可以使用内部类。通过内部类分别继承一个基类,外部类创建内部类的对象,并使用内部类的方法,变相地实现了多继承。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值