2008年6月12日
1、在一般情況下,actionForm是被存儲在一定的scope中(request或session,通過action的scope屬性來配置),當我們在配置時,指定name而不指定attribute,那么指定的name值就作為actionForm存儲在scope中的key值,我們可以在action中通過httpServletRequest.getAttribute("指定的name屬性值")來獲得這個actionForm; 當我們既配置了name又配置了attribute,那么actionForm存儲在scope中的key值就采用attribute屬性指定的值了,這時要通過httpServletRequest.getAttribute("指定的attribute屬性值")來獲得actionForm,此時通過httpServletRequest.getAttribute("指定的name屬性值")是不能獲得actionForm的。
所以,是否配置attribute屬性就決定了actionForm存儲在scope中的key值是采用name,還是采用attribute
2、 在《Programming Jakarta Struts》這本書中的第四章“Configuring the Struts Application”中這樣一段說明來分別闡述這兩
個屬性:(102頁)
++++++++
atribute:
++++++++
The name of the request or session scope attribute under which the form bean for this action can be accessed.
A value is only allowed here if there is a form bean specified in the name attribute. This attribute is
optional and has no default value.
++++++++
name:
++++++++
The name of the form bean, if any, that is associated with this action. This value must be the name attribute
from one of the form-bean elements defined earlier. This attribute is optional and has no default value.
最初看這些真的還是不好區分這兩者。不過在仔細看過struts的源代碼以后,豁然開朗。。。
下面主要對attribute進行解釋,應為沒有人會對name屬性不了解的(呵呵。。。)
解釋:在struts實例化actionform的時候,有兩種情況:如果已經存在,那么從內存中取回;如果第一次實例化,那么創建,并放入內存。
這樣就有一個問題了,struts是根據什么來取回并創建actionform的呢,答案就是attribute的值。讓我們進入struts的源代碼:
/**
*創建或者取回formbean方法
*該方法在:org.apache.struts.util.RequestUtils中
*/
public static Actionform createActionform(
HttpServletRequest request,
ActionMapping mapping,
ModuleConfig moduleConfig,
ActionServlet servlet) {
。。。。
。。。
// Is there a form bean associated with this mapping?
//得到action mapping中attribute的值
String attribute = mapping.getAttribute();
。。。。
。。。。
Actionform instance = null;
HttpSession session = null;
//yes!!就在這里了,把創建以后的actionform放在request或者session里,看到放入的名字了么,就是mapping.getAttribute();
if ("request".equals(mapping.getScope())) {
instance = (Actionform) request.getAttribute(attribute);
} else {
session = request.getSession();
instance = (Actionform) session.getAttribute(attribute);
}
。。。
。。。
}
下面又有一個問題浮出水面:如果我沒有在action mapping中指定attribute呢,那struts 是如何解決的?
答案很簡單,如果單從結果上看,此時struts使用的name的值,為什么呢,看struts源代碼:
/**
* The request-scope or session-scope attribute name under which our
* form bean is accessed, if it is different from the form bean's
* specified <code>name</code>.
*該代碼在:org.apache.struts.config.ActionConfig中
*/
protected String attribute = null;
public String getAttribute() {
//yes!!!!就在這里,看到了吧,如果你沒有設定attribute,那么struts 會把name的值拿過來用。呵呵。。。
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
public void setAttribute(String attribute) {
if (configured) {
throw new IllegalStateException("Configuration is frozen");
}
this.attribute = attribute;
}
當兩個Web組件之間為轉發關系時,轉發源會將要共享 request范圍內的數據先用setAttribute將數據放入到HttpServletRequest對象中,然后轉發目標通過 getAttribute方法來取得要共享的數據。而MVC中用的就是Web組件之間的轉發啊!真是笨,怎么當時沒有想到呢?
下面整理一下getParameter和getAttribute的區別和各自的使用范圍。
(1)HttpServletRequest類有setAttribute()方法,而沒有setParameter()方法
(2)當兩個Web組件之間為鏈接關系時,被鏈接的組件通過getParameter()方法來獲得請求參數,例如假定welcome.jsp和authenticate.jsp之間為鏈接關系,welcome.jsp中有以下代碼:
<a href="authenticate.jsp?username=wolf">authenticate.jsp </a>
或者:
<form name="form1" method="post" action="authenticate.jsp">
請輸入用戶姓名:<input type="text" name="username">
<input type="submit" name="Submit" value="提交">
</form>
在authenticate.jsp中通過request.getParameter("username")方法來獲得請求參數username:
<% String username=request.getParameter("username"); %>
(3)當兩個Web組件之間為轉發關系時,轉發目標組件通過getAttribute()方法來和轉發源組件共享request范圍內的數據。
假定 authenticate.jsp和hello.jsp之間為轉發關系。authenticate.jsp希望向hello.jsp傳遞當前的用戶名字, 如何傳遞這一數據呢?先在authenticate.jsp中調用setAttribute()方法:
<%
String username=request.getParameter("username");
request.setAttribute("username",username);
%>
<jsp:forward page="hello.jsp" />
在hello.jsp中通過getAttribute()方法獲得用戶名字:
<% String username=(String)request.getAttribute("username"); %>
Hello: <%=username %>
從更深的層次考慮,request.getParameter()方法傳遞的數據,會從Web客戶端傳到Web服務器端,代表HTTP請求數據。request.getParameter()方法返回String類型的數據。
request.setAttribute()和getAttribute()方法傳遞的數據只會存在于Web容器內部,在具有轉發關系的Web組件之間共享。這兩個方法能夠設置Object類型的共享數據。
request.getParameter()取得是通過容器的實現來取得通過類似post,get等方式傳入的數據。
request.setAttribute()和getAttribute()只是在web容器內部流轉,僅僅是請求處理階段。
getAttribute是返回對象,getParameter返回字符串
總的來說:request.getAttribute()方法返回request范圍內存在的對象,而request.getParameter()方法是獲取http提交過來的數據。
<%@page
language="java"
contentType="text/html;charset=GBK"
%>
<html>
<head>
<title>MyHtml.html</title>
<meta http-equiv="content-type" content="text/html; charset=gbk">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
<script language="javascript">
function JM_PowerList(colNum)
{
headEventObject=event.srcElement;//取得引發事件的對象
while(headEventObject.tagName!="TR") //不是tr行,則從底下的td冒泡上來尋找到相應行
{
headEventObject=headEventObject.parentElement;
}
for (i=0;i<headEventObject.children.length;i++)
{ alert(headEventObject.children[i].tagName);
if (headEventObject.children[i]!=event.srcElement)//找到事件發生的td單元格
{
headEventObject.children[i].className='listTableHead';//把點擊的列的className屬性設為listTableHead
}
}
var tableRows=0;
trObject=clearStart.children[0].children; //取得表格中行對象, 原來這里叫DataTable, 可能是你寫錯了吧??
for (i=0;i<trObject.length;i++)
{
Object=clearStart.children[0].children[i];//取得每行的對象
tableRows=(trObject[i].id=='ignore')?tableRows:tableRows+1;//如果不是忽略行,則行數加一
}
var trinnerHTML=new Array(tableRows);
var tdinnerHTML=new Array(tableRows);
var tdNumber=new Array(tableRows)
var i0=0
var i1=0
for (i=0;i<trObject.length;i++)
{
if (trObject[i].id!='ignore')
{
trinnerHTML[i0]=trObject[i].innerHTML;//把行放在數組里
tdinnerHTML[i0]=trObject[i].children[colNum].innerHTML;//把要排序的行中td的內容放數組里
tdNumber[i0]=i;//行號
i0++;//加一,下個循環用
}
}
sourceHTML=clearStart.children[0].outerHTML;//取得表格中所有tr的html代碼
//對所有td中的字符串進行排序, 算不算冒泡排序???
for (bi=0;bi<tableRows;bi++)
{
for (i=0;i<tableRows;i++)
{
if(tdinnerHTML[i]>tdinnerHTML[i+1])
{
t_s=tdNumber[i+1];
t_b=tdNumber[i];
tdNumber[i+1]=t_b;
tdNumber[i]=t_s;
temp_small=tdinnerHTML[i+1];
temp_big=tdinnerHTML[i];
tdinnerHTML[i+1]=temp_big;
tdinnerHTML[i]=temp_small;
}
}
}
var showshow='';
var numshow='';
for (i=0;i<tableRows;i++)
{
showshow=showshow+tdinnerHTML[i]+' ';//把排序好的td的內容存在showshow字串里
numshow=numshow+tdNumber[i]+'|'; //把排序好的相應的行號也存在numshow中
}
sourceHTML_head=sourceHTML.split("<TBODY>");//從<TBODY>截斷,我試了,前頭串為空
numshow=numshow.split("|");
var trRebuildHTML='';
if (event.srcElement.className=='listHeadClicked')
{//已點擊的列, 則逆排
for (i=0;i<tableRows;i++)
{
trRebuildHTML=trRebuildHTML+trObject[numshow[tableRows-1-i]].outerHTML;//取出排序好的tr的內容連接起來
}
event.srcElement.className='listHeadClicked0';
}
else
{//默認順排,新點擊順排
for (i=0;i<tableRows;i++)
{
trRebuildHTML=trRebuildHTML+trObject[numshow[i]].outerHTML;
}
event.srcElement.className='listHeadClicked';
}
//取得排序后的tr集合結果字符串
var DataRebuildTable='';
//把舊的表格頭和新的tr排序好的元素連接起來, (修改了一下)
DataRebuildTable = "<table border=1 width=100% cellpadding=1 cellspacing=1 id='clearStart'><TBODY>"
+ trObject[0].outerHTML + trRebuildHTML + "</TBODY>" + "</table>";
clearStart.outerHTML=DataRebuildTable;//表格用新串重新寫一次
}
</script>
</head>
<table border=1 id="clearStart">
<tr bgcolor=cccccc id='ignore'>
<td onclick="JM_PowerList(0)">列一
</td>
<td onclick="JM_PowerList(1)">
列二
</td>
<td onclick="JM_PowerList(2)">
列二
</td>
</tr>
<tr>
<td>
周
</td>
<td>
公務員
</td>
<td>
22
</td>
</tr>
<tr>
<td>
張三
</td>
<td>
研究員
</td>
<td>
65
</td>
</tr>
<tr>
<td>
李思
</td>
<td>
科學家
</td>
<td>
24
</td>
</tr>
<tr>
<td>
王武
</td>
<td>
社會學家
</td>
<td>
38
</td>
</tr>
</table>
</body></html>
在基于 Java 語言的編程中,我們經常碰到漢字的處理及顯示的問題。一大堆看不懂的亂碼肯定不是我們愿意看到的顯示效果,怎樣才能夠讓那些漢字正確顯示呢?Java語言默認的編碼方式是UNICODE,而我們中國人通常使用的文件和數據庫都是基于GB2312或者BIG5等方式編碼的,怎樣才能夠恰當地選擇漢字編碼方式并正確地處理漢字的編碼呢?本文將從漢字編碼的常識入手,結合Java編程實例,分析以上兩個問題并提出解決它們的方案。
現在 Java 編程語言已經廣泛應用于互聯網世界,早在 Sun 公司開發 Java 語言的時候,就已經考慮到對非英文字符的支持了。Sun 公司公布的 Java 運行環境(JRE)本身就分英文版和國際版,但只有國際版才支持非英文字符。不過在 Java 編程語言的應用中,對中文字符的支持并非如同 Java Soft 的標準規范中所宣稱的那樣完美,因為中文字符集不只一個,而且不同的操作系統對中文字符的支持也不盡相同,所以會有許多和漢字編碼處理有關的問題在我們進行應用開發中困擾著我們。有很多關于這些問題的解答,但都比較瑣碎,并不能夠滿足大家迫切解決問題的愿望,關于 Java 中文問題的系統研究并不多,本文從漢字編碼常識出發,分析 Java 中文問題,希望對大家解決這個問題有所幫助。
漢字編碼的常識
我們知道,英文字符一般是以一個字節來表示的,最常用的編碼方法是 ASCII 。但一個字節最多只能區分256個字符,而漢字成千上萬,所以現在都以雙字節來表示漢字,為了能夠與英文字符分開,每個字節的最高位一定為1,這樣雙字節最多可以表示64K格字符。我們經常碰到的編碼方式有 GB2312、BIG5、UNICODE 等。關于具體編碼方式的詳細資料,有興趣的讀者可以查閱相關資料。我膚淺談一下和我們關系密切的 GB2312 和 UNICODE。GB2312 碼,中華人民共和國國家標準漢字信息交換用編碼,是一個由中華人民共和國國家標準總局發布的關于簡化漢字的編碼,通行于中國大陸地區及新加坡,簡稱國標碼。兩個字節中,第一個字節(高字節)的值為區號值加32(20H),第二個字節(低字節)的值為位號值加32(20H),用這兩個值來表示一個漢字的編碼。UNICODE 碼是微軟提出的解決多國字符問題的多字節等長編碼,它對英文字符采取前面加“0”字節的策略實現等長兼容。如 “A” 的 ASCII 碼為0x41,UNICODE 就為0x00,0x41。利用特殊的工具各種編碼之間可以互相轉換。
Java 中文問題的初步認識
我們基于 Java 編程語言進行應用開發時,不可避免地要處理中文。Java 編程語言默認的編碼方式是 UNICODE,而我們通常使用的數據庫及文件都是基于 GB2312 編碼的,我們經常碰到這樣的情況:瀏覽基于 JSP 技術的網站看到的是亂碼,文件打開后看到的也是亂碼,被 Java 修改過的數據庫的內容在別的場合應用時無法繼續正確地提供信息。
String sEnglish = “apple”;
String sChinese = “蘋果”;
String s = “蘋果 apple ”;
sEnglish 的長度是5,sChinese的長度是4,而 s 默認的長度是14。對于 sEnglish來說, Java 中的各個類都支持得非常好,肯定能夠正確顯示。但對于 sChinese 和 s 來說,雖然 Java Soft 聲明 Java 的基本類已經考慮到對多國字符的支持(默認 UNICODE 編碼),但是如果操作系統的默認編碼不是 UNICODE ,而是國標碼等。從 Java 源代碼到得到正確的結果,要經過 “Java 源代碼-> Java 字節碼-> ;虛擬機->操作系統->顯示設備”的過程。在上述過程中的每一步驟,我們都必須正確地處理漢字的編碼,才能夠使最終的顯示結果正確。
“ Java 源代碼-> Java 字節碼”,標準的 Java 編譯器 javac 使用的字符集是系統默認的字符集,比如在中文 Windows 操作系統上就是 GBK ,而在 Linux 操作系統上就是ISO-8859-1,所以大家會發現在 Linux 操作系統上編譯的類中源文件中的中文字符都出了問題,解決的辦法就是在編譯的時候添加 encoding 參數,這樣才能夠與平臺無關。用法是
javac -encoding GBK。
“ Java 字節碼->虛擬機->操作系統”, Java 運行環境 (JRE)分英文版和國際版,但只有國際版才支持非英文字符。 Java 開發工具包 (JDK) 肯定支持多國字符,但并非所有的計算機用戶都安裝了 JDK 。很多操作系統及應用軟件為了能夠更好的支持 Java ,都內嵌了 JRE 的國際版本,為自己支持多國字符提供了方便。
“操作系統->顯示設備”,對于漢字來說,操作系統必須支持并能夠顯示它。英文操作系統如果不搭配特殊的應用軟件的話,是肯定不能夠顯示中文的。
還有一個問題,就是在 Java 編程過程中,對中文字符進行正確的編碼轉換。例如,向網頁輸出中文字符串的時候,不論你是用
out.println(string);還是用
<%=string%>,都必須作 UNICODE 到 GBK 的轉換,或者手動,或者自動。在 JSP 1.0中,可以定義輸出字符集,從而實現內碼的自動轉換。用法是
<%@page contentType=”text/html;charset=gb2312” %>
但是在一些 JSP 版本中并沒有提供對輸出字符集的支持,(例如 JSP 0.92),這就需要手動編碼輸出了,方法非常多。最常用的方法是
String s1 = request.getParameter(“keyword”);
String s2 = new String(s1.getBytes(“ISO-8859-1”),”GBK”);
getBytes 方法用于將中文字符以“ISO-8859-1”編碼方式轉化成字節數組,而“GBK” 是目標編碼方式。我們從以ISO-8859-1方式編碼的數據庫中讀出中文字符串 s1 ,經過上述轉換過程,在支持 GBK 字符集的操作系統和應用軟件中就能夠正確顯示中文字符串 s2 。
Java 中文問題的表層分析及處理
背景
開發環境 JDK1.15 Vcafe2.0 JPadPro
服務器端 NT IIS Sybase System Jconnect(JDBC)
客戶端 IE5.0 Pwin98 ?span >
.CLASS 文件存放在服務器端,由客戶端的瀏覽器運行 APPLET , APPLET 只起調入 FRAME 類等主程序的作用。界面包括 Textfield ,TextArea,List,Choice 等。
I.用 JDBC 執行 SELECT 語句從服務器端讀取數據(中文)后,將數據用 APPEND 方法加到 TextArea(TA) ,不能正確顯示。但加到 List 中時,大部分漢字卻可正確顯示。
將數據按“ISO-8859-1” 編碼方式轉化為字節數組,再按系統缺省編碼方式 (Default Character Encoding) 轉化為 STRING ,即可在 TA 和 List 中正確顯示。
程序段如下:
dbstr2 = results.getString(1);
//After reading the result from DB server,converting it to string.
dbbyte1 = dbstr2.getBytes(“iso-8859-1”);
dbstr1 = new String(dbbyte1);
在轉換字符串時不采用系統默認編碼方式,而直接采用“ GBK” 或者 “GB2312” ,在 A 和 B 兩種情況下,從數據庫取數據都沒有問題。
II.處理方式與“取中文”相逆,先將 SQL 語句按系統缺省編碼方式轉化為字節數組,再按“ISO-8859-1”編碼方式轉化為 STRING ,最后送去執行,則中文信息可正確寫入數據庫。
程序段如下:
sqlstmt = tf_input.getText();
//Before sending statement to DB server,converting it to sql statement.
dbbyte1 = sqlstmt.getBytes();
sqlstmt = newString(dbbyte1,”iso-8859-1”);
_stmt = _con.createStatement();
_stmt.executeUpdate(sqlstmt);
……
問題:如果客戶機上存在 CLASSPATH 指向 JDK 的 CLASSES.ZIP 時(稱為 A 情況),上述程序代碼可正確執行。但是如果客戶機只有瀏覽器,而沒有 JDK 和 CLASSPATH 時(稱為 B 情況),則漢字無法正確轉換。
我們的分析:
1.經過測試,在 A 情況下,程序運行時系統的缺省編碼方式為 GBK 或者 GB2312 。在 B 情況下,程序啟動時瀏覽器的 JAVA 控制臺中出現如下錯誤信息:
Can't find resource for sun.awt.windows.awtLocalization_zh_CN
然后系統的缺省編碼方式為“8859-1”。
2.如果在轉換字符串時不采用系統缺省編碼方式,而是直接采用 “GBK” 或“GB2312”,則在 A 情況下程序仍然可正常運行,在 B 情況下,系統出現錯誤:
UnsupportedEncodingException。
3. 在客戶機上,把 JDK 的 CLASSES.ZIP 解壓后,放在另一個目錄中, CLASSPATH 只包含該目錄。然后一邊逐步刪除該目錄中的 .CLASS 文件,另一邊運行測試程序,最后發現在一千多個 CLASS 文件中,只有一個是必不可少的,該文件是:
sun.io.CharToByteDoubleByte.class。
將該文件拷到服務器端和其它的類放在一起,并在程序的開頭 IMPORT 它,在 B 情況下程序仍然無法正常運行。
4.在 A 情況下,如果在 CLASSPTH 中去掉 sun.io.CharToByteDoubleByte.class ,則程序運行時測得默認編碼方式為“8859-1”,否則為 “GBK” 或 “GB2312” 。
如果 JDK 的版本為1.2以上的話,在 B 情況下遇到的問題得到了很好的解決,測試的步驟同上,有興趣的讀者可以嘗試一下。
Java 中文問題的根源分析及解決
在簡體中文 MS Windows 98 + JDK 1.3 下,可以用 System.getProperties() 得到 Java 運行環境的一些基本屬性,類 PoorChinese 可以幫助我們得到這些屬性。
類 PoorChinese 的源代碼:
public class PoorChinese {
}
執行 java PoorChinese 后,我們會得到:
系統變量 file.encoding 的值為 GBK ,user.language 的值為 zh , user.region 的值為 CN ,這些系統變量的值決定了系統默認的編碼方式是 GBK 。
在上述系統中,下面的代碼將 GB2312 文件轉換成 Big5 文件,它們能夠幫助我們理解 Java 中漢字編碼的轉化:
import java.io.*;
import java.util.*;
public class gb2big5 {
static int iCharNum=0;
public static void main(String[] args) {
System.out.println("Input GB2312 file, output Big5 file.");
if (args.length!=2) {
System.err.println("Usage: jview gb2big5 gbfile big5file");
System.exit(1);
String inputString = readInput(args[0]);
writeOutput(inputString,args[1]);
System.out.println("Number of Characters in file: "+iCharNum+".");
}
static void writeOutput(String str, String strOutFile) {
try {
FileOutputStream fos = new FileOutputStream(strOutFile);
Writer out = new OutputStreamWriter(fos, "Big5");
out.write(str);
out.close();
}
catch (IOException e) {
e.printStackTrace();
e.printStackTrace();
}
}
static String readInput(String strInFile) {
StringBuffer buffer = new StringBuffer();
try {
FileInputStream fis = new FileInputStream(strInFile);
InputStreamReader isr = new InputStreamReader(fis, "GB2312");
Reader in = new BufferedReader(isr);
int ch;
while ((ch = in.read()) > -1) {
iCharNum += 1;
buffer.append((char)ch);
}
in.close();
return buffer.toString();
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
編碼轉化的過程如下:
GB2312------------------>Unicode------------->Big5
執行 java gb2big5 gb.txt big5.txt ,如果 gb.txt 的內容是“今天星期三”,則得到的文件 big5.txt 中的字符能夠正確顯示;而如果 gb.txt 的內容是“情人節快樂”,則得到的文件 big5.txt 中對應于“節”和“樂”的字符都是符號“?”(0x3F),可見 sun.io.ByteToCharGB2312 和 sun.io.CharToByteBig5 這兩個基本類并沒有編好。
正如上例一樣, Java 的基本類也可能存在問題。由于國際化的工作并不是在國內完成的,所以在這些基本類發布之前,沒有經過嚴格的測試,所以對中文字符的支持并不像 Java Soft 所聲稱的那樣完美。前不久,我的一位技術上的朋友發信給我說,他終于找到了 Java Servlet 中文問題的根源。兩周以來,他一直為 Java Servlet 的中文問題所困擾,因為每面對一個含有中文字符的字符串都必須進行強制轉換才能夠得到正確的結果(這好象是大家公認的唯一的解決辦法)。后來,他確實不想如此繼續安分下去了,因為這樣的事情確實不應該是高級程序員所要做的工作,他就找出 Servlet 解碼的源代碼進行分析,因為他懷疑問題就出在解碼這部分。經過四個小時的奮斗,他終于找到了問題的根源所在。原來他的懷疑是正確的, Servlet 的解碼部分完全沒有考慮雙字節,直接把 %XX 當作一個字符。(原來 Java Soft 也會犯這幺低級的錯誤!)
如果你對這個問題有興趣或者遇到了同樣的煩惱的話,你可以按照他的步驟 對Servlet.jar 進行修改:
找到源代碼 HttpUtils 中的 static private String parseName ,在返回前將 sb(StringBuffer) 復制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解碼了:
HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者
form=HttpUtils.parsePostData(……)
千萬別忘了編譯后放到 Servlet.jar 里面。
關于 Java 中文問題的總結
Java 編程語言成長于網絡世界,這就要求 Java 對多國字符有很好的支持。 Java 編程語言適應了計算的網絡化的需求,為它能夠在網絡世界迅速成長奠定了堅實的基礎。 Java 的締造者 (Java Soft) 已經考慮到 Java 編程語言對多國字符的支持,只是現在的解決方案有很多缺陷在里面,需要我們付諸一些補償性的措施。而世界標準化組織也在努力把人類所有的文字統一在一種編碼之中,其中一種方案是 ISO10646 ,它用四個字節來表示一個字符。當然,在這種方案未被采用之前,還是希望 Java Soft 能夠嚴格地測試它的產品,為用戶帶來更多的方便。
<div id="img" style="position:absolute;">
<a ; target="_blank">
<img src="http://www.freedown.net/logo.gif"; border="0"></a>
</div>
<SCRIPT LANGUAGE="JavaScript">
<!--
var xPos = 20;
var yPos = document.body.clientHeight;
var step = 1;
var delay = 30;
var height = 0;
var Hoffset = 0;
var Woffset = 0;
var yon = 0;
var xon = 0;
var pause = true;
var interval;
img.style.top = yPos;
function changePos() {
width = document.body.clientWidth;
height = document.body.clientHeight;
Hoffset = img.offsetHeight;
Woffset = img.offsetWidth;
img.style.left = xPos + document.body.scrollLeft;
img.style.top = yPos + document.body.scrollTop;
if (yon) {
yPos = yPos + step;
}
else {
yPos = yPos - step;
}
if (yPos < 0) {
yon = 1;
yPos = 0;
}
if (yPos >= (height - Hoffset)) {
yon = 0;
yPos = (height - Hoffset);
}
if (xon) {
xPos = xPos + step;
}
else {
xPos = xPos - step;
}
if (xPos < 0) {
xon = 1;
xPos = 0;
}
if (xPos >= (width - Woffset)) {
xon = 0;
xPos = (width - Woffset);
}
}
function www_helpor_net() {
img.visibility = "visible";
interval = setInterval('changePos()', delay);
}
www_helpor_net();
//For more,visit:www.helpor.net
-->
</script>
在html中如何使一個button或text控件不可見?
<INPUT type="button" value="Button" id=button1 name=button1 disabled>
<INPUT type="text" id=text1 name=text1 readonly>
<INPUT type="hidden" value="Button" id=button1 name=button1 >
<INPUT type="text" id=text1 name=text1 style="visibility:hidden">
<INPUT type="button" value="Button" id=button1 name=button1 style="visibility:hidden">
可以在jsp中做如下控制:
<INPUT TYPE="button" NAME="xzdq" value="<<" onClick="selectDept()" >
<script language="javascript">
if('<%=userId%>'!= null)document.all.xzdq.style.visibility="hidden";
</script>
或者:
<INPUT TYPE="button" NAME="xzdq" value="<<" onClick="selectDept()" >
<%
if(userId != null)
{
%>
<script language="javascript">
document.all.xzdq.style.visibility="hidden";
</script>
<%
}
%>
或者:
<INPUT TYPE="button" NAME="xzdq" value="<<" onClick="selectDept()" >
<%
if(userId != null)
out.println("<script language=\"javascript\"> document.all.xzdq.style.visibility=\"hidden\";</script>");
%>
安裝Tomcat之前要先安裝JDK,可從http://java.sun.com上下載最新版本的JDK。Tomcat可從Apache Jakarta Project站點(http://jakarta.apache.org/site/binindex.cgi)上下載,本書使用的Tomcat版本是5.5.7,它需要安裝J2SE 5.0(JDK 1.5)以上的版本才能運行。對于Windows操作系統,Tomcat 5.5.7提供了兩種安裝文件,一種是jakarta-tomcat-5.5.7.exe,一種是jakarta-tomcat-5.5.7.zip(如果讀者使用的是Linux系統,請下載jakarta-tomcat-5.5.7.tar.gz)。jakarta-tomcat-5.5.7.exe是可執行的安裝程序,讀者只需要雙擊這個文件,就可以開始安裝Tomcat了。在安裝過程中,安裝程序會自動搜尋JDK和JRE的位置。安裝完成后,在Windows系統的“開始”->“程序”菜單下會添加Apache Tomcat 5.5菜單組。jakarta-tomcat-5.5.7.zip是一個壓縮包,只需要將它解壓到硬盤上就可以了。在這里,我建議讀者下載jakarta-tomcat-5.5.7.zip壓縮包,通過解壓縮的方式安裝Tomcat,因為解壓縮的方式也適用于其他的操作系統,例如Linux系統。下面我們主要介紹jakarta-tomcat-5.5.7.zip的安裝與Tomcat運行環境的設置。
安裝Tomcat
使用WinZip或WinRAR等解壓縮工具將jakarta-tomcat-5.5.7.zip解壓到指定的驅動器和目錄中。筆者是在D盤上直接解壓,產生了目錄jakarta-tomcat-5.5.7,解壓后的文件存放于D:\ jakarta-tomcat-5.5.7下。
Tomcat安裝后的目錄層次結構如圖5-2所示。

圖5-2 Tomcat 5.5.7目錄層次結構
各目錄的用途如表5-1所示。
表5-1 Tomcat的目錄結構及其用途
目 錄
|
用 途
|
/bin
|
存放啟動和關閉Tomcat的腳本文件
|
/common/lib
|
存放Tomcat服務器及所有Web應用程序都可以訪問的JAR文件
|
/conf
|
存放Tomcat服務器的各種配置文件,其中包括server.xml(Tomcat的主要配置文件)、tomcat-users.xml和web.xml等配置文件
|
/logs
|
存放Tomcat的日志文件
|
/server/lib
|
存放Tomcat服務器運行所需的各種JAR文件
|
/server/webapps
|
存放Tomcat的兩個Web應用程序:admin應用程序和manager應用程序
|
/shared/lib
|
存放所有Web應用程序都可以訪問的JAR文件
|
/temp
|
存放Tomcat運行時產生的臨時文件
|
/webapps
|
當發布Web應用程序時,通常把Web應用程序的目錄及文件放到這個目錄下
|
/work
|
Tomcat將JSP生成的Servlet源文件和字節碼文件放到這個目錄下
|
從表5-1中可以看到,/common/lib目錄、/server/lib和/shared/lib目錄下都可以存放JAR文件,它們的區別在于:
— 在/server/lib目錄下的JAR文件只能被Tomcat服務器訪問;
— 在/shared/lib目錄下的JAR文件可以被所有的Web應用程序訪問,但不能被Tomcat服務器訪問;
— 在/common/lib目錄下的JAR文件可以被Tomcat服務器和所有的Web應用程序訪問。
此外,對于后面將要介紹的Java Web應用程序,在它的WEB-INF目錄下,也可以建立lib子目錄,在lib子目錄下可以存放各種JAR文件,這些JAR文件只能被當前Web應用程序所訪問。
運行Tomcat
在Tomcat安裝目錄下的bin子目錄中,有一些批處理文件(以.bat作為后綴名的文件),其中的startup.bat就是啟動Tomcat的腳本文件,用鼠標雙擊這個文件,將會看到如圖5-3所示的畫面。

圖5-3 運行Tomcat提示出錯信息
筆者以前碰到過很多學員,在初次運行Tomcat時,看到如圖5-3所示的信息就不知所措了。有的學員以前還配置過Tomcat,但是再次使用的時候,由于忘記了上次是如何配置的,同樣感覺無從下手。
我們在學習軟件開發時,一定要養成查看錯誤提示信息,進而根據錯誤提示解決問題的良好習慣。筆者第一次配置Tomcat時,就是根據錯誤提示信息一步一步配置成功的。很多人一看見錯誤信息,立即單擊“確定”按鈕,這樣就錯過了提示信息。當看到錯誤信息時,首先不要慌張和無所適從,仔細看清楚錯誤提示,不要著急單擊按鈕。
查看圖5-3中的錯誤提示信息,可以看到這樣一句話“The JAVA_HOME environment variable is not defined”,從畫面中可以看到,在執行到“Using JAVA_HOME”這句時出現了錯誤,由此,我們可以想到,出錯的原因可能是因為沒有設置JAVA_HOME環境變量。那么JAVA_HOME環境變量的值應該是什么呢?很容易就能想到應該是JDK所在的目錄,在筆者的機器上,JDK所在的目錄是D:\Java\jdk1.5.0_01。
在Windows 2000操作系統下設置環境變量的步驟如下。
① 在桌面“我的電腦”上單擊右鍵,選擇“屬性”,出現如圖5-4所示的畫面。

圖5-4 “我的電腦”屬性
② 單擊“高級”選項卡,選擇“環境變量(E)…”,如圖5-5和圖5-6所示。


圖5-5 “高級”選項卡 圖5-6 “環境變量”對話框
③ 在“系統變量”下方單擊“新建”按鈕。在“變量名”中輸入“JAVA_HOME”,在變量值中輸入JDK所在的目錄“D:\Java\jdk1.5.0_01”,然后單擊“確定”按鈕,如圖5-7所示。

圖5-7 新建JAVA_HOME環境變量
④ 最后在“環境變量”對話框上單擊“確定”按鈕,結束JAVA_HOME環境變量的設置。
我們再一次轉到D:\ jakarta-tomcat-5.5.7\bin目錄下,用鼠標雙擊startup.bat文件,可以看到如圖5-8所示的啟動信息。

圖5-8 Tomcat啟動信息
然后,打開瀏覽器,在地址欄中輸入http://localhost:8080/(localhost表示本地機器,8080是Tomcat默認監聽的端口號),將出現如圖5-9所示的Tomcat頁面。

圖5-9 Tomcat的默認主頁
注意圖5-9中鼠標(小手形狀)指向的鏈接——Tomcat Documentation,單擊將進入Tomcat的文檔頁面,有關Tomcat的幫助信息可以在文檔頁面中找到;讀者也可以直接訪問Tomcat的文檔,文檔首頁的位置是Tomcat安裝目錄下的webapps\tomcat-docs\index.html。如果要關閉Tomcat服務器,可以用鼠標雙擊D:\ jakarta-tomcat-5.5.7\bin目錄下的shutdown.bat文件。
如果你機器上的Tomcat啟動失敗,有可能是因為TCP的8080端口被其他應用程序所占用,如果你知道是哪一個應用程序占用了8080端口,那么先關閉此程序。如果你不知道或者不想關閉占用8080端口的應用程序,你可以修改Tomcat默認監聽的端口號。
前面介紹了,Tomcat安裝目錄下的conf子目錄用于存放Tomcat服務器的各種配置文件,其中的server.xml是Tomcat的主要配置文件,這是一個格式良好的XML文檔,在這個文件中可以修改Tomcat默認監聽的端口號。用UltraEdit(你可以用記事本程序或其他的文本編輯工具)打開server.xml,找到修改8080端口的地方。讀者也許要問了,“這個配置文件,我都不熟悉,怎么知道在哪里修改端口號呢?”對于初次接觸server.xml的讀者,確實不了解這個文件的結構,但是我們應該有一種開放的思路,既然Tomcat的監聽端口號是在server.xml中配置,那么只要我們在這個文件中查找“8080”這些數字字符序列,不就能找到修改端口號的地方了嗎!在UltraEdit中,同時按下鍵盤上的“Ctrl”和“F”鍵,出現如圖5-10所示的查找對話框。

圖5-10 UltraEdit查找對話框
然后在“查找內容”中輸入“8080”,單擊“查找下一個”按鈕。重復這個過程,直到找到如圖5-11所示的在server.xml中配置端口號位置。

圖5-11 server.xml中配置端口號的位置
找到后,如果我們不能確定此處就是修改端口號的地方,也沒有關系,可以先嘗試著修改一下端口號,然后啟動Tomcat,如果啟動成功,也就證明了我們修改的地方是正確的。學習時,我們應該養成這種探索并不斷實驗的精神。在這里,我們可以修改端口號為8000(讀者可以根據自己機器的配置選擇一個端口號),然后保存。再次啟動Tomcat,在Tomcat啟動完畢后,打開瀏覽器,在地址欄中輸入http://localhost:8000/(讀者根據自己設置的端口號做相應的修改),就可以看到Tomcat的默認主頁了。關閉Tomcat服務器時,執行bin目錄下的shutdown.bat文件。
Tomcat啟動分析
在本節中我們將通過對Tomcat啟動過程的分析,來幫助讀者更好地理解和掌握Tomcat。
用文本編輯工具打開用于啟動Tomcat的批處理文件startup.bat,仔細閱讀,可以發現,在這個文件中,首先判斷CATALINA_HOME環境變量是否為空,如果為空,就將當前目錄設為CATALINA_HOME的值,接著判斷當前目錄下是否存在bin\catalina.bat,如果文件不存在,將當前目錄的父目錄設為CATALINA_HOME的值,根據筆者機器上Tomcat安裝目錄的層次結構,最后CATALINA_HOME的值被設為Tomcat的安裝目錄。如果環境變量CATALINA_HOME已經存在,則通過這個環境變量調用bin目錄下的“catalina.bat start”命令。通過這段分析,我們了解到兩個信息,一是Tomcat啟動時,需要查找CATALINA_HOME這個環境變量,如果在當前目錄下調用startup.bat,Tomcat會自動設置CATALINA_HOME;二是執行startup.bat命令,實際上執行的是“catalina.bat start”命令。
如果我們不是在bin目錄作為當前目錄時調用startup.bat,就會出現如圖5-12所示的錯誤信息(在bin目錄的父目錄下調用除外)。

圖5-12 在其他目錄下啟動Tomcat出錯
要在其他目錄下也能啟動Tomcat,就需要設置CATALINA_HOME環境變量,你可以將CATALINA_HOME添加到Windows 2000系統的環境變量中,其值就是Tomcat的安裝目錄,在筆者的機器上安裝目錄是D:\jakarta-tomcat-5.5.7,添加環境變量的過程和前述添加JAVA_HOME環境變量的過程是一樣的。如果你不想在系統的環境變量中添加,也可以直接在startup.bat文件中進行設置。下面是在startup.bat文件中設置CATALINA_HOME后的文件片段:
……
rem $Id: shutdown.bat,v 1.5 2004/05/27 15:05:01 yoavs Exp $
rem ---------------------------------------------------------------------------
set CATALINA_HOME=D:\jakarta-tomcat-5.5.7
rem Guess CATALINA_HOME if not defined
set CURRENT_DIR=%cd%
if not "%CATALINA_HOME%" == "" goto gotHome
set CATALINA_HOME=%CURRENT_DIR%
……
注意以粗體顯示的這句話的作用就是設置CATALINA_HOME環境變量。在它的下面就可以判斷CATALINA_HOME是否為空了。如果你找不準位置,干脆將設置CATALINA_HOME環境變量的這句話放置到文件的第一行。JAVA_HOME環境變量也可以采用同樣的方式進行設置。不過,如果你要在其他目錄下,利用shutdown.bat來關閉Tomcat服務器,你也需要在shutdown.bat文件中設置CATALINA_HOME和JAVA_HOME這兩個環境變量,設置變量的位置和startup.bat文件一樣,都是在判斷CATALINA_HOME是否為空之前。當然,為了一勞永逸,避免重裝Tomcat后還要進行設置(需要是同一版本的Tomcat安裝在同一位置),我們最好還是將CATALINA_HOME和JAVA_HOME這兩個環境變量添加到Windows 2000系統的環境變量中。
有的讀者可能會對設置Tomcat安裝目錄的環境變量的名字是CATALINA_HOME而感到奇怪,按照以前設置的環境變量來看,JAVA_HOME表示JDK的安裝目錄,那么應該用TOMCAT_HOME來表示Tomcat的安裝目錄,可為什么要使用CATALINA_HOME呢?實際上,在Tomcat 4以前,用的就是TOMCAT_HOME來表示Tomcat的安裝目錄,在Tomcat 4以后,采用了新的Servlet容器Catalina,所以環境變量的名字也改為了CATALINA_HOME。
提示:在Windows系統下環境變量的名字是與大小寫無關的,也就是說JAVA_HOME和java_home是相同的。
了解了startup.bat文件以后,我們再來看看真正負責啟動Tomcat服務器的catalina.bat文件。通過分析catalina.bat文件,我們發現它還調用了一個文件setclasspath.bat。在setclasspath.bat文件中,它檢查JAVA_HOME環境變量是否存在,并通過設置的環境變量JAVA_HOME,找到java.exe,用于啟動Tomcat。在這個文件中,還設置了其他的一些變量,分別表示JDK中的一些工具,有興趣的讀者可以自行分析一下這個文件。在執行完setclasspath.bat之后,catalina.bat剩下的部分就開始了Tomcat服務器的啟動進程。
直接執行catalina.bat時,需要帶上命令行的參數。讀者可以在命令提示符窗口下,執行catalina.bat,就會打印出catalina.bat命令的各種參數及其含義,如圖5-13所示。

圖5-13 catalina.bat的各參數信息
其中常用的參數是start、run和stop,參數start表示在一個單獨的窗口中啟動Tomcat服務器,參數run表示在當前窗口中啟動Tomcat服務器,參數stop表示關閉Tomcat服務器。我們執行startup.bat,實際上執行的就是“catalina.bat start”命令;執行shutdown.bat,實際上執行的是“catalina.bat stop”命令。“catalina.bat run”命令有時候是非常有用的,特別是當我們需要查看Tomcat的出錯信息時。我們在開發JSP程序時,經常會碰到自己機器上的8080端口號被別的應用程序占用,或者在配置server.xml時出現錯誤,當通過startup.bat(相當于執行“catalina.bat start”)啟動Tomcat服務器時,會導致啟動失敗,因為是在單獨的窗口中啟動Tomcat服務器,所以一旦啟動失敗,命令提示符窗口就自動關閉了,程序運行中輸出的出錯信息也隨之消失,而且沒有任何的日志信息,這就使得我們沒有辦法找出錯誤原因。當出現錯誤時,我們可以換成“catalina.bat run”命令再次啟動,一旦啟動失敗,僅僅是Tomcat服務器異常終止,但是在當前的命令提示符窗口下仍然保留了啟動時的出錯信息,這樣我們就可以查找啟動失敗的原因了。
Tomcat的體系結構
Tomcat服務器是由一系列可配置的組件構成的,其中核心組件是Catalina Servlet容器,它是所有其他Tomcat組件的頂層容器。Tomcat各組件之間的層次關系如圖5-14所示。

圖5-14 Tomcat組件之間的層次結構
我們下面簡單介紹一下各組件在Tomcat服務器中的作用。
(1)Server
Server表示整個的Catalina Servlet容器。Tomcat提供了Server接口的一個默認實現,這通常不需要用戶自己去實現。在Server容器中,可以包含一個或多個Service組件。
(2)Service
Service是存活在Server中的內部組件,它將一個或多個連接器(Connector)組件綁定到一個單獨的引擎(Engine)上。在Server中,可以包含一個或多個Service組件。Service也很少由用戶定制,Tomcat提供了Service接口的默認實現,而這種實現既簡單又能滿足應用。
(3)Connector
連接器(Connector)處理與客戶端的通信,它負責接收客戶請求,以及向客戶返回響應結果。在Tomcat中,有多個連接器可以使用。
(4)Engine
在Tomcat中,每個Service只能包含一個Servlet引擎(Engine)。引擎表示一個特定的Service的請求處理流水線。作為一個Service可以有多個連接器,引擎從連接器接收和處理所有的請求,將響應返回給適合的連接器,通過連接器傳輸給用戶。用戶可以通過實現Engine接口提供自定義的引擎,但通常不需要這么做。
(5)Host
Host表示一個虛擬主機,一個引擎可以包含多個Host。用戶通常不需要創建自定義的Host,因為Tomcat給出的Host接口的實現(類StandardHost)提供了重要的附加功能。
(6)Context
一個Contex表示了一個Web應用程序,運行在特定的虛擬主機中。什么是Web應用程序呢?在Sun公司發布的Java Servlet規范中,對Web應用程序做出了如下的定義:“一個Web應用程序是由一組Servlet、HTML頁面、類,以及其他的資源組成的運行在Web服務器上的完整的應用程序。它可以在多個供應商提供的實現了Servlet規范的Web容器中運行”。一個Host可以包含多個Context(代表Web應用程序),每一個Context都有一個惟一的路徑。用戶通常不需要創建自定義的Context,因為Tomcat給出的Context接口的實現(類StandardContext)提供了重要的附加功能。
下面我們通過圖5-15來幫助讀者更好地理解Tomcat服務器中各組件的工作流程。

圖5-15 Tomcat各組件的工作流程
要了解這些組件的其他信息,可以看下面的頁面:
%CATALINA_HOME%\webapps\tomcat-docs\architecture\index.html
我們可以在conf目錄下的server.xml文件中對這些組件進行配置,讀者打開server.xml文件,就可以看到元素名和元素之間的嵌套關系,與Tomcat服務器的組件是一一對應的,server.xml文件的根元素就是server。關于server.xml配置文件中的各元素及其屬性的含義,請參見附錄C。
在Tomcat中,提供了各組件的接口及其實現類,如果你要替代Tomcat中的某個組件,只需要根據該組件的接口或類的說明,重寫該組件,并進行配置即可。圖5-16是Tomcat各組件的類圖。

在類圖的接口名或類名下面是該接口或該類所在的包,這些接口和類都在%CATALINA_HOME%\ server\lib\catalina.jar文件中。對Tomcat服務器的實現感興趣的讀者,可以從http://tomcat.apache.org/上下載Tomcat的源代碼。
提示:由于Apache軟件基金會并不是一個商業性的組織,所以文檔更新的速度有時候跟不上版本更新的速度。在Tomcat 5.5.7中,就可以發現文檔與其源碼實現有不一致的地方。在Tomcat 5.5.x中,去掉了org.apache.catalina.Connector接口及其相關的實現類,而直接以org.apache.catalina.connector.Connector類來代替。我們在看Tomcat的文檔時,最好結合其API文檔一起看,這樣才能保證了解的信息是完整的和準確的。
Tomcat提供了兩個管理程序:admin和manager。其中admin用于管理和配置Tomcat服務器,manager用于管理部署到Tomcat服務器中的Web應用程序。
admin Web應用程序
admin Web應用程序需要單獨下載,與Tomcat在同一個下載頁面,鏈接名是Admin zip,下載后的文件名是jakarta-tomcat-5.5.7-admin.zip,解壓縮后,覆蓋Tomcat安裝目錄下的同名目錄。admin Web應用程序位于%CATALINA_HOME%\server\webapps\admin目錄下。
要訪問admin Web應用程序,需要添加具有管理員權限的賬號,編輯%CATALINA_HOME%\ conf\tomcat-users.xml文件,在<tomcat-users>元素中添加如下內容:
<user username="admin" password="12345678" roles="admin"/>
其中用戶名和密碼可以根據自己的喜好設置。
啟動Tomcat服務器,打開瀏覽器,在地址欄中輸入:
http://localhost:8080/admin/
將出現如圖5-17所示的頁面。

圖5-17 admin Web應用程序的登錄界面
也可以在Tomcat的默認主頁的左上方單擊“Tomcat Administration”鏈接,進入admin登錄頁面。輸入用戶名admin,密碼12345678,單擊“Login”按鈕,將看到如圖5-18所示的頁面。

圖5-18 admin Web應用程序的主頁面
在這個頁面中,可以進行Tomcat服務器的各項配置。
5.6.2 manager Web應用程序
manager Web應用程序包含在Tomcat的安裝包中。和admin程序一樣,需要添加訪問manager Web應用程序的管理員賬號,編輯%CATALINA_HOME%\conf\tomcat-users.xml文件,在<tomcat-users>元素中添加如下內容:
<user username="manager" password="12345678" roles="manager"/>
其中用戶名和密碼可以根據自己的喜好設置。
啟動Tomcat服務器,打開瀏覽器,在地址欄中輸入:
http://localhost:8080/manager/html/
將出現如圖5-19所示的頁面。

圖5-19 manager Web應用程序的登錄界面
也可以在Tomcat的默認主頁的左上方單擊“Tomcat Manager”鏈接,訪問manager程序。輸入用戶名manager,密碼12345678,單擊“確定”按鈕,將看到如圖5-20所示的頁面。

圖5-20 manager Web應用程序的主頁面
在這個頁面中,你可以部署、啟動、停止、重新加載、卸載Web應用程序。注意在兩個圓角矩形框中的路徑“/jsp-examples”和“/servlets-examples”,單擊這兩個路徑,將看到Tomcat提供的JSP和Servlet的例子程序,這些程序可以作為學習JSP和Servlet的參考。不過在這兩個路徑下,只列出了部分的例子程序,完整的JSP和Servlet例子程序位于下面的兩個目錄中:
%CATALINA_HOME%\webapps\jsp-examples
%CATALINA_HOME%\webapps\servlets-examples
void foo() throws FooException,Foo2Exception{
//other code
throw new FooException();
}
throw語句用在方法體內,表示拋出異常,由方法體內的語句處理。不能單獨使用,要么和try catch一起使用,要么和trows一起使用。
throws語句用在方法聲明后面,表示這個方法可能會拋出異常, 表示的是一種傾向、可能,但不一定實際發生。由調用這個方法的上一級方法中的語句來處理 。后面可以跟多個異常,中間用逗號分割。
例如:
void doA() throws Exception1, Exception3 {
try {
……
} catch(Exception1 e) {
throw e;
} catch(Exception2 e) {
System.out.println("出錯了");
}
if (a != b)
throw new Exception3("自定義異常");
}
代碼塊……中可能產生異常Exception1、Exception2和Exception3。
如果產生Exception1異常,則捕捉了之后拋出由該方法的調用者去做處理;
如果產生Exception2異常,則該方法自己做了處理(打印出了說出錯了),所以該方法就不會再向外拋出Exception2異常了,void doA() throws Exception1,,Excpetion3里面的Exception2也就不用寫了;
而Exception3異常是該方法的某段邏輯出錯,程序員自己作了處理在該段邏輯錯誤的情況下拋出異常Exception3,則調用者也需要處理。
關于JSP中的錯誤頁面處理
通常JSP 在執行時,在兩個階段下會發生錯誤。
JSP 網頁 → Servlet 類
Servlet 類處理每一個請求時
在第一階段時,產生的錯誤我們稱為Translation Time Processing Errors;在第二階段時,產生的錯誤我們稱為Client Request Time Processing Errors。接下來我們針對這兩個階段產生錯誤的原因和處理方法,進行介紹。
1、 Translation Time Processing Errors
Translation Time Processing Errors 產生的主要原因:我們在撰寫JSP時的語法有錯誤,導致JSP Container 無法將JSP 網頁編譯成Servlet 類文件( .class),例如:500 Internal Server
Error,500 是指HTTP 的錯誤狀態碼,因此是Server Error。
通常產生這種錯誤時,可能是JSP 的語法有錯誤,或是JSP Container 在一開始安裝、設定時,有不適當的情形發生。解決的方法就是再一次檢查程序是否有寫錯的,不然也有可能是JSPContainer 的bug。
2、 Client Request Time Processing Errors
Client Request Time Processing Errors 錯誤的發生,往往不是語法錯誤,而可能是邏輯上的錯誤,簡單地說,你寫一個計算除法的程序,當用戶輸入的分母為零時,程序會發生錯誤并拋出異常(Exception),交由異常處理(Exception Handling)機制做適當的處理。對于這種錯誤的處理,我們通常會交給errorPage 去處理。下面舉個例子:
使用errorPage 的范例程序 :ErrorPage.jsp
<%@ page contentType="text/html;charset=GB2312" errorPage="Error.jsp" %> //設置Error.jsp頁為本頁的錯誤處理頁
<html>
<head>
<title>CH4 - ErrorPage.jsp</title>
</head>
<body>
<h2>errorPage 的范例程序</h2>
<%!
private double toDouble(String value)
{
return(Double.valueOf(value).doubleValue());
}
%>
<%
double num1 = toDouble(request.getParameter("num1"));
double num2 = toDouble(request.getParameter("num2"));
%>
您傳入的兩個數字為:<%= num1 %> 和 <%= num2 %><br>
兩數相加為 <%= (num1+num2) %>
</body>
</html>
ErrorPage.jsp 程序中,我們使用page 指令中的errorPage 屬性,告訴JSP Container,如果在程序中有錯誤產生時(指的是servlet運行時產生的錯誤或顯示用throw拋出的異常),會自動交給Error.jsp 處理。
Error.jsp
<%@ page contentType="text/html;charset=GB2312" isErrorPage="true" %> //設置該頁為錯誤處理頁
<%@ page import="java.io.PrintWriter" %>
<html>
<head>
<title>CH4 - Error.jsp</title>
</head>
<body>
<h2>errorPage 的范例程序</h2>
<p>ErrorPage.jsp 錯誤產生:<I><%= exception %></I></p><br>
<pre>
問題如下:<% exception.printStackTrace(new PrintWriter(out)); %> //輸出錯誤的原因
</pre>
</body>
</html>
Error.jsp 主要處理ErrorPage.jsp 所產生的錯誤,所以在ErrorPage.jsp 中page 指令的屬性errorPage設為Error.jsp,因此,若ErrorPage.jsp有錯誤發生時,會自動轉到Error.jsp來處理。Error.jsp 必須設定page 指令的屬性isErrorPage為true,因為Error.jsp是專門用來處理錯誤的網頁。設定page 指令的屬性isErrorPage為true后,在Error.jsp里就可以使用exception異常類了。
由于在這個程序中并沒有做一個窗體來輸入兩個數字,所以必須手動在URL后輸入num1和num2的值,如http://localhost:8080/ErrorPage.jsp?num1=100&num2=245。當ErrorPage.jsp 產生錯誤時(如加數為字符串型),就會交由Error.jsp 去處理,所以我們看到的結果,不
再是原始的服務器提示的亂七把糟的錯誤提示,將是執行Error.jsp 后的結果。
在jsp中顯式地拋出異常時,系統也會轉到錯誤處理頁面:
<%@page language="java" contentType="text/html;charset=GBK" import= "java.util.* " errorPage="exception.jsp"
%>
<jsp:useBean id="user" scope="session" class="S_userObj"/>
<%
if(user= =null)
throw new Exception("您沒有登陸或者登陸超時,請重新登陸");
。。。 。。。
當user為空時系統會自動轉到錯誤處理頁面。
總之,以下情況jsp會轉到錯誤處理頁面:
前提:jsp成功的轉換到了servlet,轉換過程中沒有發生錯誤。
當servlet運行時,程序中碰到異常。
當servlet運行時,程序控制轉到了顯式拋出的異常。例如:if(…) throw new exception();
正則表達式基礎知識
2007-11-16 17:32
一個正則表達式就是由普通字符(例如字符 a 到 z)以及特殊字符(稱為元字符)組成的文字模式。該模式描述在查找文字主體時待匹配的一個或多個字符串。正則表達式作為一個模板,將某個字符模式與所搜索的字符串進行匹配。如:
JScript |
VBScript |
匹配 |
/^\[ \t]*$/ |
"^\[ \t]*$" |
匹配一個空白行。 |
/\d{2}-\d{5}/ |
"\d{2}-\d{5}" |
驗證一個ID 號碼是否由一個2位數字,一個連字符以及一個5位數字組成。 |
/<(.*)>.*<\/\1>/ |
"<(.*)>.*<\/\1>" |
匹配一個 HTML 標記。 |
下表是元字符及其在正則表達式上下文中的行為的一個完整列表:
字符 |
描述 |
\ |
將下一個字符標記為一個特殊字符、或一個原義字符、或一個 向后引用、或一個八進制轉義符。例如,'n' 匹配字符 "n"。'\n' 匹配一個換行符。序列 '\\' 匹配 "\" 而 "\(" 則匹配 "("。 |
^ |
匹配輸入字符串的開始位置。如果設置了 RegExp 對象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
$ |
匹配輸入字符串的結束位置。如果設置了RegExp 對象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
* |
匹配前面的子表達式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價于{0,}。 |
+ |
匹配前面的子表達式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價于 {1,}。 |
? |
匹配前面的子表達式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價于 {0,1}。 |
{n} |
n 是一個非負整數。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。 |
{n,} |
n 是一個非負整數。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價于 'o+'。'o{0,}' 則等價于 'o*'。 |
{n,m} |
m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價于 'o?'。請注意在逗號和兩個數之間不能有空格。 |
? |
當該字符緊跟在任何一個其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面時,匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對于字符串 "oooo",'o+?' 將匹配單個 "o",而 'o+' 將匹配所有 'o'。 |
. |
匹配除 "\n" 之外的任何單個字符。要匹配包括 '\n' 在內的任何字符,請使用象 '[.\n]' 的模式。 |
(pattern) |
匹配 pattern 并獲取這一匹配。所獲取的匹配可以從產生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中則使用 $0…$9 屬性。要匹配圓括號字符,請使用 '\(' 或 '\)'。 |
(?:pattern) |
匹配 pattern 但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供以后使用。這在使用 "或" 字符 (|) 來組合一個模式的各個部分是很有用。例如, 'industr(?:y|ies) 就是一個比 'industry|industries' 更簡略的表達式。 |
(?=pattern) |
正向預查,在任何匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如,'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。 |
(?!pattern) |
負向預查,在任何不匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始 |
x|y |
匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 則匹配 "zood" 或 "food"。 |
[xyz] |
字符集合。匹配所包含的任意一個字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。 |
[^xyz] |
負值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。 |
[a-z] |
字符范圍。匹配指定范圍內的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范圍內的任意小寫字母字符。 |
[^a-z] |
負值字符范圍。匹配任何不在指定范圍內的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范圍內的任意字符。 |
\b |
匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B |
匹配非單詞邊界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\cx |
匹配由 x 指明的控制字符。例如, \cM 匹配一個 Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個原義的 'c' 字符。 |
\d |
匹配一個數字字符。等價于 [0-9]。 |
\D |
匹配一個非數字字符。等價于 [^0-9]。 |
\f |
匹配一個換頁符。等價于 \x0c 和 \cL。 |
\n |
匹配一個換行符。等價于 \x0a 和 \cJ。 |
\r |
匹配一個回車符。等價于 \x0d 和 \cM。 |
\s |
匹配任何空白字符,包括空格、制表符、換頁符等等。等價于 [ \f\n\r\t\v]。 |
\S |
匹配任何非空白字符。等價于 [^ \f\n\r\t\v]。 |
\t |
匹配一個制表符。等價于 \x09 和 \cI。 |
\v |
匹配一個垂直制表符。等價于 \x0b 和 \cK。 |
\w |
匹配包括下劃線的任何單詞字符。等價于'[A-Za-z0-9_]'。 |
\W |
匹配任何非單詞字符。等價于 '[^A-Za-z0-9_]'。 |
\xn |
匹配 n,其中 n 為十六進制轉義值。十六進制轉義值必須為確定的兩個數字長。例如,'\x41' 匹配 "A"。'\x041' 則等價于 '\x04' & "1"。正則表達式中可以使用 ASCII 編碼。. |
\num |
匹配 num,其中 num 是一個正整數。對所獲取的匹配的引用。例如,'(.)\1' 匹配兩個連續的相同字符。 |
\n |
標識一個八進制轉義值或一個向后引用。如果 \n 之前至少 n 個獲取的子表達式,則 n 為向后引用。否則,如果 n 為八進制數字 (0-7),則 n 為一個八進制轉義值。 |
\nm |
標識一個八進制轉義值或一個向后引用。如果 \nm 之前至少有 nm 個獲得子表達式,則 nm 為向后引用。如果 \nm 之前至少有 n 個獲取,則 n 為一個后跟文字 m 的向后引用。如果前面的條件都不滿足,若 n 和 m 均為八進制數字 (0-7),則 \nm 將匹配八進制轉義值 nm。 |
\nml |
如果 n 為八進制數字 (0-3),且 m 和 l 均為八進制數字 (0-7),則匹配八進制轉義值 nml。 |
\un |
匹配 n,其中 n 是一個用四個十六進制數字表示的 Unicode 字符。例如, \u00A9 匹配版權符號 (©)。 |
下面看幾個例子:
"^The":表示所有以"The"開始的字符串("There","The cat"等);
"of despair$":表示所以以"of despair"結尾的字符串;
"^abc$":表示開始和結尾都是"abc"的字符串——呵呵,只有"abc"自己了;
"notice":表示任何包含"notice"的字符串。
'*','+'和'?'這三個符號,表示一個或一序列字符重復出現的次數。它們分別表示“沒有或
更多”,“一次或更多”還有“沒有或一次”。下面是幾個例子:
"ab*":表示一個字符串有一個a后面跟著零個或若干個b。("a", "ab", "abbb",……);
"ab+":表示一個字符串有一個a后面跟著至少一個b或者更多;
"ab?":表示一個字符串有一個a后面跟著零個或者一個b;
"a?b+$":表示在字符串的末尾有零個或一個a跟著一個或幾個b。
也可以使用范圍,用大括號括起,用以表示重復次數的范圍。
"ab{2}":表示一個字符串有一個a跟著2個b("abb");
"ab{2,}":表示一個字符串有一個a跟著至少2個b;
"ab{3,5}":表示一個字符串有一個a跟著3到5個b。
請注意,你必須指定范圍的下限(如:"{0,2}"而不是"{,2}")。還有,你可能注意到了,'*','+'和
'?'相當于"{0,}","{1,}"和"{0,1}"。
還有一個'¦',表示“或”操作:
"hi¦hello":表示一個字符串里有"hi"或者"hello";
"(b¦cd)ef":表示"bef"或"cdef";
"(a¦b)*c":表示一串"a""b"混合的字符串后面跟一個"c";
'.'可以替代任何字符:
"a.[0-9]":表示一個字符串有一個"a"后面跟著一個任意字符和一個數字;
"^.{3}$":表示有任意三個字符的字符串(長度為3個字符);
方括號表示某些字符允許在一個字符串中的某一特定位置出現:
"[ab]":表示一個字符串有一個"a"或"b"(相當于"a¦b");
"[a-d]":表示一個字符串包含小寫的'a'到'd'中的一個(相當于"a¦b¦c¦d"或者"[abcd]");
"^[a-zA-Z]":表示一個以字母開頭的字符串;
"[0-9]%":表示一個百分號前有一位的數字;
",[a-zA-Z0-9]$":表示一個字符串以一個逗號后面跟著一個字母或數字結束。
你也可以在方括號里用'^'表示不希望出現的字符,'^'應在方括號里的第一位。(如:"%[^a-zA-Z]%"表
示兩個百分號中不應該出現字母)。
為了逐字表達,必須在"^.$()¦*+?{\"這些字符前加上轉移字符'\'。
請注意在方括號中,不需要轉義字符。
|
1.首先介紹三個String對象比較的方法:
(1)equals:比較兩個String對象的值是否相等。例如:
String str1 = "hello quanjizhu";
String str2 =str1+"haha";
String str3 = new String("hello quanjizhu");
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
輸出結果都為true。
(2)= =:比較兩個String對象的指向的內存地址是否相等。例如:
String str1 = "hello quanjizhu";
String str2 =str1+"haha";
String str3 = new String("hello quanjizhu");
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
輸出結果都為false。
3.原理
要理解 java中String的運作方式,必須明確一點:String是一個非可變類(immutable)。什么是非可變類呢?簡單說來,非可變類的實例是不能被修改的,每個實例中包含的信息都必須在該實例創建的時候就提供出來,并且在對象的整個生存周期內固定不變。java為什么要把String設計為非可變類呢?你可以問問 james Gosling :)。但是非可變類確實有著自身的優勢,如狀態單一,對象簡單,便于維護。其次,該類對象對象本質上是線程安全的,不要求同步。此外用戶可以共享非可變對象,甚至可以共享它們的內部信息。(詳見 《Effective java》item 13)。String類在java中被大量運用,甚至在class文件中都有其身影,因此將其設計為簡單輕便的非可變類是比較合適的。
(1)創建。
好了,知道String是非可變類以后,我們可以進一步了解String的構造方式了。創建一個Stirng對象,主要就有以下兩種方式:
java 代碼
String str1 = new String("abc");
Stirng str2 = "abc";
雖然兩個語句都是返回一個String對象的引用,但是jvm對兩者的處理方式是不一樣的。對于第一種,jvm會馬上在heap中創建一個String對象,然后將該對象的引用返回給用戶。對于第二種,jvm首先會在內部維護的strings pool中通過String的 equels 方法查找是對象池中是否存放有該String對象,如果有,則返回已有的String對象給用戶,而不會在heap中重新創建一個新的String對象;如果對象池中沒有該String對象,jvm則在heap中創建新的String對象,將其引用返回給用戶,同時將該引用添加至strings pool中。注意:使用第一種方法創建對象時,jvm是不會主動把該對象放到strings pool里面的,除非程序調用 String的intern方法。看下面的例子:
java 代碼
String str1 = new String("abc"); //jvm 在堆上創建一個String對象
//jvm 在strings pool中找不到值為“abc”的字符串,因此
//在堆上創建一個String對象,并將該對象的引用加入至strings pool中
//此時堆上有兩個String對象
Stirng str2 = "abc";
if(str1 == str2){
System.out.println("str1 == str2");
}else{
System.out.println("str1 != str2");
}
//打印結果是 str1 != str2,因為它們是堆上兩個不同的對象
String str3 = "abc";
//此時,jvm發現strings pool中已有“abc”對象了,因為“abc”equels “abc”
//因此直接返回str2指向的對象給str3,也就是說str2和str3是指向同一個對象的引用
if(str2 == str3){
System.out.println("str2 == str3");
}else{
System.out.println("str2 != str3");
}
//打印結果為 str2 == str3
再看下面的例子:
java 代碼
String str1 = new String("abc"); //jvm 在堆上創建一個String對象
str1 = str1.intern();
//程序顯式將str1放到strings pool中,intern運行過程是這樣的:首先查看strings pool
//有沒“abc”對象的引用,沒有,則在堆中新建一個對象,然后將新對象的引用加入至
//strings pool中。執行完該語句后,str1原來指向的String對象已經成為垃圾對象了,隨時會
//被GC收集。
//此時,jvm發現strings pool中已有“abc”對象了,因為“abc”equels “abc”
//因此直接返回str1指向的對象給str2,也就是說str2和str1引用著同一個對象,
//此時,堆上的有效對象只有一個。
Stirng str2 = "abc";
if(str1 == str2){
System.out.println("str1 == str2");
}else{
System.out.println("str1 != str2");
}
//打印結果是 str1 == str2
為什么jvm可以這樣處理String對象呢?就是因為String的非可變性。既然所引用的對象一旦創建就永不更改,那么多個引用共用一個對象時互不影響。
(2)串接(Concatenation)。
java程序員應該都知道濫用String的串接操作符是會影響程序的性能的。性能問題從何而來呢?歸根結底就是String類的非可變性。既然String對象都是非可變的,也就是對象一旦創建了就不能夠改變其內在狀態了,但是串接操作明顯是要增長字符串的,也就是要改變String的內部狀態,兩者出現了矛盾。怎么辦呢?要維護String的非可變性,只好在串接完成后新建一個String 對象來表示新產生的字符串了。也就是說,每一次執行串接操作都會導致新對象的產生,如果串接操作執行很頻繁,就會導致大量對象的創建,性能問題也就隨之而來了。
為了解決這個問題,jdk為String類提供了一個可變的配套類,StringBuffer。使用StringBuffer對象,由于該類是可變的,串接時僅僅時改變了內部數據結構,而不會創建新的對象,因此性能上有很大的提高。針對單線程,jdk 5.0還提供了StringBuilder類,在單線程環境下,由于不用考慮同步問題,使用該類使性能得到進一步的提高。
(3)String的長度
我們可以使用串接操作符得到一個長度更長的字符串,那么,String對象最多能容納多少字符呢?查看String的源代碼我們可以得知類String中是使用域 count 來記錄對象字符的數量,而count 的類型為 int,因此,我們可以推測最長的長度為 2^32,也就是4G。
不過,我們在編寫源代碼的時候,如果使用 Sting str = "aaaa";的形式定義一個字符串,那么雙引號里面的ASCII字符最多只能有 65534 個。為什么呢?因為在class文件的規范中, CONSTANT_Utf8_info表中使用一個16位的無符號整數來記錄字符串的長度的,最多能表示 65536個字節,而java class 文件是使用一種變體UTF-8格式來存放字符的,null值使用兩個字節來表示,因此只剩下 65536- 2 = 65534個字節。也正是變體UTF-8的原因,如果字符串中含有中文等非ASCII字符,那么雙引號中字符的數量會更少(一個中文字符占用三個字節)。如果超出這個數量,在編譯的時候編譯器會報錯。
(3)compareTo:比較兩個String對象的值是否相等。例如:
String str1 = "hello quanjizhu";
String str2 =str1+"haha";
String str3 = new String("hello quanjizhu");
System.out.println(str1.compareTo(str2));
System.out.println(str1.compareTo(str3));
輸出結果都為0。(若輸出結果大于0表示str1大于str2)
2.String類的幾種初始化方法的區別
(1) String str1 = "hello quanjizhu";
首先到String pool中查找有沒有值為hello quanjizhu的對象,若有則讓str1直接指向此內存地址;若沒有則在內存堆中重新開辟空間給str1,并把hello quanjizhu加到String pool中。
(2)String str3 = new String("hello quanjizhu");
每次初始化都會重新在內存堆中開辟空間給新的對象,而不會到String pool中查找,更不會添加到String pool中。除非顯示的調用intern方法。
str3.interl();這時就會把hello quanjizhu加到String pool中。
(3)
String str1 = "hello quanjizhu";
String str2 ="hello" +"quanjizhu";
String str3 ="hello "+"quanjizhu";在編譯的時候會優化成String str3 = "hello quanjizhu";所有str1和str2指向的是同一內存地址。
(4)
String var = “quanjizhu“;
String str4 = “hello “+var;
System.out.println(str1= =str4)的結果是什么呢?輸出結果是false,證明了String str4 = “hello “+var;
在內存堆中會重新分配空間,而不是讓str4指向var的地址。換用一種定義方法:str4 = (“hello “+var4).intern();intern()方法告訴編譯器將此結果放到String pool里,因此,System.out.println(str1= =str4)輸出結構將是true;
java繼承中對構造函數是不繼承的,只是調用(隱式或顯式)。
以下是例子:
public class FatherClass {
public FatherClass() {
System.out.println(100);
}
public FatherClass(int age) {
System.out.println(age);
}
}
public class SonClass extends FatherClass{
public SonClass() {
}
public SonClass(int c) {
System.out.println(1234);
}
public static void main(String[] args) {
SonClass s = new SonClass(66);
}
}
編譯后執行結果如下是什么呢?
分析:SonClass s = new SonClass(66);執行這句時,調用
public SonClass(int c) {
System.out.println(1234);//系統會自動先調用父類的無參構造函數(super())
}
在這個構造函數中,等價于
public SonClass(int c) {
super();//必須是第1行,否則不能編譯
System.out.println(1234);
}
所以結果是 100
1234
3.如果子類構造函數是這樣寫的
public SonClass(int c) {
super(22);//必須是第1行,否則不能編譯
//顯式調用了super后,系統就不再調用無參的super()了;
System.out.println(1234);
}
執行結果是 22
1234
總結1:構造函數不能繼承,只是調用而已。
如果父類沒有無參構造函數
創建子類時,不能編譯,除非在構造函數代碼體中第一行,必須是第一行顯式調用父類有參構造函數
如下:
SonClass (){
super(777);//顯示調用父類有參構造函數
System.out.println(66);
}
如果不顯示調用父類有參構造函數,系統會默認調用父類無參構造函數super();
但是父類中沒有無參構造函數,那它不是不能調用了。所以編譯就無法通過了。
總結2:創建有參構造函數后,系統就不再有默認無參構造函數。
如果沒有任何構造函數,系統會默認有一個無參構造函數。
java里面任何class都要裝載在虛擬機上才能運行。Class.forName(xxx.xx.xx)就是裝載類用的(和new 不一樣,要分清楚),裝載后jvm將執行類中的靜態代碼。
至于什么時候用,可以考慮一下這個問題,給你一個字符串變量,它代表一個類的包名和類名,你怎么實例化它?只有用提到的這個方法了,不過要再加一點。
A a = (A)Class.forName("pacage.A").newInstance();
這和
A a = new A();
是一樣的效果。
有的jdbc連接數據庫的寫法里是Class.forName(xxx.xx.xx);而有一些是Class.forName(xxx.xx.xx).newInstance(),為什么會有這兩種寫法呢?
Class.forName(xxx.xx.xx) 返回的是一個類Class。
Class.forName(xxx.xx.xx).newInstance() 是創建一個對象,返回的是Object。
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加載指定的類,也就是說JVM會執行該類的靜態代碼段。
在JDBC規范中明確要求這個Driver類必須向DriverManager注冊自己,即任何一個JDBC Driver的Driver類的代碼都必須類似如下:
public class MyJDBCDriver implements Driver {
static {
DriverManager.registerDriver(new MyJDBCDriver());
}
}
所以我們在使用JDBC時只需要Class.forName(XXX.XXX);就可以了
we just want to load the driver to jvm only, but not need to user the instance of driver, so call Class.forName(xxx.xx.xx) is enough, if you call Class.forName(xxx.xx.xx).newInstance(), the result will same as calling Class.forName(xxx.xx.xx), because Class.forName(xxx.xx.xx).newInstance() will load driver first, and then create instance, but the instacne you will never use in usual, so you need not to create it.
在JDBC驅動中,有一塊靜態代碼,也叫靜態初始化塊,它執行的時間是當class調入到內存中就執行(你可以想像成,當類調用到內存后就執行一個方法)。所以很多人把jdbc driver調入到內存中,再實例化對象是沒有意義的。
toString()是在Object類中定義的方法。所有類都是繼承Object類,所以“所有對象都有這個方法” 。
它通常只是為了方便輸出,比如System.out.println(xx),括號里面的“xx”如果不是String類型的話,就自動調用xx的toString()方法。總而言之,它只是sun公司開發java的時候為了方便所有類的字符串操作而特意加入的一個方法。
例子1:
public class A{
public String toString(){return "this is A";}
}
如果某個方法里面有如下句子:
A obj=new A();
System.out.println(obj);
會得到輸出:this is A
例子2:
public class A{
public String getString(){return "this is A";}//toString改個名字試試看
}
A obj=new A();
System.out.println(obj);
會得到輸出:xxxx@xxxxxxx的類名加地址形式
System.out.println(obj.getString());
會得到輸出:this is A
看出區別了嗎,toString的好處是在碰到“println”之類的輸出方法時會自動調用,不用顯式打出來。
所謂反射,可以理解為在運行時期獲取對象類型信息的操作。傳統的編程方法要求程序員在編譯階段決定使用的類型,但是在反射的幫助下,編程人員可以動態獲取這些信息,從而編寫更加具有可移植性的代碼。嚴格地說,反射并非編程語言的特性,因為在任何一種語言都可以實現反射機制,但是如果編程語言本身支持反射,那么反射的實現就會方便很多。
1,獲得類型類
我們知道在Java中一切都是對象,我們一般所使用的對象都直接或間接繼承自Object類。Object類中包含一個方法名叫getClass,利用這個方法就可以獲得一個實例的類型類。類型類指的是代表一個類型的類,因為一切皆是對象,類型也不例外,在Java使用類型類來表示一個類型。所有的類型類都是Class類的實例。例如,有如下一段代碼:
A a = new A();
if(a.getClass()==A.class)
System.out.println("equal");
else System.out.println("unequal");
可以看到,對象a是A的一個實例,A某一個類,在if語句中使用a.getClass()返回的結果正是A的類型類,在Java中表示一個特定類型的類型類可以用“類型.class”的方式獲得,因為a.getClass()獲得是A的類型類,也就是A.class,因此上面的代碼執行的結果就是打印出“equal”。特別注意的是,類型類是一一對應的,父類的類型類和子類的類型類是不同的,因此,假設A是B的子類,那么如下的代碼將得到“unequal”的輸出:
A a = new A();
if(a.getClass()==B.class)
System.out.println("equal");
else System.out.println("unequal");
因此,如果你知道一個實例,那么你可以通過實例的“getClass()”方法獲得該對象的類型類,如果你知道一個類型,那么你可以使用“.class”的方法獲得該類型的類型類。
2,獲得類型的信息
在獲得類型類之后,你就可以調用其中的一些方法獲得類型的信息了,主要的方法有:
getName():String:獲得該類型的全稱名稱。
getSuperClass():Class:獲得該類型的直接父類,如果該類型沒有直接父類,那么返回null。
getInterfaces():Class[]:獲得該類型實現的所有接口。
isArray():boolean:判斷該類型是否是數組。
isEnum():boolean:判斷該類型是否是枚舉類型。
isInterface():boolean:判斷該類型是否是接口。
isPrimitive():boolean:判斷該類型是否是基本類型,即是否是int,boolean,double等等。
isAssignableFrom(Class cls):boolean:判斷這個類型是否是類型cls的父(祖先)類或父(祖先)接口。
getComponentType():Class:如果該類型是一個數組,那么返回該數組的組件類型。
此外還可以進行類型轉換這類的操作,主要方法有:
asSubclass(Class clazz):Class:將這個類型轉換至clazz,如果可以轉換,那么總是返回clazz這個引用,否則拋出異常。
cast(Object obj):Object:將obj強制轉換為這個類型類代表的類型,不能轉換的話將拋出異常。
除了這些以外,利用類型類還可以反射該類型中的所有屬性和方法。在Java中所有的屬性信息都用Field表示,所有的方法信息都用Method表示,這輛各類都是java.lang.reflect包中的類。在Class中提供了4個相關的方法獲得類型的屬性:
getField(String name):Field
getFields():Field[]
getDeclaredField(String name):Field
getDeclaredFields():Field[]
其中getField用于返回一個指定名稱的屬性,但是這個屬性必須是公有的,這個屬性可以在父類中定義。如果是私有屬性或者是保護屬性,那么都會拋出異常提示找不到這個屬性。getFields則是返回類型中的所有公有屬性,所有的私有屬性和保護屬性都找不到。getDeclaredField獲得在這個類型的聲明中定義的指定名稱的屬性,這個屬性必須是在這個類型的聲明中定義,但可以使私有和保護的。getDeclaredFields獲得在這個類型的聲明中定義的所有屬性,包括私有和保護的屬性都會被返回,但是所有父類的屬性都不會被返回。舉個例子,先考慮下面兩個類的聲明:
class A extends B {
public int a1;
private int a2;
}
class B {
public int b1;
private int b2;
}
如果利用A的類型類調用getFields,那么會返回a1和b1兩個屬性,如果調用getField("a2")則會報錯;如果調用getDeclaredFields則會返回a1和a2,如果調用getDeclaredField("b1")則會報錯。
對于方法也有類似的函數即:
getMethods():Method[]
getMethod(String name, Class ... parameterTypes):Method
getDeclaredMethods():Method[]
getDeclaredMethod(Strubg name, Class ...parameterTypes):Method
不定長參數...是JDK5.0以后新加入的語法。這幾個方法的用法和上面的類似,只是在獲得特定方法時,除了要告知方法的名字,還需要告知方法的參數,如果沒有參數,那么可以傳遞null,或者空數組,但是最好的方法就是什么都不寫,編譯器會自行解決不定長參數問題。
如果要獲得所有的屬性(方法),包括公有和私有的,那么就必須利用getDeclareFields(getDeclareMethods)方法,然后再利用getSuperClass的方法獲得父類,然后遞歸下去。
3,屬性和方法
所有的屬性都使用Field表示,所有的方法都使用Method表示。利用Field和Method可以獲得屬性和方法的信息,甚至執行是獲取、修改屬性值和調用方法。
對于屬性,主要有以下方法可以使用:
getType():Class:獲得該屬性的類型。
getName():String:獲得屬性名稱。
isAccessible():boolean:判斷該屬性是否是可以訪問的,通常私有和保護的類型都是不可以訪問的。
get(Object obj):Object:獲得實例obj的屬性值,如果該實例的類型中不包含這個屬性,那么就會報錯。
set(Object obj, Object value):設置該實例的屬性值
setAccessible(boolean flag):設置該屬性是否可以訪問,如果你調用get和set方法,那么有可能會引發訪問權限的錯誤,這個時候你可以調用setAccessible方法使得該屬性可以訪問。例如下面的代碼:
A a = new A();
Field f = A.class.getDeclaredField("a2");
f.setAccessibe(true);
System.out.println(f.get(a));
f.set(a,12);
System.out.println(f.get(a));
如果移出中間的f.setAccessibe(true);那么代碼會報錯,反之輸出0 12。
對于屬性而言,如果該屬性的類型是基本類型,那么還可以使用一些便捷的set和get操作,例如getInt,setInt什么的,你可以根據自己的需要調用相應的方法。
對于方法,可以有以下的方法:
getName():String:獲得方法的名字。
getReturnType():Class:獲得方法的返回值類型。
getParameterTypes():Class[]:獲得方法的參數類型。
isAccessible():boolean:判斷該方法是否是可以訪問的。
setAccessible(boolean flag):設置該方法是否可以訪問。
invoke(Object obj, Object... args):Object:調用實例obj的相應方法,其參數由args給定,如果沒有參數那么可以什么都不寫。
getExceptionTypes():Class[]:獲得該方法可能拋出的異常類類型。
這幾個方法的含義和用法都和Field的類似,這里不再贅述。
4,創建實例
利用Class對象可以創建一個類型的實例。如果一個類型擁有無參數的構造函數,那么可以簡單地調用Class.newInstance()方法創建一個實例。如果該類型沒有無參數的構造函數,或者你希望是用某個有參數的構造函數,那么可以首先使用getConstructors()、getConstructor(Class[] parameterTypes)和getDeclaredConstructors()、getDeclaredConstructor(Class[] parameterTypes)獲得構造函數,這兩個方法的返回值都使Constructor類型。特別注意的是,構造函數不能繼承,因此你調用getConstructor也只能返回這個類型中定義的所有公有構造函數。
Constructor的使用方法和Method的類似,它也存在getParameterTypes()方法和getExceptionTypes()方法,不同的是,它使用newInstance(Object... args)來調用一個構造函數,注意newInstance不需要實例對象,因為這個時候你還沒創建出來這個實例呢