java学习资料

2006年2月12日
JSP内建对象- -                                      

 

① out - javax.servlet.jsp.jspWriter
   out对象用于把结果输出到网页上。
方法:
1. void clear() ;
   清除输出缓冲区的内容,但是不输出到客户端。
2. void clearBuffer() ;
   清除输出缓冲区的内容,并输出到客户端。
3. void close() ;
   关闭输出流,清除所有内容。
4. void flush() ;
   输出缓冲区里面的数据。
5. int getBufferSize() ;
   获取以kb为单位的目前缓冲区大小。
6. int getRemaining() ;
   获取以kb为单位的缓冲区中未被占用的空间大小。
7. boolean isAutoFlush() ;
   是否自动刷新缓冲区。
8. void newLine() ;
   输出一个换行字符。
9. void print( boolean b ) ;
   void print( char c ) ;
   void print( char[] s ) ;
   void print( double d ) ;
   void print( float f ) ;
   void print( int i ) ;
   void print( long l ) ;
   void print( Object obj ) ;
   void print( String s ) ;
   将指定类型的数据输出到Http流,不换行。
10. void println( boolean b ) ;
    void println( char c ) ;
    void println( char[] s ) ;
    void println( double d ) ;
    void println( float f ) ;
    void println( int i ) ;
    void println( long l ) ;
    void println( Object obj ) ;
    void println( String s ) ;
    将指定类型的数据输出到Http流,并输出一个换行符。
   
11. Appendable append( char c ) ;
    Appendable append( CharSequence cxq, int start, int end ) ;
    Appendable append( CharSequence cxq ) ;
    将一个字符或者实现了CharSequence接口的对象添加到输出流的后面。
成员:
int DEFAULT_BUFFER = 0    - 缺省缓冲区大小
int NO_BUFFER = -1        - writer是否处于缓冲输出状态
int UNBOUNDED_BUFFER = -2 - 是否限制缓冲区大小

② request - javax.servlet.http.HttpServletRequest
   request对象包含所有请求的信息,如请求的来源、标头、cookies和请求相关的参数值等。
方法:
1. Object getAttribute( String name ) ;
   返回由name指定的属性值,该属性不存在时返回null。
2. Enumeration getAttributeNames() ;
   返回request对象的所有属性名称的集合。
3. String getAuthType() ;
   返回用来保护servlet的认证方法的名称,未受保护时返回null。
4. String getCharacterEncoding() ;
   返回请求中的字符编码方法,可以在response对象中设置。
5. int getContentLength() ;
   返回请求的BODY的长度,不能确定长度时返回-1。可以在response中设置。
6. String getContentType() ;
   返回在response中定义的内容类型。
7. String getContentPath() ;
   返回请求的路径。
8. Cookie[] getCookies() ;
   返回客户端所有的Cookie的数组。
9. Enumeration getHeaderNames() ;
   返回所有HTTP头的名称的集合。
10. Enumeration getHeaders( String name ) ;
    返回指定HTTP头的所有值的集合。
11. String getHeader( String name ) ;
    返回指定名称的HTTP头的信息。
12. long getDateHeader( String name ) ;
    返回指定名称的Data类型的HTTP头的信息。
13. int getIntHeader( String name ) ;
    返回指定名称的Int类型的HTTP头的信息。
14. ServletInputStream getInputStream() ;
    返回请求的输入流。
15. Locale getLocale() ;
    返回当前页的Locale对象,可以在response中设定。
16. Enumeration getLocales() ;
    返回请求中所有的Locale对象的集合。
17. String getLocalName() ;
    获取响应请求的服务器端主机名。
18. String getLocalAddr() ;
    获取响应请求的服务器端地址。
19. int getLocalPort() ;
    获取响应请求的服务器端端口
20. String getMethod() ;
    获取客户端向服务器端发送请求的方法(GET、POST)。
21. String getParameter( String name ) ;
    获取客户端发送给服务器端的参数值。
22. Map getParameterMap() ;
    该方法返回包含请求中所有参数的一个Map对象。
23. Enumeration getParameterNames() ;
    返回请求中所有参数的集合。
24. String[] getParameterValues( String name ) ;
    获得请求中指定参数的所有值。
25. String getQueryString() ;
    返回get方法传递的参数字符串,该方法不分解出单独的参数。
26. String getPathInfo() ;
    取出请求中处于ServletPath和QueryString之间的额外信息。
27. String getPathTranslated() ;
    返回用getPathInfo()方法取得的路径信息的实际路径。
28. String getProtocol() ;
    返回请求使用的协议。可以是HTTP1.1或者HTTP1.0。
29. BufferedReader getReader() ;
    返回请求的输入流对应的Reader对象,该方法和getInputStream()方法在一个页面中只能调用一个。
30. String getRemoteAddr() ;
    获取发出请求的客户端IP地址。
31. String getRemoteHost() ;
    获取发出请求的客户端主机名
32. String getRemoteUser() ;
    返回经过客户端验证的用户名,未经验证返回null。
33. int getRemotePort() ;
    返回发出请求的客户端主机端口。
34. String getRealPath( String path ) ;
    返回给定虚拟路径的物理路径。
35. RequestDispatcher getRequestDispatcher( String path ) ;
    按给定的路径生成资源转向处理适配器对象。
36. String getRequestedSessionId() ;
    返回请求的session的标识。
37. String RequestURI() ;
    返回发出请求的客户端地址,但是不包括请求的参数字符串。
38. StringBuffer getRequestURI() ;
    返回响应请求的服务器端地址
39. String getScheme() ;
    获取协议名称,缺省值为HTTP协议。
40. String getServerName() ;
    返回响应请求的服务器名称。
41. String getServletPath() ;
    获取客户端所请求的脚本文件的文件路径。
42. int getServerPort() ;
    获取响应请求的服务器端主机端口号。
43. void removeAttribute( String name ) ;
    在属性列表中删除指定名称的属性。
44. void setAttribute( String name, Object value ) ;
    在属性列表中添加/删除指定的属性。
45. void setCharacterEncoding( String name ) ;
    设置请求的字符编码格式。
46. HttpSession getSession() ;
    HttpSession getSession( boolean create ) ;
    获取session,如果create为true,在无session的情况下创建一个。
   
47. boolean isRequestedSessionIdFromCookie() ;
    检查请求的会话ID是否为通过Cookie传入。
48. boolean isRequestedSessionIdFromURL() ;
    检查请求的会话ID是否为通过URL传入。
49. boolean isRequestedSessionIdValid() ;
    检查请求的会话ID是否仍然有效。
50. boolean isSecure() ;
    检查请求是否使用安全链接,如果HTTPS等。
51. boolean isUserInRole( String role ) ;
    检查已经通过验证的用户是否在是role所指定的角色。
52. Principal getUserPrincipal() ;
    返回包含用户登陆名的一个java.security.Principal对象。
成员:
String BASIC_AUTH = "BASIC"             -
String CLIENT_CERT_AUTH = "CLIENT_CERT" -
String DIGEST_AUTH = "DIGEST"           -
String FORM_AUTH = "FORM"               -

③ response - javax.servlet.http.HttpServletResponse
   response对象主要将JSP容器处理后的结果传回到客户端。
方法:
1. void addCookie( Cookie cookie ) ;
   添加一个Cookie对象,保存客户端信息。
2. void addDateHeader( String name, long value ) ;
   添加一个日期类型的HTTP头信息,覆盖同名的HTTP头信息。
3. void addHeader( String name, String value ) ;
   添加一个HTTP头,覆盖同名的旧HTTP头。
4. void addIntHeader( String name, int value ) ;
   添加一个整型的HTTP头,覆盖同名的旧HTTP头。
5. boolean containsHeader( String name ) ;
   判断指定的HTTP头是否存在。
6. String encodeRedirectURL( String url ) ;
   对sendRedirect()方法使用的URL进行编码。
7. String encodeURL( String url ) ;
   将URL予以编码,回传包含session ID的URL。
  
8. void flushBuffer() ;
   强制把当前缓冲区的内容发送到客户端。
9. int getBufferSize() ;
   取得以kb为单位的缓冲区大小。
10. String getCharacterEncoding() ;
    获取响应的字符编码格式。
11. String getContentType() ;
    获取响应的类型。
12. Locale getLocale() ;
    获取响应的Locale对象。
13. ServletOutputStream getOutputStream() ;
    返回客户端的输出流对象。
14. PrintWriter getWriter() ;
    获取输出流对应的writer对象。
15. boolean isCommitted() ;
    判断服务器端是否已经将数据输出到客户端。
16. void reset() ;
    清空buffer中的所有内容。
17. void resetBuffer() ;
    情况buffer中所有的内容,但是保留HTTP头和状态信息。
18. void sendError( int xc, String msg ) ;
    void sendError( int xc ) ;
    发送错误,包括状态码和错误信息。
19. void sendRedirect( String locationg ) ;
    把响应发送到另外一个位置进行处理。
20. void setBufferSize( int size ) ;
    设置以kb为单位的缓冲区大小。
21. void setCharacterEncoding( String charset ) ;
    设置响应使用的字符编码格式。
22. void setContentLength( int length ) ;
    设置响应的BODY长度。
23. void setContentType( String type ) ;
    设置响应的类型。
24. void setDateHeader( String name, long value ) ;
    设置指定名称的Data类型的HTTP头的值。
25. void setHeader( String name, String value ) ;
    设置指定名称的HTTP头的值。
26. void setIntHeader( String name, int value ) ;
    设置指定名称的int类型的HTTP头的值。
27. void setStatus( int xc ) ;
    设置响应状态码,新值会覆盖当前值。
成员(HTTP状态码):
int SC_CONTINUE = 100                      int SC_SWITCHING_PROTOCOLS = 101
int SC_OK = 200                            int SC_NON_AUTHORITATIVE_INFORMATION = 203
int SC_ACCEPTED = 202                      int SC_CREATED = 201
int SC_NO_CONTENT = 204                    int SC_RESET_CONTENT = 205
int SC_PARTIAL_CONTENT = 206               int SC_MULTIPLE_CHOICES = 300
int SC_MOVED_PERMANENTLY = 301             int SC_MOVED_TEMPORARILY = 302
int SC_FOUND = 302                         int SC_SEE_OTHER = 303
int SC_NOT_MODIFIED = 304                  int SC_USE_PROXY = 305
int SC_TEMPORARY_REDIRECT = 307            int SC_BAD_REQUEST = 400
int SC_UNAUTHORIZED = 401                  int SC_PAYMENT_REQUIRED = 402
int SC_FORBIDDEN = 403                     int SC_NOT_FOUND = 404
int SC_METHOD_NOT_ALLOWED = 405            int SC_NOT_ACCEPTABLE = 406
int SC_PROXY_AUTHENTICATION_REQUIRED = 407 int SC_REQUEST_TIMEOUT = 408
int SC_CONFLICT = 409                      int SC_GONE = 410
int SC_LENGTH_REQUIRED = 411               int SC_PRECONDITION_FAILED = 412
int SC_REQUEST_ENTITY_TOO_LARGE = 413      int SC_REQUEST_URI_TOO_LONG = 414
int SC_UNSUPPORTED_MEDIA_TYPE = 415        int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416
int SC_EXPECTATION_FAILED = 417            int SC_INTERNAL_SERVER_ERROR = 500
int SC_NOT_IMPLEMENTED = 501               int SC_BAD_GATEWAY = 502
int SC_SERVICE_UNAVAILABLE = 503           int SC_GATEWAY_TIMEOUT = 504
int SC_HTTP_VERSION_NOT_SUPPORTED = 505

④ session - javax.servlet.http.HttpSession
   session对象表示目前个别用户的会话状态,用来识别每个用户。
方法:
1. Object getAttribute( String name ) ;
   获取与指定名字相关联的session属性值。
2. Enumeration getAttributeNames() ;
   取得session内所有属性的集合。
3. long getCreationTime() ;
   返回session的创建时间,最小单位千分之一秒。
4. String getId() ;
   取得session标识。
5. long getLastAccessedTime() ;
   返回与当前session相关的客户端最后一次访问的时间,由1970-01-01算起,单位毫秒。
6. int getMaxInactiveInterval( int interval ) ;
   返回总时间,以秒为单位,表示session的有效时间(session不活动时间)。-1为永不过期。
7. ServletContext getServletContext() ;
   返回一个该JSP页面对应的ServletContext对象实例。
8. HttpSessionContext getSessionContext() ;
  
9. Object getValue( String name ) ;
   取得指定名称的session变量值,不推荐使用。
10. String[] getValueNames() ;
    取得所有session变量的名称的集合,不推荐使用。
11. void invalidate() ;
    销毁这个session对象。
12. boolean isNew() ;
    判断一个session是否由服务器产生,但是客户端并没有使用。
13. void pubValue( String name, Object value ) ;
    添加一个session变量,不推荐使用。
14. void removeValue( String name ) ;
    移除一个session变量的值,不推荐使用。
15. void setAttribute( String name, String value ) ;
    设置指定名称的session属性值。
16. void setMaxInactiveInterval( int interval ) ;
    设置session的有效期。
17. void removeAttribute( String name ) ;
    移除指定名称的session属性。

⑤ pageContext - javax.servlet.jsp.PageContext
   pageContext对象存储本JSP页面相关信息,如属性、内建对象等。
方法:
1. void setAttribute( String name, Object value, int scope ) ;
   void setAttribute( String name, Object value ) ;
   在指定的共享范围内设置属性。
2. Object getAttribute( String name, int scope ) ;
   Object getAttribute( String name ) ;
   取得指定共享范围内以name为名字的属性值。
3. Object findAttribute( String name ) ;
   按页面、请求、会话和应用程序共享范围搜索已命名的属性。
4. void removeAttribute( String name, int scope ) ;
   void removeAttribute( String name ) ;
   移除指定名称和共享范围的属性。
5. void forward( String url ) ;
   将页面导航到指定的URL。
6. Enumeration getAttributeNamesScope( int scope ) ;
   取得指定共享范围内的所有属性名称的集合。
7. int getAttributeScope( String name ) ;
   取得指定属性的共享范围。
8. ErrorData getErrorDate() ;
   取得页面的errorData对象。
9. Exception getException() ;
   取得页面的exception对象。
10. ExpressionEvaluator getExpressionEvaluator() ;
    取得页面的expressionEvaluator对象。
11. JspWriter getOut() ;
    取得页面的out对象。
12. Object getPage() ;
    取得页面的page对象。
13. ServletRequest getRequest() ;
    取得页面的request对象。
14. ServletResponse getResponse() ;
    取得页面的response对象。
15. ServletConfig getConfig() ;
    取得页面的config对象。
16. ServletContext getServletContext() ;
    取得页面的servletContext对象。
17. HttpSession getSession() ;
    取得页面的session对象。
18. VariableResolver getVariableResolver() ;
    取得页面的variableResolver对象。
19. void include( String url, boolean flush ) ;
    void include( String url ) ;
    包含其他的资源,并指定是否自动刷新。
20. void release() ;
    重置pageContext内部状态,释放所有内部引用。
21. void initialize( Servlet servlet, ServletRequest request, ServletResponse response,
                     String errorPageURL, boolean needSession, int bufferSize, boolean autoFlush ) ;
    初始化未经初始化的pageContext对象。
22. BodyContext pushBody() ;
    BodyContext pushBody( Writer writer ) ;
    保存当前的out对象,并更新pageContext中page范围内的out对象。
23. JspWrite popBody() ;
    取出由pushBody()方法保存的out对象。
24. void handlePageException( Exception e ) ;
    void handlePageException( Thrwoable t ) ;
   
成员:
int PAGE_SCOPE = 1        - 页面共享范围
int REQUEST_SCOPE = 2     - 请求共享范围
int SESSION_SCOPE = 3     - 会话共享范围
int APPLICATION_SCOPE = 4 - 应用程序共享范围
String PAGE = "javax.servlet.jsp.jspPage"
String PAGECONTEXT = "javax.servlet.jsp.jspPageContext"
String REQUEST = "javax.servlet.jsp.jspRequest"
String RESPONSE = "javax.servlet.jsp.jspResponse"
String CONFIG = "javax.servlet.jsp.jspConfig"
String SESSION = "javax.servlet.jsp.jspSession"
String OUT = "javax.servlet.jsp.jspOut"
String APPLICATION = "javax.servlet.jsp.jspApplication"
String EXCEPTION = "javax.servlet.jsp.jspException"

⑥ application - javax.servlet.ServletContext
   application主要功用在于取得或更改Servlet的设定。
方法:
1. Object getAttribute( String name ) ;
   返回由name指定的application属性。
2. Enumeration getAttributes() ;
   返回所有的application属性。
3. ServletContext getContext( String uripath ) ;
   取得当前应用的ServletContext对象。
4. String getInitParameter( String name ) ;
   返回由name指定的application属性的初始值。
5. Enumeration getInitParameters() ;
   返回所有的application属性的初始值的集合。
6. int getMajorVersion() ;
   返回servlet容器支持的Servlet API的版本号。
7. String getMimeType( String file ) ;
   返回指定文件的类型,未知类型返回null。一般为"text/html"和"image/gif"。
8. int getMinorVersion() ;
   返回servlet容器支持的Servlet API的副版本号。
9. String getRealPath( String path ) ;
   返回给定虚拟路径所对应物理路径。
10. RequestDispatcher getNamedDispatcher( String name ) ;
    为指定名字的Servlet对象返回一个RequestDispatcher对象的实例。
11. RequestDispatcher getRequestDispatcher( String path ) ;
    返回一个RequestDispatcher对象的实例。
12. URL getResource( String path ) ;
    返回指定的资源路径对应的一个URL对象实例,参数要以"/"开头。
13. InputStream getResourceAsStream( String path ) ;
    返回一个由path指定位置的资源的InputStream对象实例。
14. Set getResourcePaths( String path ) ;
    返回存储在web-app中所有资源路径的集合。
15. String getServerInfo() ;
    取得应用服务器版本信息。
16. Servlet getServlet( String name ) ;
    在ServletContext中检索指定名称的servlet。
17. Enumeration getServlets() ;
    返回ServletContext中所有servlet的集合。
18. String getServletContextName() ;
    返回本web应用的名称。
19. Enumeration getServletContextNames() ;
    返回ServletContext中所有servlet的名称集合。
20. void log( Exception ex, String msg ) ;
    void log( String msg, Throwable t ) ;
    void log( String msg ) ;
    把指定的信息写入servlet log文件。
21. void removeAttribute( String name ) ;
    移除指定名称的application属性。
22. void setAttribute( String name, Object value ) ;
    设定指定的application属性的值。

⑦ config - javax.servlet.ServletConfig
   config对象用来存放Servlet初始的数据结构。
方法:
1. String getInitParameter( String name ) ;
   返回名称为name的促使参数的值。
2. Enumeration getInitParameters() ;
   返回这个JSP所有的促使参数的名称集合。
3. ServletContext getContext() ;
   返回执行者的servlet上下文。
4. String getServletName() ;
   返回servlet的名称。

⑧ exception - java.lang.Throwable
   错误对象,只有在JSP页面的page指令中指定isErrorPage="true"后,才可以在本页面使用exception对象。
方法:
1. Throwable fillInStackTrace() ;
   将当前stack信息记录到exception对象中。
2. String getLocalizedMessage() ;
   取得本地语系的错误提示信息。
3. String getMessage()
   取得错误提示信息。
4. StackTrackElement[] getStackTrace() ;
   返回对象中记录的call stack track信息。
5. Throwable initCause( Throwable cause ) ;
   将另外一个异常对象嵌套进当前异常对象中。
  
6. Throwable getCause() ;
   取出嵌套在当前异常对象中的异常。
7. void printStackTrace() ;
   void printStackTrace( printStream s ) ;
   void printStackTrace( printWriter s ) ;
   打印出Throwable及其call stack trace信息。
8. void setStackTrace( StackTraceElement[] stackTrace )
   设置对象的call stack trace信息。

⑨ page - javax.servlet.jsp.HttpJspPage
   page对象代表JSP对象本身,或者说代表编译后的servlet对象,
   可以用( (javax.servlet.jsp.HttpJspPage)page )来取用它的方法和属性。
posted @ 2006-02-12 11:35 活在JAVA岛的日子 阅读(235) | 评论 (0)编辑  收藏
 
  • 为什么GenericServlet在init(ServletConfig config)基础上增加了一个init()方法?
    init()方法被GenericServlet.init(ServletConfig config)方法调用。
    init()方法方便了开发人员定制Servlet的初始化,而无须去维护ServletConfig对象的存储工作。
    重写GenericServlet.init(ServletConfig config)必须要显示的调用super.init(config)方法。
  • ServletContext.getContect(java.lang.String uripath)的作用是什么?
    返回同一Server中指定的path对应的ServletContext对象,通过该对象可以实现与Server中的其他Context打交道。
    uripath必须是以"/"开始(该路径的含义是相对于整个Servlet文档的根路径,而不是当前ServletContext的根路径)。
  • Servlet生命周期是什么?
    一般的Servlet(GenericServlet,即与协议无关的Servlet)的生命周期:init() --> GenericServlet.service(ServletRequest req, ServletResponse res) --> destroy.
    HttpServlet的生命周期: init() --> GenericServlet.service(ServletRequest req, ServletResponse res)---> service(HttpServletRequest req, HttpServletResponse resp) --> doXXXX()-->destroy.
  • 有没有必要重写GenericServlet.service()方法?
    对于HttpServlet来说没有必要。只需要重写它的doXXXX()方法就可以了。HttpServlet中service()方法会自动的根据用户请求类型把请求转发给相应的doXXXX()方法(例如doGet()方法)。
  • ServletRequest.getReader()和ServletRequest.getInputStream()如何使用?
    注意两个方法不能同时使用。
  • ServletRequest.getRealPath(String path)方法已经不推荐使用。
    请使用ServletContext.getRealPath(String path)方法。
  • ServletResponse缺省的字符集(charset)是什么?
    ServletResponse缺省的字符集(charset)是ISO-8859-1,可以通过setContentType(java.lang.String)方法改变新的字符集。
    例如:setContentType("text/html; charset=Shift_JIS").
    关于字符集信息,可以浏览 RFC 2045
  • HttpServletRequest.getRequestURI()和HttpServletRequest.getRequestURL()区别是什么?
    request.getRequestURI() 返回值类似:/xuejava/requestdemo.jsp
    request.getRequestURL() 返回值类似:http://localhost:8080/xuejava/requestdemo.jsp
  • HttpServletRequest.encodeURL()和HttpServletRequest.encodeRedirectURL(()区别是什么?为什么要有两个不同的方法呢?
    当用URL-rewriting方式来管理Session的时候,需要用到以上的两个方法。
    两个方法的不同点是:两个方法确定是否需要包含session ID的逻辑不同。
    在调用HttpServletResponse.sendRedirect前,应该先调用encodeRedirectURL()方法,否则可能会丢失Sesssion信息。 ...
  • 如何使你的Servlet或者JSP实现Single Thread Model?
    对于Servlet实现javax.single.SingleThreadModel接口。
    对于JSP,在Page Directive中写如下的语句
  • JSP Tag 和 JSP XML-based Tag
    ...
  • 如何把某一个JSP Page定义成为Error Page?为什么要这样做?
    实现方法:
    为什么? 因为需要获取Exception 对象(缺省情况下,在JSP Page中是不能直接使用“隐含对象” exception的)。
  • JSP Page的执行顺序是如何的?
    JSP Page的执行顺序如下:
    • JSP Page Translation. JSP Page --> Servlet source code.
    • JSP Page Compilation. Servlet source code --> Servlet class.
    • Load Class(First time or the server restarted)
    • Create instance(可能会很多次,如果JSP Page中声明了 )
    • Call jspInit method(一般的JSP Page都没有重写这个方法,重写需要在声明语句段中)。
    • Call _jspService method(类似与一般HttpServlet的doGet和doPost方法,但是可以同时用来处理Post和Getq请求)。
    • Call jspDestroy method(Server在卸载Servet的时候,例如当Servlet很久没有使用的情况)。
  • JSP Page中有哪些隐含对象(Implicity Object)?各自的类型和作用是什么?

     

    • request --
    • reponse --
    • session --
    • application --
    • out --
    • page --
    • pagecontext --
    • exception -- 只有在当前JSP Page为Error Page的时候才有效。
    • config --
  • 和 <@ include file="/foo/foo.jsp" %>的区别是什么?
    <@ include ... -- Page translation time.
  • Servlets/JSP Container(Engine)有几种运行方式?

     

    • Standalone
      Tomcat standalone mode
    • In-process
      Tomcat running inside Apache Web Server.
    • Out-of-process
      Apache + mod_jk + Tomcat
  • Servlet,Servlet开发人员,Servlet API, Servlet Container的关系是什么?
    Servlet,Servlet开发人员 --->Servlet API --> Servlet Container
  • The parts of an HTTP message
     
        Message part Description
        The initial line: Specifies the purpose of the request or response message
                           例子:GET /reports/sales/index.html HTTP/1.0 
        The header section:Specifies the meta-information, such as size, type, and encoding, 
                           about the content of the message
        A blank line: 
        An optional message body: The main content of the request or response message
    
    下面是一个Response的例子:
     
        
          
    HTTP/1.0 200 OK Date: Tue, 01 Dec 2001 23:59:59 GMT Content-Type: text/html Content-Length: 52
    Hello, John!

  • HTTP规范中定义了哪些方法?各自有什么用途?
    • GET
    • HEAD
    • POST
      从 Http 1.1规范开始,增加了以下的方法:
    • PUT
    • OPTIONS
    • TRACE
    • DELETE
    • CONNECT
  • ServetRequest中为什么要定义:getContentType(),getContentLength()方法。
    根据HTTP协议规范,Request 和 Response一样也有这些必不可少的内容!
    所以需要首先了解 HTTP Message的概念和其内容的格式,这些东西对于Request和Reponse是一样的。
    对于GET方式发送的请求,其内容类型为:null
    对于POST方式发送的请求,其内容类型为:application/x-www-form-urlencoded

    POST方式发送请求的内容类似于:username=xuejava.
  • RequestDispatcher.forward()和HttpServletResponse.sendRedirect()的区别是什么?
    RequestDispatcher.forward()是在服务器端运行;HttpServletResponse.sendRedirect()是通过向客户浏览器发送命令来完成。
    所以RequestDispatcher.forward()对于浏览器来说是“透明的”;而HttpServletResponse.sendRedirect()则不是。
    另外,还要注意RequestDispatcher.forward()在调用的时候Response不能已经Commit了(Response.isCommitted())。
  • ServletContext.getRequestDispatcher(String url)和ServletRequest.getRequestDispatcher(String url)的区别是什么?为什么?
    ServletContext.getRequestDispatcher(String url)中的url只能使用绝对路径;而ServletRequest.getRequestDispatcher(String url)中的url可以使用相对路径。
    因为ServletRequest具有相对路径的概念;而ServletContext对象无次概念。
  • 如何把请求转移到另外一个Web App中的某个地址?
    ServletContext.getRequestDispatcher(String url)和ServletRequest.getRequestDispatcher(String url)只能把请求转移到同一个Web App中的地址。
    如果需要把请求转移到另外一个Web App中的某个地址,可以按下面的做法:
    1. 获得另外一个Web App的ServletConext对象(currentServletContext.getContext(uripath)).
    2. 调用ServletContext.getRequestDispatcher(String url)方法。
posted @ 2006-02-12 11:29 活在JAVA岛的日子 阅读(50) | 评论 (0)编辑  收藏
 
2005年12月6日
全面理解String(JAVA复习)- -
                                      

 

1.      首先String不属于8种基本数据类型,String是一个对象。

因为对象的默认值是null,所以String的默认值也是null

但它又是一种特殊的对象,有其它对象没有的一些特性。

2.      new String()new String(“”)都是申明一个新的空字符串,是空串不是null

3.      String str=”kvill”

String str=new String (“kvill”);的区别:

在这里,我们不谈堆,也不谈栈,只先简单引入常量池这个简单的概念。

常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

看例1

        String s0=”kvill”;

        String s1=”kvill”;

        String s2=”kv” + “ill”;

        System.out.println( s0==s1 );

System.out.println( s0==s2 );

        结果为:

true

true

       首先,我们要知道Java会确保一个字符串常量只有一个拷贝。

因为例子中的s0s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1true;而”kv””ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”kvill”的一个引用。

所以我们得出s0==s1==s2;

new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

看例2

        String s0=”kvill”;

        String s1=new String(”kvill”);

        String s2=”kv” + new String(“ill”);

        System.out.println( s0==s1 );

System.out.println( s0==s2 );

System.out.println( s1==s2 );

        结果为:

false

false

false

       2s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分new String(“ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。

4.      String.intern()

再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。Stringintern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了

3

        String s0= “kvill”;

        String s1=new String(”kvill”);

        String s2=new String(“kvill”);

        System.out.println( s0==s1 );

        System.out.println( “**********” );

       s1.intern();

        s2=s2.intern();             //把常量池中“kvill”的引用赋给s2

        System.out.println( s0==s1);

System.out.println( s0==s1.intern() );

System.out.println( s0==s2 );

        结果为:

false      

**********

false       //虽然执行了s1.intern(),但它的返回值没有赋给s1

true    //说明s1.intern()返回的是常量池中”kvill”的引用

true

      

       最后我再破除一个错误的理解:

有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中

如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址

如果在表中没有相同值的字符串,则将自己的地址注册到表中“

如果我把他说的这个全局的String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

看例4

        String s1=new String("kvill");

        String s2=s1.intern();

System.out.println( s1==s1.intern() );

System.out.println( s1+" "+s2 );

System.out.println( s2==s1.intern() );

结果:

false

kvill kvill

true

在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”

当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。

       s1==s1.intern()false说明原来的“kvill”仍然存在;

       s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()true

5.      关于equals()==:

这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。

6.      关于String是不可变的。

这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,

比如说:String str=”kv”+”ill”+” “+”ans”;

就是有 4 个字符串常量,首先 ”kv” ”ill” 生成了 ”kvill” 存在内存中,然后 ”kvill” 又和 ” “ 生成 ”kvill “ 存在内存中,最后又和生成了 ”kvill ans”; 并把这个字符串的地址赋给了 str, 就是因为 String 的“不可变”产生了很多临时变量,这也就是为什么建议用 StringBuffer 的原因了,因为 StringBuffer 是可改变的

 
posted @ 2005-12-06 22:28 活在JAVA岛的日子 阅读(119) | 评论 (0)编辑  收藏
 
2005年11月25日
Vector、ArrayList和List的异同[zz]
线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。

 

Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection接口
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
    Iterator it = collection.iterator(); // 获得一个迭代子
    while(it.hasNext()) {
      Object obj = it.next(); // 得到下一个元素
    }
  由Collection接口派生的两个接口是List和Set。
List接口
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
LinkedList类
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
    List list = Collections.synchronizedList(new LinkedList(...));
ArrayList类
  ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
Vector类
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
Stack 类
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
Set接口
  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
Map接口
  请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Hashtable类
  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
    Hashtable numbers = new Hashtable();
    numbers.put(“one”, new Integer(1));
    numbers.put(“two”, new Integer(2));
    numbers.put(“three”, new Integer(3));
  要取出一个数,比如2,用相应的key:
    Integer n = (Integer)numbers.get(“two”);
    System.out.println(“two = ” + n);
  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
  Hashtable是同步的。
HashMap类
  HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
WeakHashMap类
  WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
总结
  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

[b]同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
posted @ 2005-11-25 01:05 活在JAVA岛的日子 阅读(289) | 评论 (0)编辑  收藏
 
2005年11月24日
ArrayList是List接口的一个可变长数组实现。实现了所有List接口的操作,并允许存储null值。除了没有进行同步,ArrayList基本等同于Vector。在Vector中几乎对所有的方法都进行了同步,但ArrayList仅对writeObject和readObject进行了同步,其它比如add(Object)、remove(int)等都没有同步。

1.存储
ArrayList使用一个Object的数组存储元素。
private transient Object elementData[];
ArrayList实现了java.io.Serializable接口,这儿的transient标示这个属性不需要自动序列化。下面会在writeObject()方法中详细讲解为什么要这样作。

2.add和remove


    public boolean add(Object o) { 
    ensureCapacity(size + 1);  // Increments modCount!! 
    elementData[size++] = o; 
    return true; 
    } 

注意这儿的ensureCapacity()方法,它的作用是保证elementData数组的长度可以容纳一个新元素。在“自动变长机制”中将详细讲解。

    public Object remove(int index) { 
    RangeCheck(index); 
    modCount++; 
    Object oldValue = elementData[index]; 
    int numMoved = size - index - 1; 
    if (numMoved > 0) 
        System.arraycopy(elementData, index+1, elementData, index, 
                 numMoved); 
    elementData[--size] = null; // Let gc do its work 
    return oldValue; 
    } 

RangeCheck()的作用是进行边界检查。由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责。
modCount的作用将在下面的“iterator()中的同步”中说明。
注:在前移时使用了System提供的一个实用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以对同一个数组进行操作,这个方法是一个native方法,如果对同一个数组进行操作时,会首先把从源部分拷贝到一个临时数组,在把临时数组的元素拷贝到目标位置。

3.自动变长机制
在实例化一个ArrayList时,你可以指定一个初始容量。这个容量就是elementData数组的初始长度。如果你使用:

    ArrayList list = new ArrayList(); 

则使用缺省的容量:10。

    public ArrayList() { 
    this(10); 
    } 

ArrayList提供了四种add()方法,

public boolean add(Object o)

public void add(int index, Object element)

public boolean addAll(Collection c)

public boolean addAll(int index, Collection c)

在每一种add()方法中,都首先调用了一个ensureCapacity(int miniCapacity)方法,这个方法保证elementData数组的长度不小于miniCapacity。ArrayList的自动变长机制就是在这个方法中实现的。

    public void ensureCapacity(int minCapacity) { 
    modCount++; 
    int oldCapacity = elementData.length; 
    if (minCapacity > oldCapacity) { 
        Object oldData[] = elementData; 
        int newCapacity = (oldCapacity * 3)/2 + 1; 
            if (newCapacity < minCapacity) 
        newCapacity = minCapacity; 
        elementData = new Object[newCapacity]; 
        System.arraycopy(oldData, 0, elementData, 0, size); 
    } 
    } 

从这个方法实现中可以看出ArrayList每次扩容,都扩大到原来大小的1.5倍。
每种add()方法的实现都大同小异,下面给出add(Object)方法的实现:

    public boolean add(Object o) { 
    ensureCapacity(size + 1);  // Increments modCount!! 
    elementData[size++] = o; 
    return true; 
    } 


4.iterator()中的同步
在父类AbstractList中定义了一个int型的属性:modCount,记录了ArrayList结构性变化的次数。

    protected transient int modCount = 0; 

在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。

AbstractList中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,生成一个Itr对象(Iterator接口)返回:

    public Iterator iterator() { 
    return new Itr(); 
    } 

Itr实现了Iterator()接口,其中也定义了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。

    int expectedModCount = modCount; 

注:内部成员类Itr也是ArrayList类的一个成员,它可以访问所有的AbstractList的属性和方法。理解了这一点,Itr类的实现就容易理解了。

在Itr.hasNext()方法中:

    public boolean hasNext() { 
        return cursor != size(); 
    } 

调用了AbstractList的size()方法,比较当前光标位置是否越界。

在Itr.next()方法中,Itr也调用了定义在AbstractList中的get(int)方法,返回当前光标处的元素:

    public Object next() { 
        try { 
        Object next = get(cursor); 
        checkForComodification(); 
        lastRet = cursor++; 
        return next; 
        } catch(IndexOutOfBoundsException e) { 
        checkForComodification(); 
        throw new NoSuchElementException(); 
        } 
    } 

注意,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查:

    final void checkForComodification() { 
        if (modCount != expectedModCount) 
        throw new ConcurrentModificationException(); 
    } 

现在对modCount和expectedModCount的作用应该非常清楚了。在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作,这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。在AbstractList中,使用了一个简单的机制来规避这些风险。这就是modCount和expectedModCount的作用所在。

5.序列化支持
ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。ArrayList的主要属性定义如下:

private static final long serialVersionUID = 8683452581122892189L;

private transient Object elementData[];

private int size;

可以看出serialVersionUID和size都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。

    private synchronized void writeObject(java.io.ObjectOutputStream s) 
        throws java.io.IOException{ 
    // Write out element count, and any hidden stuff 
    s.defaultWriteObject(); 
   // Write out array length 
    s.writeInt(elementData.length); 
    // Write out all elements in the proper order. 
    for (int i=0; i<size; i++) 
            s.writeObject(elementData[i]); 
    } 

这样元素数组elementData中的所以元素对象就可以正确地序列化到存储介质了。
对应的readObject()也按照writeObject()方法的顺序从输入流中读取:

    private synchronized void readObject(java.io.ObjectInputStream s) 
        throws java.io.IOException, ClassNotFoundException { 
    // Read in size, and any hidden stuff 
    s.defaultReadObject(); 
    // Read in array length and allocate array 
    int arrayLength = s.readInt(); 
    elementData = new Object[arrayLength]; 
    // Read in all elements in the proper order. 
    for (int i=0; i<size; i++) 
            elementData[i] = s.readObject(); 
    } 

本章介绍Java的实用工具类库java.util包。在这个包中,Java提供了一些实用的方法和数据结构。例如,Java提供日期(Data)类、日历(Calendar)类来产生和获取日期及时间,提供随机数(Random)类产生各种类型的随机数,还提供了堆栈(Stack)、向量(Vector) 、位集合(Bitset)以及哈希表(Hashtable)等类来表示相应的数据结构。
  图1.1给出了java.util包的基本层次结构图。下面我们将具体介绍其中几个重要的类。
           ┌java.util.BitSet
           │java.util.Calendar
           │      └java.util.GregorianCalendar
           │java.util.Date
           │java.util.Dictionary
           │      └java.util.Hashtable
           │             └java.util.Properties
           │java.util.EventObject
           │java.util.ResourceBundle
       ┌普通类┤      ├java.util.ListResourceBundle
       │   │      └java.util.PropertyResourceBundle
       │   │java.util.Local
       │   │java.util.Observable
       │   │java.util.Random
       │   │java.util.StringTokenizer
       │   │java.util.Vector
       │   │      └java.util.Stack
  Java.util┤   └java.util.TimeZone
       │          └java.util.SimpleTimeZone
       │   ┌java.util.Enumeration
       ├接 口┤java.util.EventListener
       │   └java.util.Observer
       │   ┌java.util.EmptyStackException
       └异常类┤java.util.MissingResourceException
           │java.util.NoSuchElementException
           └java.util.TooManyListenersException
       图1.1 java.util包的基本层次结构


1.2 日期类Date

  Java在日期类中封装了有关日期和时间的信息,用户可以通过调用相应的方法来获取系统时间或设置日期和时间。Date类中有很多方法在JDK1.0公布后已经过时了,在8.3中我们将介绍JDK1.0中新加的用于替代Date的功能的其它类。
  在日期类中共定义了六种构造函数。
  (1)public Date()
  创建的日期类对象的日期时间被设置成创建时刻相对应的日期时间。
  例 Date today=new Date();//today被设置成创建时刻相对应的日期时间。
  (2)public Date (long date)
  long 型的参数date可以通过调用Date类中的static方法parse(String s)来获得。
  例 long l=Date.parse("Mon 6 Jan 1997 13:3:00");
    Date day=new Date(l);
  //day中时间为1997年 1月6号星期一,13:3:00。
  (3)public Date(String s)
  按字符串s产生一日期对象。s的格式与方法parse中字符串参数的模式相同。
  例 Date day=new Date("Mon 6 Jan 1997 13:3:00");
  //day 中时间为1997年1月6号星期一,13:3:00.
  (4)public Date(int year,int month,int date)
  (5)public Date(int year,int month,int date,int hrs,int min)
  (6)public Date(int year,int month,int date,int hrs,int min,int sec)
  按给定的参数创建一日期对象。
  参数说明:
  year的值为:需设定的年份-1900。例如需设定的年份是1997则year的值应为97,即1997-1900的结果。所以Date中可设定的年份最小为1900;
  month的值域为0~11,0代表1月,11表代表12月;
  date的值域在1~31之间;
  hrs的值域在0~23之间。从午夜到次日凌晨1点间hrs=0,从中午到下午1点间hrs=12;
  min和sec的值域在0~59之间。
  例 Date day=new Date(11,3,4);
  //day中的时间为:04-Apr-11 12:00:00 AM
另外,还可以给出不正确的参数。
  例 设定时间为1910年2月30日,它将被解释成3月2日。
  Date day=new Date(10,1,30,10,12,34);
  System.out.println("Day's date is:"+day);
  //打印结果为:Day's date is:Web Mar 02 10:13:34 GMT+08:00 1910
  下面我们给出一些Date类中常用方法。
  (1)public static long UTC(int year,int month,int date,int hrs. int min,int sec)
  该方法将利用给定参数计算UTC值。UTC是一种计时体制,与GMT(格林威治时间)的计时体系略有差别。UTC计时体系是基于原子时钟的,而GTMT计时体系是基于天文学观测的。计算中使用的一般为GMT计时体系。
  (2)public static long parse(String s)
  该方法将字符串s转换成一个long型的日期。在介绍构造方法Date(long date)时曾使用过这个方法。
  字符串s有一定的格式,一般为:
  (星期 日 年 时间GMT+时区)
  若不注明时区,则为本地时区。
  (3)public void setMonth(int month)
  (4)public int getMonth()
  这两个方法分别为设定和获取月份值。
  获取的月份的值域为0~11,0代表1月,11代表12月。
  (5)public String toString()
  (6)public String toLocalString()
  (7)public String toGMTString()
  将给定日期对象转换成不同格式的字符串。它们对应的具体的格式可参看例子8.1。
  (8)public int getTimezoneOffset()
  该方法用于获取日期对象的时区偏移量。
  例8.1中对上面介绍的Date类中的基本方法进行了具体的应用,并打印了相应的结果。由于使用了一些过时的方法,所以编译时会有警告信息。另外,由于本例中的时间表示与平台有关,不同的JDK版本对此处理不完全相同,因此不同版本的JDK执行本例的结果可能有细微差异。
  例1.1 DateApp.java
  import java.lang.System;
  import java.util.Date;
  public class DateApp{
   public static void main(String args[]){
    Date today=new Date();
    //today中的日期被设成创建时刻的日期和时间,假设创建时刻为1997年3月
    //23日17时51分54秒。
    System.out.println("Today's date is "+today);
    //返回一般的时间表示法,本例中结果为
    //Today's date is Fri May 23 17:51:54 1997
    System.out.println("Today's date(Internet GMT)is:"
     +today.toGMTString());
    //返回结果为GMT时间表示法,本例中结果为
    //Today's date(Internet GMT)is: 23 May 1997 09:51:54:GMT
    System.out.println("Today's date(Locale) is:"
     +today.toLocaleString());
    //返回结果为本地习惯的时间表示法,结果为
    //Today's date(Locale)is:05/23/97 17:51:54
    System.out.println("Today's year is: "+today.getYear());
    System.out.println("Today's month is: "+(today.getMonth()+1));
    System.out.println("Today's date is: "+today.getDate());
    //调用Date类中方法,获取年月日的值。
    //下面调用了不同的构造方法来创建Date类的对象。
    Date day1=new Date(100,1,23,10,12,34);
    System.out.println("Day1's date is: "+day1);
    Date day2=new Date("Sat 12 Aug 1996 13:3:00");
    System.out.println("Day2's date is: "+day2);
    long l= Date.parse("Sat 5 Aug 1996 13:3:00 GMT+0800");
    Date day3= new Date(l);
    System.out.println("Day3's date(GMT)is: "+day3.toGMTString());
    System.out.println("Day3's date(Locale)is: "
     +day3.toLocaleString());
    System.out.println("Day3's time zone offset is:"
     +day3.getTimezoneOffset());
   }
  }

  运行结果(JDK1.3版,与原文不同,原文是JDK1.0版):
  E:/java/tutorial/java01>java DateApp
  Today's date is Thu Dec 27 17:58:16 CST 2001
  Today's date(Internet GMT)is:27 Dec 2001 09:58:16 GMT
  Today's date(Locale) is:2001-12-27 17:58:16
  Today's year is: 101
  Today's month is: 12
  Today's date is: 27
  Day1's date is: Wed Feb 23 10:12:34 CST 2000
  Day2's date is: Fri Aug 12 13:03:00 CST 1996
  Day3's date(GMT)is: 5 Aug 1996 05:03:00 GMT
  Day3's date(Locale)is: 1996-8-5 13:03:00
  Day3's time zone offset is:-480

  E:/java/tutorial/java01>

1.3 日历类Calendar

  在早期的JDK版本中,日期(Date)类附有两大功能:(1)允许用年、月、日、时、分、秒来解释日期:(2)允许对表示日期的字符串进行格式化和句法分析。在JDK1.1中提供了类Calendar来完成第一种功能,类DateFormat来完成第二项功能。dateFormat是java.text包中的一个类。与Date类有所不同的是,DateFormat类接受用各种语言和不同习惯表示的日期字符串。本节将介绍java.util包中的类Calendar及其它新增加的相关的类。
  类Calendar是一个抽象类,它完成日期(Date)类和普通日期表示法(即用一组整型域如YEAR,MONTH,DAY,HOUR表示日期)之间的转换。
  由于所使用的规则不同,不同的日历系统对同一个日期的解释有所不同。在JDK1.1中提供了Calendar类一个子类GregorianCalendar??它实现了世界上普遍使用的公历系统。当然用户也可以通过继承Calendar类,并增加所需规则,以实现不同的日历系统。
  第GregorianCalendar继承了Calendar类。本节将在介绍类GregorianCalendar的同时顺带介绍Calendar类中的相关方法。
  类GregorianCalendar提供了七种构造函数:
  (1)public GregorianCalendar()
  创建的对象中的相关值被设置成指定时区,缺省地点的当前时间,即程序运行时所处的时区、地点的当前时间。
  (2)public GregorianCalendar(TimeZone zone)
  创建的对象中的相关值被设置成指定时区zone,缺省地点的当前时间。
  (3)public GregorianCalendar(Locale aLocale)
  创建的对象中的相关值被设置成缺省时区,指定地点aLocale的当前时间。
  (4)public GregorianCalendar(TimeZone zone,Local aLocale)
  创建的对象中的相关值被设置成指定时区,指定地点的当前时间。
  上面使用到的类TimeZone的性质如下:
  TimeZone是java.util包中的一个类,其中封装了有关时区的信息。每一个时区对应一组ID。类TimeZone提供了一些方法完成时区与对应ID两者之间的转换。
  (Ⅰ)已知某个特定的ID,可以调用方法
  public static synchronized TimeZone getTimeZone(String ID)
来获取对应的时区对象。
  例 太平洋时区的ID为PST,用下面的方法可获取对应于太平洋时区的时区对象:
  TimeZone tz=TimeZone.getTimeZone("PST");
  调用方法getDefault()可以获取主机所处时区的对象。
  TimeZone tz=TimeZone.getDefault();
  (Ⅱ)调用以下方法可以获取时区的ID
  ■public static synchronized String[] getavailableIDs(int rawOffset)
  根据给定时区偏移值获取ID数组。同一时区的不同地区的ID可能不同,这是由于不同地区对是否实施夏时制意见不统一而造成的。
  例String s[]=TimeZone.getAvailableIDs(-7*60*60*1000);
  打印s,结果为s[0]=PNT,s[1]=MST
  ■public static synchronized String[] getAvailableIDs()
  获取提供的所有支持的ID。
  ■public String getID()
  获取特定时区对象的ID。
  例 TimeZone tz=TimeZone.getDefault();
  String s=tz.getID();
  打印s,结果为s=CTT。
  上面使用类的对象代表了一个特定的地理、政治或文化区域。Locale只是一种机制,它用来标识一类对象,Local本身并不包含此类对象。
  要获取一个Locale的对象有两种方法:
  (Ⅰ)调用Locale类的构造方法
  Locale(String language,String country)
  Locale(String language,String country,String variant)
  参数说明:language??在ISO-639中定义的代码,由两个小写字母组成。
       country??在ISO-3166中定义的代码,由两个大写字母组成。
       variant??售货商以及特定浏览器的代码,例如使用WIN代表Windows。
  (Ⅱ)调用Locale类中定义的常量
  Local类提供了大量的常量供用户创建Locale对象。
  例 Locale.CHINA
    为中国创建一个Locale的对象。
  类TimeZone和类Locale中的其它方法,读者可查阅API。
  (5)public GregorianCalendar(int year,int month,int date)
  (6)public GregorianCalendar(int year,int month,int date,int hour,int minute)
  (7)public GregorianCalendar(int year,int month,int date,int hour,int minute,int second)
  用给定的日期和时间创建一个GregorianCalendar的对象。
  参数说明:
  year-设定日历对象的变量YEAR;month-设定日历对象的变量MONTH;
  date-设定日历对象的变量DATE;hour-设定日历对象的变量HOUR_OF_DAY;
  minute-设定日历对象的变量MINUTE;second-设定日历对象的变量SECOND。
  与Date类中不同的是year的值没有1900这个下限,而且year的值代表实际的年份。month的含义与Date类相同,0代表1月,11代表12月。
  例 GregorianCalendar cal=new GregorianCalendar(1991,2,4)
  cal的日期为1991年3月4号。
  除了与Date中类似的方法外,Calendar类还提供了有关方法对日历进行滚动计算和数学计算。计算规则由给定的日历系统决定。进行日期计算时,有时会遇到信息不足或信息不实等特殊情况。Calendar采取了相应的方法解决这些问题。当信息不足时将采用缺省设置,在GregorianCalendar类中缺省设置一般为YEAR=1970,MONTH=JANUARY,DATE=1。
  当信息不实时,Calendar将按下面的次序优先选择相应的Calendar的变量组合,并将其它有冲突的信息丢弃。
  MONTH+DAY_OF_MONTH
  MONTH+WEEK_OF_MONTH+DAY_OF_WEEK
  MONTH+DAY_OF_WEEK_OF_MONTH+DAY_OF_WEEK
  DAY_OF+YEAR
  DAY_OF_WEEK_WEEK_OF_YEAR
  HOUR_OF_DAY

1.4 随机数类Random

  Java实用工具类库中的类java.util.Random提供了产生各种类型随机数的方法。它可以产生int、long、float、double以及Goussian等类型的随机数。这也是它与java.lang.Math中的方法Random()最大的不同之处,后者只产生double型的随机数。
  类Random中的方法十分简单,它只有两个构造方法和六个普通方法。
  构造方法:
  (1)public Random()
  (2)public Random(long seed)
  Java产生随机数需要有一个基值seed,在第一种方法中基值缺省,则将系统时间作为seed。
  普通方法:
  (1)public synonronized void setSeed(long seed)
  该方法是设定基值seed。
  (2)public int nextInt()
  该方法是产生一个整型随机数。
  (3)public long nextLong()
  该方法是产生一个long型随机数。
  (4)public float nextFloat()
  该方法是产生一个Float型随机数。
  (5)public double nextDouble()
  该方法是产生一个Double型随机数。
  (6)public synchronized double nextGoussian()
  该方法是产生一个double型的Goussian随机数。
  例1.2 RandomApp.java。
  //import java.lang.*;
  import java.util.Random;

  public class RandomApp{
   public static void main(String args[]){
    Random ran1=new Random();
    Random ran2=new Random(12345);
    //创建了两个类Random的对象。
    System.out.println("The 1st set of random numbers:");
    System.out.println("/t Integer:"+ran1.nextInt());
    System.out.println("/t Long:"+ran1.nextLong());
    System.out.println("/t Float:"+ran1.nextFloat());
    System.out.println("/t Double:"+ran1.nextDouble());
    System.out.println("/t Gaussian:"+ran1.nextGaussian());
    //产生各种类型的随机数
    System.out.print("The 2nd set of random numbers:");
    for(int i=0;i<5;i++){
     System.out.println(ran2.nextInt()+" ");
     if(i==2) System.out.println();
     //产生同种类型的不同的随机数。
     System.out.println();//原文如此
    }
   }
  }

  运行结果:
  E:/java01>java RandomApp
  The 1st set of random numbers:
    Integer:-173899656
    Long:8056223819738127077
    Float:0.6293638
    Double:0.7888394520265607
    Gaussian:0.5015701094568733
  The 2nd set of random numbers:1553932502
  -2090749135
  -287790814
  -355989640
  -716867186
  E:/java01>

1.5 向量类Vector

  Java.util.Vector提供了向量(Vector)类以实现类似动态数组的功能。在Java语言中。正如在一开始就提到过,是没有指针概念的,但如果能正确灵活地使用指针又确实可以大大提高程序的质量,比如在C、C++中所谓“动态数组”一般都由指针来实现。为了弥补这点缺陷,Java提供了丰富的类库来方便编程者使用,Vector类便是其中之一。事实上,灵活使用数组也可完成向量类的功能,但向量类中提供的大量方法大大方便了用户的使用。
  创建了一个向量类的对象后,可以往其中随意地插入不同的类的对象,既不需顾及类型也不需预先选定向量的容量,并可方便地进行查找。对于预先不知或不愿预先定义数组大小,并需频繁进行查找、插入和删除工作的情况,可以考虑使用向量类。
  向量类提供了三种构造方法:
  public vector()
  public vector(int initialcapacity,int capacityIncrement)
  public vector(int initialcapacity)
  使用第一种方法,系统会自动对向量对象进行管理。若使用后两种方法,则系统将根据参数initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时,系统会扩充向量对象的存储容量。参数capacityIncrement给定了每次扩充的扩充值。当capacityIncrement为0时,则每次扩充一倍。利用这个功能可以优化存储。
  在Vector类中提供了各种方法方便用户使用:
  ■插入功能
  (1)public final synchronized void addElement(Object obj)
  将obj插入向量的尾部。obj可以是任何类的对象。对同一个向量对象,可在其中插入不同类的对象。但插入的应是对象而不是数值,所以插入数值时要注意将数值转换成相应的对象。
  例 要插入一个整数1时,不要直接调用v1.addElement(1),正确的方法为:
  Vector v1=new Vector();
  Integer integer1=new Integer(1);
  v1.addElement(integer1);
  (2)public final synchronized void setElementAt(object obj,int index)
  将index处的对象设成obj,原来的对象将被覆盖。
  (3)public final synchronized void insertElementAt(Object obj,int index)
  在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。
  ■删除功能
  (1)public final synchronized void removeElement(Object obj)
  从向量中删除obj。若有多个存在,则从向量头开始试,删除找到的第一个与obj相同的向量成员。
  (2)public final synchronized void removeAllElement()
  删除向量中所有的对象。
  (3)public final synchronized void removeElementlAt(int index)
  删除index所指的地方的对象。
  ■查询搜索功能
  (1)public final int indexOf(Object obj)
  从向量头开始搜索obj ,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1。
  (2)public final synchronized int indexOf(Object obj,int index)
  从index所表示的下标处开始搜索obj。
  (3)public final int lastIndexOf(Object obj)
  从向量尾部开始逆向搜索obj。
  (4)public final synchronized int lastIndexOf(Object obj,int index)
  从index所表示的下标处由尾至头逆向搜索obj。
  (5)public final synchronized Object firstElement()
  获取向量对象中的首个obj。
  (6)public final synchronized Object lastelement()
  获取向量对象中的最后一个obj。
  了解了向量的最基本的方法后,我们来看一下例8.3VectorApp.java。
  例1.3 VectorApp.java。
  import java.util.Vector;
  import java.lang.*;//这一句不应该要,但原文如此
  import java.util.Enumeration;
  public class VectorApp{
   public static void main(String[] args){
    Vector v1=new Vector();
    Integer integer1=new Integer(1);
    v1.addElement("one");
    //加入的为字符串对象
    v1.addElement(integer1);
    v1.addElement(integer1);
    //加入的为Integer的对象
    v1.addElement("two");
    v1.addElement(new Integer(2));
    v1.addElement(integer1);
    v1.addElement(integer1);
    System.out.println("The vector v1 is:/n/t"+v1);
    //将v1转换成字符串并打印
    v1.insertElementAt("three",2);
    v1.insertElementAt(new Float(3.9),3);
    System.out.println("The vector v1(used method insertElementAt()) is:/n/t "+v1);
    //往指定位置插入新的对象,指定位置后的对象依次往后顺延
    v1.setElementAt("four",2);
    System.out.println("The vector v1(used method setElementAt()) is:/n/t "+v1);
    //将指定位置的对象设置为新的对象
    v1.removeElement(integer1);
    //从向量对象v1中删除对象integer1由于存在多个integer1所以从头开始
    //找,删除找到的第一个integer1
    Enumeration enum=v1.elements();
    System.out.print("The vector v1(used method removeElement())is:");
    while(enum.hasMoreElements())
    System.out.print(enum.nextElement()+" ");
    System.out.println();
    //使用枚举类(Enumeration)的方法来获取向量对象的每个元素
    System.out.println("The position of object 1(top-to-bottom):"
     + v1.indexOf(integer1));
    System.out.println("The position of object 1(tottom-to-top):"
     +v1.lastIndexOf(integer1));
    //按不同的方向查找对象integer1所处的位置
    v1.setSize(4);
    System.out.println("The new vector(resized the vector)is:"+v1);
    //重新设置v1的大小,多余的元素被行弃
   }
  }
  运行结果:
  E:/java01>java VectorApp
  The vector v1 is:
     [one, 1, 1, two, 2, 1, 1]
  The vector v1(used method insertElementAt()) is:
     [one, 1, three, 3.9, 1, two, 2, 1, 1]
  The vector v1(used method setElementAt()) is:
     [one, 1, four, 3.9, 1, two, 2, 1, 1]
  The vector v1(used method removeElement())is:one four 3.9 1 two 2 1 1
  The position of object 1(top-to-bottom):3
  The position of object 1(tottom-to-top):7
  The new vector(resized the vector)is:[one, four, 3.9, 1]
  E:/java01>
  从例1.3运行的结果中可以清楚地了解上面各种方法的作用,另外还有几点需解释。
  (1)类Vector定义了方法
  public final int size()
  此方法用于获取向量元素的个数。它的返回值是向是中实际存在的元素个数,而非向量容量。可以调用方法capactly()来获取容量值。
  方法:
  public final synchronized void setsize(int newsize)
  此方法用来定义向量大小。若向量对象现有成员个数已超过了newsize的值,则超过部分的多余元素会丢失。
  (2)程序中定义了Enumeration类的一个对象
  Enumeration是java.util中的一个接口类,在Enumeration中封装了有关枚举数据集合的方法。
  在Enumeration中提供了方法hawMoreElement()来判断集合中是束还有其它元素和方法nextElement()来获取下一个元素。利用这两个方法可以依次获得集合中元素。
  Vector中提供方法:
  public final synchronized Enumeration elements()
  此方法将向量对象对应到一个枚举类型。java.util包中的其它类中也大都有这类方法,以便于用户获取对应的枚举类型。

1.6 栈类Stack

  Stack类是Vector类的子类。它向用户提供了堆栈这种高级的数据结构。栈的基本特性就是先进后出。即先放入栈中的元素将后被推出。Stack类中提供了相应方法完成栈的有关操作。
  基本方法:
  public Object push(Object Hem)
  将Hem压入栈中,Hem可以是任何类的对象。
  public Object pop()
  弹出一个对象。
  public Object peek()
  返回栈顶元素,但不弹出此元素。
  public int search(Object obj)
  搜索对象obj,返回它所处的位置。
  public boolean empty()
  判别栈是否为空。
  例1.4 StackApp.java使用了上面的各种方法。
  例1.4 StackApp.java。
  import java.lang.*;
  import java.util.*;
  public class StackApp{
   public static void main(String args[]){
    Stack sta=new Stack();
    sta.push("Apple");
    sta.push("banana");
    sta.push("Cherry");
    //压入的为字符串对象
    sta.push(new Integer(2));
    //压入的为Integer的对象,值为2
    sta.push(new Float(3.5));
    //压入的为Float的对象,值为3.5
    System.out.println("The stack is,"+sta);
    //对应栈sta
    System.out.println("The top of stack is:"+sta.peek());
    //对应栈顶元素,但不将此元素弹出
    System.out.println("The position of object Cherry is:"
    +sta.search("cherry"));
    //打印对象Cherry所处的位置
    System.out.print("Pop the element of the stack:");
    while(!sta.empty())
    System.out.print(sta.pop()+" ");
    System.out.println();
    //将栈中的元素依次弹出并打印。与第一次打印的sta的结果比较,可看出栈
    //先进后出的特点
   }
  }
  运行结果(略)


1.7 哈希表类Hashtable

  哈希表是一种重要的存储方式,也是一种常见的检索方法。其基本思想是将关系码的值作为自变量,通过一定的函数关系计算出对应的函数值,把这个数值解释为结点的存储地址,将结点存入计算得到存储地址所对应的存储单元。检索时采用检索关键码的方法。现在哈希表有一套完整的算法来进行插入、删除和解决冲突。在Java中哈希表用于存储对象,实现快速检索。
  Java.util.Hashtable提供了种方法让用户使用哈希表,而不需要考虑其哈希表真正如何工作。
  哈希表类中提供了三种构造方法,分别是:
  public Hashtable()
  public Hashtable(int initialcapacity)
  public Hashtable(int initialCapacity,float loadFactor)
  参数initialCapacity是Hashtable的初始容量,它的值应大于0。loadFactor又称装载因子,是一个0.0到0.1之间的float型的浮点数。它是一个百分比,表明了哈希表何时需要扩充,例如,有一哈希表,容量为100,而装载因子为0.9,那么当哈希表90%的容量已被使用时,此哈希表会自动扩充成一个更大的哈希表。如果用户不赋这些参数,系统会自动进行处理,而不需要用户操心。
  Hashtable提供了基本的插入、检索等方法。
  ■插入
  public synchronized void put(Object key,Object value)
给对象value设定一关键字key,并将其加到Hashtable中。若此关键字已经存在,则将此关键字对应的旧对象更新为新的对象Value。这表明在哈希表中相同的关键字不可能对应不同的对象(从哈希表的基本思想来看,这也是显而易见的)。
  ■检索
  public synchronized Object get(Object key)
  根据给定关键字key获取相对应的对象。
  public synchronized boolean containsKey(Object key)
  判断哈希表中是否包含关键字key。
  public synchronized boolean contains(Object value)
  判断value是否是哈希表中的一个元素。
  ■删除
  public synchronized object remove(object key)
  从哈希表中删除关键字key所对应的对象。
  public synchronized void clear()
  清除哈希表
  另外,Hashtalbe还提供方法获取相对应的枚举集合:
  public synchronized Enumeration keys()
  返回关键字对应的枚举对象。
  public synchronized Enumeration elements()
  返回元素对应的枚举对象。
  例1.5 Hashtable.java给出了使用Hashtable的例子。
  例1.5 Hashtalbe.java。
  //import java.lang.*;
  import java.util.Hashtable;
  import java.util.Enumeration;
  public class HashApp{
   public static void main(String args[]){
    Hashtable hash=new Hashtable(2,(float)0.8);
    //创建了一个哈希表的对象hash,初始容量为2,装载因子为0.8

    hash.put("Jiangsu","Nanjing");
    //将字符串对象“Jiangsu”给定一关键字“Nanjing”,并将它加入hash
    hash.put("Beijing","Beijing");
    hash.put("Zhejiang","Hangzhou");

    System.out.println("The hashtable hash1 is: "+hash);
    System.out.println("The size of this hash table is "+hash.size());
    //打印hash的内容和大小

    Enumeration enum1=hash.elements();
    System.out.print("The element of hash is: ");
    while(enum1.hasMoreElements())
     System.out.print(enum1.nextElement()+" ");
    System.out.println();
    //依次打印hash中的内容
    if(hash.containsKey("Jiangsu"))
     System.out.println("The capatial of Jiangsu is "+hash.get("Jiangsu"));
    hash.remove("Beijing");
    //删除关键字Beijing对应对象
    System.out.println("The hashtable hash2 is: "+hash);
    System.out.println("The size of this hash table is "+hash.size());
   }
  }

  运行结果:
  The hashtable hash1 is: {Beijing=Beijing, Zhejiang=Hangzhou, Jiangsu=Nanjing}
  The size of this hash table is 3
  The element of hash is: Beijing Hangzhou Nanjing
  The capatial of Jiangsu is Nanjing
  The hashtable hash2 is: {Zhejiang=Hangzhou, Jiangsu=Nanjing}
  The size of this hash table is 2

  Hashtable是Dictionary(字典)类的子类。在字典类中就把关键字对应到数据值。字典类是一个抽象类。在java.util中还有一个类Properties,它是Hashtable的子类。用它可以进行与对象属性相关的操作。

1.8 位集合类BitSet

  位集合类中封装了有关一组二进制数据的操作。
  我们先来看一下例8.6 BitSetApp.java。
  例8.6 BitSetApp.java
  //import java.lang.*;
  import java.util.BitSet;
  public class BitSetApp{
   private static int n=5;
   public static void main(String[] args){
    BitSet set1=new BitSet(n);
    for(int i=0;i<n;i++) set1.set(i);
    //将set1的各位赋1,即各位均为true
    BitSet set2= new BitSet();
    set2=(BitSet)set1.clone();
    //set2为set1的拷贝
    set1.clear(0);
    set2.clear(2);
    //将set1的第0位set2的第2位清零
    System.out.println("The set1 is: "+set1);
    //直接将set1转换成字符串输出,输出的内容是set1中值true所处的位置
    //打印结果为The set1 is:{1,2,3,4}
    System.out.println("The hash code of set2 is: "+set2.hashCode());
    //打印set2的hashCode
    printbit("set1",set1);
    printbit("set2",set2);
    //调用打印程序printbit(),打印对象中的每一个元素
    //打印set1的结果为The bit set1 is: false true true true true
    set1.and(set2);
    printbit("set1 and set2",set1);
    //完成set1 and set2,并打印结果
    set1.or(set2);
    printbit("set1 or set2",set1);
    //完成set1 or set2,并打印结果
    set1.xor(set2);
    printbit("set1 xor set2",set1);
    //完成set1 xor set2,并打印结果
   }
   //打印BitSet对象中的内容
   public static void printbit(String name,BitSet set){
    System.out.print("The bit "+name+" is: ");
    for(int i=0;i<n;i++)
     System.out.print(set.get(i)+" ");
    System.out.println();
   }
  }

  运行结果:
  The set1 is: {1, 2, 3, 4}
  The hash code of set2 is: 1225
  The bit set1 is: false true true true true
  The bit set2 is: true true false true true
  The bit set1 and set2 is: false true false true true
  The bit set1 or set2 is: true true false true true
  The bit set1 xor set2 is: false false false false false

  程序中使用了BitSet类提供的两种构造方法:
    public BitSet();
    public BitSet(int n);
  参数n代表所创建的BitSet类的对象的大小。BitSet类的对象的大小在必要时会由系统自动扩充。
  其它方法:
  public void set(int n)
  将BitSet对象的第n位设置成1。
  public void clear(int n)
  将BitSet对象的第n位清零。
  public boolean get(int n)
  读取位集合对象的第n位的值,它获取的是一个布尔值。当第n位为1时,返回true;第n位为0时,返回false。
  另外,如在程序中所示,当把一BitSet类的对象转换成字符串输出时,输出的内容是此对象中true所处的位置。
  在BitSet中提供了一组位操作,分别是:
  public void and(BitSet set)
  public void or(BitSet set)
  public void xor(BitSet set)
利用它们可以完成两个位集合之间的与、或、异或操作。
  BitSet类中有一方法public int size()来取得位集合的大小,它的返回值与初始化时设定的位集合大小n不一样,一般为64。

小结

  本章我们介绍了Java的实用工具类库java.util中一些常用的类。java.util包中还有其它一些类。它们的具体用法用户可以自行查阅API。 
posted @ 2005-11-24 17:55 活在JAVA岛的日子 阅读(280) | 评论 (0)编辑  收藏
 
2005年11月23日
     package untitled4;
import java.util.*;
class compare implements Comparator
{
    public int compare(Object o1,Object o2)
    { 
        int i=((Person)o1).getFirstName().compareTo(((Person)o2).getFirstName());
        return (i!=0?i:((Person)o1).getLastName().compareTo(((Person)o2).getLastName()));
    }
}
class Person implements Comparable
{   private String firstName;
    private String lastName;
    public String getFirstName()
    {
      return firstName;
    }
    public String getLastName()
    {      return lastName;
    }
    public Person(String f,String l)
    {
       firstName=f;
       lastName=l;
    }
    public int compareTo(Object o)
    {  Person p=(Person)o;
        int l=firstName.compareTo(p.firstName);
       return (l!=0?l:(lastName.compareTo(p.lastName)));
    }
    public String toString()
    {
        return firstName+" "+lastName;
    }

}
public class ArrayTest {
    public ArrayTest() {
    }
public static void main(String args[])
    {
        int a[];
        a = new int[5];
        Arrays.fill(a, 5);  //用5填充数组
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
        int b[] = {10, 3, 5, 6, 8, 9};
        Arrays.sort(b);
        for (int i = 0; i < b.length; i++) {
            System.out.println(b[i]);
        }
   
        Person p[] = {
                     new Person("John", "Lennon"),
                     new Person("Karl", "Marx"),
                     new Person("Groucho", "Marx"),
                     new Person("Oscar", "Grouch")
        };
        for (int i = 0; i < p.length; i++) {
            System.out.println(p[i]);
        }
   
        //Arrays.sort(p);
        Arrays.sort(p, new compare());
        for (int i = 0; i < p.length; i++) {
           System.out.println(p[i]);
       }
        int s = Arrays.binarySearch(p, new Person("Groucho", "Marx"), new compare());
        System.out.println(s);
        Person person[];
        person = new Person[4];
        System.arraycopy(p, 0, person, 0, p.length);
        for (int c= 0; c < person.length; c++)
        {
            System.out.println(person[c]);
   
        }
    }
}
当用Arrays的sort方法时,需要排序的数组必须实现Comparable借口.或者实现Comparator接口
Arrays的fill方法是用一个基本类型或者一个对象填充数组.
当调用binarySearch()时如果是调用sort(Objiec a[],Comparator a)时,,应调用相应的binarySearch(Object a[],Object value,Comparator)方法.
equals()方法用于比较非基本类型数组时,调用他们的equals方法..比如
 int a[]={5,4,3,2,1};
 int c[]={5,4,3,2,1};  基本类型比较他们的值,
 System.out.println(Arrays.equals(a,c));  TRUE
Integer a[]={new Integer(1),new Integer(2),new Integer(3)};
Integer b[]={new Integer(1),new Integer(2),new Integer(3)};
System.out.println(Arrays.equals(a,b));   调用Integer.equals()方法
对于没有覆盖Object equals方法的对象数组,他们之间比较的是对象的引用.
System.arraycopy()方法对与基本类型来说只是复制他们的值.而对于非基本类型时他们复制引用
  person person[]={new person("guo",20),new person("cher",21)};
         person per[]={new person("guo",20),new person("cher",21)};
       person []p=new person[2];
       System.arraycopy(person,0,p,0,person.length);
       System.out.println(person==p);    //输出  false
       person[1].setName("hao");
       System.out.println(person[1].equals(p[1]));  输出 true
       System.out.println(Arrays.equals(person,p));   输出 true
        System.out.println(Arrays.equals(person,per));  输出 false
posted @ 2005-11-23 17:22 活在JAVA岛的日子 阅读(101) | 评论 (0)编辑  收藏
 
2005年11月16日
如果子类继承了父类的方法(未重写),则运行时系统调用父类的方法。
在例3-6中,
posted @ 2005-11-16 02:59 活在JAVA岛的日子 阅读(116) | 评论 (0)编辑  收藏
 
品味Java子类型多态的魅力
 
<script language=javascript> var newasp_fontsize=9; var newasp_lineheight=12; function do_color(vobject,vvar) { document.getElementById(vobject).style.color=vvar; } </script>
减小字体 增大字体

  “polymorphism(多态)”一词来自希腊语,意为“多种形式”。多数Java程序员把多态看作对象的一种能力,使其能调用正确的方法版本。尽管如此,这种面向实现的观点导致了多态的神奇功能,胜于仅仅把多态看成纯粹的概念。

  Java中的多态总是子类型的多态。几乎是机械式产生了一些多态的行为,使我们不去考虑其中涉及的类型问题。本文研究了一种面向类型的对象观点,分析了如何将对象能够表现的行为和对象即将表现的行为分离开来。抛开Java中的多态都是来自继承的概念,我们仍然可以感到,Java中的接口是一组没有公共代码的对象共享实现。

   多态的分类

  多态在面向对象语言中是个很普遍的概念.虽然我们经常把多态混为一谈,但实际上有四种不同类型的多态。在开始正式的子类型多态的细节讨论前,然我们先来看看普通面向对象中的多态。

  Luca Cardelli和Peter Wegner("On Understanding Types, Data Abstraction, and Polymorphism"一文的作者, 文章参考资源链接)把多态分为两大类----特定的和通用的----四小类:强制的,重载的,参数的和包含的。他们的结构如下:

screen.width-600)this.style.width=screen.width-600;">

  在这样一个体系中,多态表现出多种形式的能力。通用多态引用有相同结构类型的大量对象,他们有着共同的特征。特定的多态涉及的是小部分没有相同特征的对象。四种多态可做以下描述:

  强制的:一种隐式做类型转换的方法。

  重载的:将一个标志符用作多个意义。

  参数的:为不同类型的参数提供相同的操作。

  包含的:类包含关系的抽象操作。

  我将在讲述子类型多态前简单介绍一下这几种多态。

   强制的多态

  强制多态隐式的将参数按某种方法,转换成编译器认为正确的类型以避免错误。在以下的表达式中,编译器必须决定二元运算符‘+’所应做的工作:

  2.0 + 2.0

  2.0 + 2

  2.0 + "2"

  第一个表达式将两个double的操作数相加;Java中特别声明了这种用法。

  第二个表达式将double型和int相加。Java中没有明确定义这种运算。不过,编译器隐式的将第二个操作数转换为double型,并作double型的加法。做对程序员来说十分方便,否则将会抛出一个编译错误,或者强制程序员显式的将int转换为double。

  第三个表达式将double与一个String相加。Java中同样没有定义这样的操作。所以,编译器将double转换成String类型,并将他们做串联。

  强制多态也会发生在方法调用中。假设类Derived继承了类Base,类C有一个方法,原型为m(Base),在下面的代码中,编译器隐式的将Derived类的对象derived转化为Base类的对象。这种隐式的转换使m(Base)方法使用所有能转换成Base类的所有参数。

C c = new C();

Derived derived = new Derived();

c.m( derived );

  并且,隐式的强制转换,可以避免类型转换的麻烦,减少编译错误。当然,编译器仍然会优先验证符合定义的对象类型。

   重载的多态

  重载允许用相同的运算符或方法,去表示截然不同的意义。‘+’在上面的程序中有两个意思:两个double型的数相加;两个串相连。另外还有整型相加,长整型,等等。这些运算符的重载,依赖于编译器根据上下文做出的选择。以往的编译器会把操作数隐式转换为完全符合操作符的类型。虽然Java明确支持重载,但不支持用户定义的操作符重载。

  Java支持用户定义的函数重载。一个类中可以有相同名字的方法,这些方法可以有不同的意义。这些重载的方法中,必须满足参数数目不同,相同位置上的参数类型不同。这些不同可以帮助编译器区分不同版本的方法。

  编译器以这种唯一表示的特征来表示不同的方法,比用名字表示更为有效。据此,所有的多态行为都能编译通过。

  强制和重载的多态都被分类为特定的多态,因为这些多态都是在特定的意义上的。这些被划入多态的特性给程序员带来了很大的方便。强制多态排除了麻烦的类型和编译错误。重载多态像一块糖,允许程序员用相同的名字表示不同的方法,很方便。

   参数的多态

  参数多态允许把许多类型抽象成单一的表示。例如,List抽象类中,描述了一组具有同样特征的对象,提供了一个通用的模板。你可以通过指定一种类型以重用这个抽象类。这些参数可以是任何用户定义的类型,大量的用户可以使用这个抽象类,因此参数多态毫无疑问的成为最强大的多态。

  乍一看,上面抽象类好像是java.util.List的功能。然而,Java实际上并不支持真正的安全类型风格的参数多态,这也是java.util.List和java.util的其他集合类是用原始的java.lang.Object写的原因(参考我的文章"A Primordial Interface?" 以获得更多细节)。Java的单根继承方式解决了部分问题,但没有发挥出参数多态的全部功能。Eric Allen有一篇精彩的文章“Behold the Power of Parametric Polymorphism”,描述了Java通用类型的需求,并建议给Sun的Java规格需求#000014号文档"Add Generic Types to the Java Programming Language."(参考资源链接)

   包含的多态

  包含多态通过值的类型和集合的包含关系实现了多态的行为.在包括Java在内的众多面向对象语言中,包含关系是子类型的。所以,Java的包含多态是子类型的多态。

  在早期,Java开发者们所提及的多态就特指子类型的多态。通过一种面向类型的观点,我们可以看到子类型多态的强大功能。以下的文章中我们将仔细探讨这个问题。为简明起见,下文中的多态均指包含多态。

   面向类型观点

  图1的UML类图给出了类和类型的简单继承关系,以便于解释多态机制。模型中包含5种类型,4个类和一个接口。虽然UML中称为类图,我把它看成类型图。如"Thanks Type and Gentle Class," 一文中所述,每个类和接口都是一种用户定义的类型。按独立实现的观点(如面向类型的观点),下图中的每个矩形代表一种类型。从实现方法看,四种类型运用了类的结构,一种运用了接口的结构。
screen.width-600)this.style.width=screen.width-600;">

图1:示范代码的UML类图

  以下的代码实现了每个用户定义的数据类型,我把实现写得很简单。

/* Base.java */

public class Base
{
 public String m1()
 {
  return "Base.m1()";
 }

 public String m2( String s )
 {
  return "Base.m2( " + s + " )";
 }
}

/* IType.java */

interface IType
{
 String m2( String s );
 String m3();
}

/* Derived.java */

public class Derived
extends Base
implements IType
{
 public String m1()
 {
  return "Derived.m1()";
 }

 public String m3()
 {
  return "Derived.m3()";
 }
}

/* Derived2.java */

public class Derived2
extends Derived
{
 public String m2( String s )
 {
  return "Derived2.m2( " + s + " )";
 }
 public String m4()
 {
  return "Derived2.m4()";
 }
}

/* Separate.java */

public class Separate
implements IType
{
 public String m1()
 {
  return "Separate.m1()";
 }
 public String m2( String s )
 {
  return "Separate.m2( " + s + " )";
 }

 public String m3()
 {
  return "Separate.m3()";
 }
}


  用这样的类型声明和类的定义,图2从概念的观点描述了Java指令。

Derived2 derived2 = new Derived2();

screen.width-600)this.style.width=screen.width-600;">
图2 :Derived2 对象上的引用

  上文中声明了derived2这个对象,它是Derived2类的。图2种的最顶层把Derived2引用描述成一个集合的窗口,虽然其下的Derived2对象是可见的。这里为每个Derived2类型的操作留了一个孔。Derived2对象的每个操作都去映射适当的代码,按照上面的代码所描述的那样。例如,Derived2对象映射了在Derived中定义的m1()方法。而且还重载了Base类的m1()方法。一个Derived2的引用变量无权访问Base类中被重载的m1()方法。但这并不意味着不可以用super.m1()的方法调用去使用这个方法。关系到derived2这个引用的变量,这个代码是不合适的。Derived2的其他的操作映射同样表明了每种类型操作的代码执行。

  既然你有一个Derived2对象,可以用任何一个Derived2类型的变量去引用它。如图1所示,Derived, Base和IType都是Derived2的基类。所以,Base类的引用是很有用的。图3描述了以下语句的概念观点。

Base base = derived2;

screen.width-600)this.style.width=screen.width-600;">
图3:Base类引用附于Derived2对象之上
 
  虽然Base类的引用不用再访问m3()和m4(),但是却不会改变它Derived2对象的任何特征及操作映射。无论是变量derived2还是base,其调用m1()或m2(String)所执行的代码都是一样的。

String tmp;
// Derived2 reference (Figure 2)
tmp = derived2.m1(); // tmp is "Derived.m1()"
tmp = derived2.m2( "Hello" ); // tmp is "Derived2.m2( Hello )"

// Base reference (Figure 3)

tmp = base.m1(); // tmp is "Derived.m1()"
tmp = base.m2( "Hello" ); // tmp is "Derived2.m2( Hello )"


  两个引用之所以调用同一个行为,是因为Derived2对象并不知道去调用哪个方法。对象只知道什么时候调用,它随着继承实现的顺序去执行。这样的顺序决定了Derived2对象调用Derived里的m1()方法,并调用Derived2里的m2(String)方法。这种结果取决于对象本身的类型,而不是引用的类型。

  尽管如此,但不意味着你用derived2和base引用的效果是完全一样的。如图3所示,Base的引用只能看到Base类型拥有的操作。所以,虽然Derived2有对方法m3()和m4()的映射,但是变量base不能访问这些方法。

String tmp;
// Derived2 reference (Figure 2)
tmp = derived2.m3(); // tmp is "Derived.m3()"
tmp = derived2.m4(); // tmp is "Derived2.m4()"

// Base reference (Figure 3)

tmp = base.m3(); // Compile-time error
tmp = base.m4(); // Compile-time error


  运行期的Derived2对象保持了接受m3()和m4()方法的能力。类型的限制使Base的引用不能在编译期调用这些方法。编译期的类型检查像一套铠甲,保证了运行期对象只能和正确的操作进行相互作用。换句话说,类型定义了对象间相互作用的边界。

  多态的依附性

  类型的一致性是多态的核心。对象上的每一个引用,静态的类型检查器都要确认这样的依附和其对象的层次是一致的。当一个引用成功的依附于另一个不同的对象时,有趣的多态现象就产生了。(严格的说,对象类型是指类的定义。)你也可以把几个不同的引用依附于同一个对象。在开始更有趣的场景前,我们先来看一下下面的情况为什么不会产生多态。

  多个引用依附于一个对象

  图2和图3描述的例子是把两个及两个以上的引用依附于一个对象。虽然Derived2对象在被依附之后仍保持了变量的类型,但是,图3中的Base类型的引用依附之后,其功能减少了。结论很明显:把一个基类的引用依附于派生类的对象之上会减少其能力。

  一个开发这怎么会选择减少对象能力的方案呢?这种选择是间接的。假设有一个名为ref的引用依附于一个包含如下方法的类的对象:

posted @ 2005-11-16 02:44 活在JAVA岛的日子 阅读(119) | 评论 (0)编辑  收藏

2005年11月15日

J2EE学习者越来越多,J2EE本身技术不断在发展,涌现出各种概念,本文章试图从一种容易理解的角度对这些概念向初学者进行解释,以便掌握学习J2EE学习方向。首先我们需要知道Java和J2EE是两个不同概念,Java不只是指一种语言,已经代表与微软不同的另外一个巨大阵营,所以Java有时是指一种软件系统的流派,当然目前主要是.NET和Java两大主流体系。

  J2EE可以说指Java在数据库信息系统上实现,数据库信息系统从早期的dBase、到Delphi/VB等C/S结构,发展到B/S(Browser浏览器/Server服务器)结构,而J2EE主要是指B/S结构的实现。

  J2EE又是一种框架和标准,框架类似API、库的概念,但是要超出它们。如果需要详细了解框架,可先从设计模式开始学习。

  J2EE是一个虚的大的概念,J2EE标准主要有三种子技术标准:WEB技术、EJB技术和JMS,谈到J2EE应该说最终要落实到这三个子概念上。

  这三种技术的每个技术在应用时都涉及两个部分:容器部分和应用部分,Web容器也是指Jsp/Servlet容器,你如果要开发一个Web应用,无论是编译或运行,都必须要有Jsp/Servlet库或API支持(除了JDK/J2SE以外)。

  Web技术中除了Jsp/Servlet技术外,还需要JavaBeans或Java Class实现一些功能或者包装携带数据,所以Web技术最初裸体简称为Jsp/Servlet+JavaBeans系统。
谈到JavaBeans技术,就涉及到组件构件技术(component),这是Java的核心基础部分,很多软件设计概念(设计模式)都是通过JavaBeans实现的。

  JavaBeans不属于J2EE概念范畴中,如果一个JavaBeans对象被Web技术(也就是Jsp/Servlet)调用,那么JavaBeans就运行在J2EE的Web容器中;如果它被EJB调用,它就运行在EJB容器中。

  EJB(企业JavaBeans)是普通JavaBeans的一种提升和规范,因为企业信息系统开发中需要一个可伸缩的性能和事务、安全机制,这样能保证企业系统平滑发展,而不是发展到一种规模重新更换一套软件系统。

  至此,JavaBeans组件发展到EJB后,并不是说以前的那种JavaBeans形式就消失了,这就自然形成了两种JavaBeans技术:EJB和POJO,POJO完全不同于EJB概念,指的是普通JavaBeans,而且这个JavaBeans不依附某种框架,或者干脆可以说:这个JavaBeans是你为这个应用程序单独开发创建的。

  J2EE应用系统开发工具有很多:如JBuilder、Eclipse等,这些IDE首先是Java开发工具,也就是说,它们首要基本功能是可以开发出JavaBeans或Java class,但是如果要开发出J2EE系统,就要落实到要么是Web技术或EJB技术,那么就有可能要一些专门模块功能(如eclipse需要lomboz插件),最重要的是,因为J2EE系统区分为容器和应用两个部分,所以,在任何开发工具中开发J2EE都需要指定J2EE容器。

  J2EE容器分为WEB容器和EJB容器,Tomcat/Resin是Web容器;JBoss是EJB容器+Web容器等,其中Web容器直接使用Tomcat实现的。所以你开发的Web应用程序可以在上面两种容器运行,而你开发的Web+EJB应用则只可以在JBoss服务器上运行,商业产品Websphere/Weblogic等和JBoss属于同一种性质。

  J2EE容器也称为J2EE服务器,大部分时它们概念是一致的。
如果你的J2EE应用系统的数据库连接是通过JNDI获得,也就是说是从容器中获得,那么你的J2EE应用系统基本与数据库无关,如果你在你的J2EE应用系统耦合了数据库JDBC驱动的配置,那么你的J2EE应用系统就有数据库概念色彩,作为一个成熟需要推广的J2EE应用系统,不推荐和具体数据库耦合,当然这其中如何保证J2EE应用系统运行性能又是体现你的设计水平了。

  衡量J2EE应用系统设计开发水平高低的标准就是:解耦性;你的应用系统各个功能是否能够彻底脱离?是否不相互依赖,也只有这样,才能体现可维护性、可拓展性的软件设计目标。

  为了达到这个目的,诞生各种框架概念,J2EE框架标准将一个系统划分为WEB和EJB主要部分,当然我们有时不是以这个具体技术区分,而是从设计上抽象为表现层、服务层和持久层,这三个层次从一个高度将J2EE分离开来,实现解耦目的。

  因此,我们实际编程中,也要将自己的功能向这三个层次上靠,做到大方向清楚,泾渭分明,但是没有技术上约束限制要做到这点是很不容易的,因此我们还是必须借助J2EE具体技术来实现,这时,你可以使用EJB规范实现服务层和持久层,Web技术实现表现层;

  EJB为什么能将服务层从Jsp/Servlet手中分离出来,因为它对JavaBeans编码有强制的约束,现在有一种对JavaBeans弱约束,使用Ioc模式实现的(当然EJB 3.0也采取这种方式),在Ioc模式诞生前,一般都是通过工厂模式来对JavaBeans约束,形成一个服务层,这也是是Jive这样开源论坛设计原理之一。

  由此,将服务层从表现层中分离出来目前有两种可选架构选择:管理普通JavaBeans(POJO)框架(如Spring、JdonFramework)以及管理EJB的EJB框架,因为EJB不只是框架,还是标准,而标准可以扩展发展,所以,这两种区别将来是可能模糊,被纳入同一个标准了。 但是,个人认为:标准制定是为某个目的服务的,总要牺牲一些换取另外一些,所以,这两种架构会长时间并存。

  这两种架构分歧也曾经诞生一个新名词:完全POJO的系统也称为轻量级系统(lightweight),其实这个名词本身就没有一个严格定义,更多是一个吸引人的招牌,轻量是指容易学习容易使用吗?按照这个定义,其实轻量Spring等系统并不容易学习;而且EJB 3.0(依然叫EJB)以后的系统是否可称为轻量级了呢?
前面谈了服务层框架,使用服务层框架可以将JavaBeans从Jsp/Servlet中分离出来,而使用表现层框架则可以将Jsp中剩余的JavaBeans完全分离,这部分JavaBeans主要负责显示相关,一般是通过标签库(taglib)实现,不同框架有不同自己的标签库,Struts是应用比较广泛的一种表现层框架。

  这样,表现层和服务层的分离是通过两种框架达到目的,剩余的就是持久层框架了,通过持久层的框架将数据库存储从服务层中分离出来是其目的,持久层框架有两种方向:直接自己编写JDBC等SQL语句(如iBatis);使用O/R Mapping技术实现的Hibernate和JDO技术;当然还有EJB中的实体Bean技术。

  持久层框架目前呈现百花齐放,各有优缺点的现状,所以正如表现层框架一样,目前没有一个框架被指定为标准框架,当然,表现层框架现在又出来了一个JSF,它代表的页面组件概念是一个新的发展方向,但是复杂的实现让人有些忘而却步。

  在所有这些J2EE技术中,虽然SUN公司发挥了很大的作用,不过总体来说:网络上有这样一个评价:SUN的理论天下无敌;SUN的产品用起来撞墙;对于初学者,特别是那些试图通过或已经通过SUN认证的初学者,赶快摆脱SUN的阴影,立即开溜,使用开源领域的产品来实现自己的应用系统。

  最后,你的J2EE应用系统如果采取上面提到的表现层、服务层和持久层的框架实现,基本你也可以在无需深刻掌握设计模式的情况下开发出一个高质量的应用系统了。

  还要注意的是: 开发出一个高质量的J2EE系统还需要正确的业务需求理解,那么域建模提供了一种比较切实可行的正确理解业务需求的方法,相关详细知识可从UML角度结合理解。

  当然,如果你想设计自己的行业框架,那么第一步从设计模式开始吧,因为设计模式提供你一个实现JavaBeans或类之间解耦参考实现方法,当你学会了系统基本单元JavaBean或类之间解耦时,那么系统模块之间的解耦你就可能掌握,进而你就可以实现行业框架的提炼了,这又是另外一个发展方向了。
posted @ 2005-11-15 16:13 活在JAVA岛的日子 阅读(141) | 评论 (0)编辑  收藏

2005年11月12日

public class statictest {
    public statictest()
    {   
    }
    public static void prin(String s)
    {
    System.out.println(s);
    }
    public static int i=printy("hehe");
    public static int printy(String s)
      {
          System.out.println(s);
          return 4;
       }
    public static void main(String[] args) {
        statictest.prin("fdsafa");
    }
}
输出结果 hehe fdsafa
当生成一个类的对象时,或者首次访问属于哪个类的静态数据成员时,,进行初始化.
package untitled4;
class teststatic
{
   static int i=prin("test");
   static int prin(String s)
   {
       System.out.println(s);
       return 2;
   }
   static void play()
   {
   System.out.println("play");
   }

}
public class statictest2 {
    public statictest2() {
    }

    public static void main(String[] args) {
      teststatic.play();
    }
}
输出结果   TEST PLAY
对于类CLASS A的执行相当于调用A.main(),,他首先对A的元素初始化(遵循,从A的基类开始,STATIC)
非STATIC在对象生成时候才初始化



public String poly1( Base base )
{
 return base.m1();
}


  用一个Derived2的参数调用poly(Base)是符合参数类型检查的:

ref.poly1( derived2 );


  方法调用把一个本地Base类型的变量依附在一个引入的对象上。所以,虽然这个方法只接受Base类型的参数,但Derived2对象仍是允许的。开发这就不必选择丢失功能的方案。从人眼在通过Derived2对象时所看到的情况,Base类型引用的依附导致了功能的丧失。但从执行的观点看,每一个传入poly1(Base)的参数都认为是Base的对象。执行机并不在乎有多个引用指向同一个对象,它只注重把指向另一个对象的引用传给方法。这些对象的类型不一致并不是主要问题。执行器只关心给运行时的对象找到适当的实现。面向类型的观点展示了多态的巨大能力。

  附于多个对象的引用

  让我们来看一下发生在poly1(Base)中的多态行为。下面的代码创建了三个对象,并通过引用传给poly1(Base):

Derived2 derived2 = new Derived2();
Derived derived = new Derived();
Base base = new Base();

String tmp;

tmp = ref.poly1( derived2 ); // tmp is "Derived.m1()"
tmp = ref.poly1( derived ); // tmp is "Derived.m1()"
tmp = ref.poly1( base ); // tmp is "Base.m1()"


  poly1(Base)的实现代码是调用传进来的参数的m1()方法。图3和图4展示了把三个类的对象传给方法时,面向类型的所使用的体系结构。

screen.width-600)this.style.width=screen.width-600;">
图4:将Base引用指向Derived类,以及Base对象

  请注意每个图中方法m1()的映射。图3中,m1()调用了Derived类的代码;上面代码中的注释标明了ploy1(Base)调用Derived.m1()。图4中Derived对象调用的仍然是Derived类的m1()方法。最后,图4中,Base对象调用的m1()是Base类中定义的代码。

  多态的魅力何在?再来看一下poly1(Base)的代码,它可以接受任何属于Base类范畴的参数。然而,当他收到一个Derived2的对象时,它实际上却调用了Derived版本的方法。当你根据Base类派生出其他类时,如Derived,Derived2,poly1(Base)都可以接受这些参数,并作出选择调用合适的方法。多态允许你在完成poly1(Base)后扩展它的用途。

  这看起来当然很神奇。基本的理解展示了多态的内部工作原理。在面向类型的观点中,底层的对象所实现的代码是非实质性的。重要的是,类型检查器会在编译期间为每个引用选择合适的代码以实现其方法。多态使开发者运用面向类型的观点,不考虑实现的细节。这样有助于把类型和实现分离(实际用处是把接口和实现分离)。

  对象接口

  多态依赖于类型和实现的分离,多用来把接口和实现分离。但下面的观点好像把Java的关键字interface搞得很糊涂。

  更为重要的使开发者们怎样理解短语“the interface to an object",典型地,根据上下文,这个短语的意思是指一切对象类中所定义的方法,至一切对象公开的方法。这种倾向于以实现为中心的观点较之于面向类型的观点来说,使我们更加注重于对象在运行期的能力。图3中,引用面板的对象表面被标志成"Derived2 Object"。这个面板上列出了Derived2对象的所有可用的方法。但是要理解多态,我们必须从实现这一层次上解放出来,并注意面向类型的透视图中被标为"Base Reference"的面板。在这一层意思上,引用变量的类型指明了一个对象的表面。这只是一个表面,不是接口。在类型一致的原则下,我们可以用面向类型的观点,为一个对象依附多个引用。对interface to an object这个短语的理解没有确定的理解。

  在类型概念中,the interface to an object refers 引用了面向类型观点的最大可能----如图2的情形。把一个基类的引用指向相同的对象缩小了这样的观点----如图3所示。类型概念能使人获得把对象间的相互作用同实现细节分离的要领。相对于一个对象的接口,面向类型的观点更鼓励人们去使用一个对象的引用。引用类型规定了对象间的相互作用。当你考虑一个对象能做什么的时候,只需搞明白他的类型,而不需要去考虑他的实现细节。

  Java接口

  以上所谈到的多态行为用到了类的继承关系所建立起来的子类型关系。Java接口同样支持用户定义的类型,相对地,Java的接口机制启动了建立在类型层次结构上的多态行为。假设一个名为ref的引用变量,并使其指向一个包含一下方法的类对象:

public String poly2( IType iType )
{
 return iType.m3();
}

  为了弄明白poly2(IType)中的多态,以下的代码从不同的类创建两个对象,并分别把他们传给poly2(IType):

Derived2 derived2 = new Derived2();
Separate separate = new Separate();

String tmp;

tmp = ref.poly2( derived2 ); // tmp is "Derived.m3()"
tmp = ref.poly2( separate ); // tmp is "Separate.m3()"

  上面的代码类似于关于poly1(Base)中的多态的讨论。poly2(IType)的实现代码是调用每个对象的本地版本的m3()方法。如同以前,代码的注释表明了每次调用所返回的CString类型的结果。图5表明了两次调用poly2(IType)的概念结构:

screen.width-600)this.style.width=screen.width-600;">
图5:指向Derived2和Separate对象的IType引用

  方法poly1(Base)和poly2(IType)中所表现的多态行为的相似之处可以从透视图中直接看出来。把我们在实现在一层上的理解再提高一层,就可以看到这两段代码的技巧。基类的引用指向了作为参数传进的类,并且按照类型的限制调用对象的方法。引用既不知道也不关心执行哪一段代码。编译期间的子类型关系检查保证了通过的对象有能力在被调用的时候选择合适的实现代码。

  然而,他们在实现层上有一个重要的差别。在poly1(Base)的例子中(图3和图4),Base-Derived-Derived2的类继承结构为子类型关系的建立提供了条件,并决定了方法去调用哪段代码。在poly2(IType)的例子中(如图5),则是完全不同的动态发生的。Derived2和Separate不共享任何实现的层次,但是他们还是通过IType的引用展示了多态的行为。

  这样的多态行为使Java的接口的功能的重大意义显得很明显。图1中的UML类图说明了Derived是Base和IType的子类型。通过完全脱离实现细节的类型的定义方法,Java实现了多类型继承,并且不存在Java所禁止的多继承所带来的烦人的问题。完全脱离实现层次的类可以按照Java接口实现分组。在图1中,接口IType和Derived,Separate以及这类型的其他子类型应该划为一组。

  按照这种完全不同于实现层次的分类方法,Java的接口机制是多态变得很方便,哪怕不存在任何共享的实现或者复写的方法。如图5所示,一个IType的引用,用多态的方法访问到了Derived2和Separate对象的m3()方法。

  再次探讨对象的接口

  注意图5中的Derived2和Separate对象的对m1()的映射方法。如前所述,每一个对象的接口都包含方法m1()。但却没有办法用这两个对象使方法m1()表现出多态的行为。每一个对象占有一个m1()方法是不够的。必须存在一个可以操作m1()方法的类型,通过这个类型可以看到对象。这些对象似乎是共享了m1()方法,但在没有共同基类的条件下,多态是不可能的。通过对象的接口来看多态,会把这个概念搞混。

  结论

  从全文所述的面向对象多态所建立起来的子类型多态,你可以清楚地认识到这种面向类型的观点。如果你想理解子类型多态的思想,就应该把注意力从实现的细节转移到类型的上。类型把对象分成组,并且管理着这些对象的接口。类型的继承层次结构决定了实现多态所需的类型关系。

  有趣的是,实现的细节并不影响子类型多态的层次结构。类型决定了对象调用什么方法,而实现则决定了对象怎么执行这个方法。也就是说,类型表明了责任,而负责实施的则是具体的实现。将实现和类型分离后,我们好像看到了这两个部分在一起跳舞,类型决定了他的舞伴和舞蹈的名字,而实现则是舞蹈动作的设计师。

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页