2010年10月30日
最近在網上看到的相關材料都比較陳舊,也太簡略,參看了一下其他人的內容,針對Hive2.1.1做點分享:
1)下載apache-hive-2.1.1-bin.tar.gz
2)解壓縮,下面的命令行如啟動報錯,請自行查略Hive啟動配置
3)啟動
hiveserver2 (非必須,使用jdbc訪問的時候才使用)
bin目錄下
hive --service hiveserver2 -p10001來啟動hiveserver2 服務(默認為10000端口)
nohup hive --service hiverserver2 -p10001可以在后臺跑
4)
hive腳本運行流程bin目錄下,使用命令方法為:
./hive <parameters> --service serviceName <service parameters>
舉例:hive --debug :
查看bin/hive文件
流程中會判斷$1=‘--debug’則$DEBUG=‘--debug’
if [ "$DEBUG" ]; then
if [ "$HELP" ]; then //如還有--help,就會執行debug_help方法。
debug_help
exit 0
else
get_debug_params "$DEBUG"
export HADOOP_CLIENT_OPTS="$HADOOP_CLIENT_OPTS $HIVE_MAIN_CLIENT_DEBUG_OPTS"http://設置HIVE_MAIN_CLIENT_DEBUG_OPTS的參數中加入debug相應參數
fi
fi
if [ "$SERVICE" = "" ] ; then
if [ "$HELP" = "_help" ] ; then
SERVICE="help"
else
SERVICE="cli" //默認賦值cli
fi
fi
這個shell腳本很多變量應該是在其他sh文件中定義,其中$SERVICE_LIST就是其他很多sh文件的最開始形成的:export SERVICE_LIST="${SERVICE_LIST}${THISSERVICE} "
hive腳本最后的$TORUN "$@" ,默認情況下TORUN其實就是cli,即執行/ext/cli.sh腳本,該腳本中主要是調用/ext/util/execHiveCmd.sh 來執行最后的CliDriver。
【
shell腳本中的$*,$@和$#舉例說:
腳本名稱叫test.sh 入參三個: 1 2 3
運行test.sh 1 2 3后
$*為"1 2 3"(一起被引號包住)
$@為"1" "2" "3"(分別被包住)
$#為3(參數數量)
】即exec $HADOOP jar ${HIVE_LIB}/$JAR $CLASS $HIVE_OPTS "$@" //1
其中:
$HADOOP=$HADOOP_HOME/bin/hadoop 【hive腳本中定義HADOOP=$HADOOP_HOME/bin/hadoop】
$CLASS=org.apache.hadoop.hive.cli.CliDriver【傳入的第一個參數,在cli.sh中有定義】
hadoop腳本(2.7.3為例)中最終會執行:
# Always respect HADOOP_OPTS and HADOOP_CLIENT_OPTS
HADOOP_OPTS="$HADOOP_OPTS $HADOOP_CLIENT_OPTS"
#make sure security appender is turned off
HADOOP_OPTS="$HADOOP_OPTS -Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-INFO,NullAppender}"
export CLASSPATH=$CLASSPATH
exec "$JAVA" $JAVA_HEAP_MAX $HADOOP_OPTS $CLASS "$@" //2
hive的debug參數就是在啟動hive腳本時放到HADOOP_OPTS中的
1和2處結合可得到最終的運行命令,查看一下運行結果:ps -ef|grep CliDriver
/usr/java/jdk1.8.0_101/bin/java -Xmx256m -Djava.net.preferIPv4Stack=true -Dhadoop.log.dir=.. -Dhadoop.log.file=hadoop.log -Dhadoop.home.dir=.. -Dhadoop.id.str=root -Dhadoop.root.logger=INFO,console -Djava.library.path=.. -Dhadoop.policy.file=hadoop-policy.xml -Djava.net.preferIPv4Stack=true -Xmx512m -Dproc_hivecli -XX:+UseParallelGC -agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y -Dlog4j.configurationFile=hive-log4j2.properties -Djava.util.logging.config.file=..
-Dhadoop.security.logger=INFO,NullAppender org.apache.hadoop.util.RunJar /yuxh/app/apache-hive-2.*/lib/hive-cli-2.*.jar org.apache.hadoop.hive.cli.CliDriver
appfuse3.5使用Hibernate4.3.6, 而Hibernate日志框架使用了jboss-logging,想在后臺打出sql的參數一直無法生效。
檢查配置文件,框架里面的兩個配置文件,src/test/resources/log4j2.xml(單元測試時配置),src/main/resources/log4j2.xml(運行時配置)
搞清log4j2的配置后,各種修改(主要是
<Logger name="org.hibernate.SQL" level="trace"/>
<Logger name="org.hibernate.type" level="trace"/>)
用junit測試任然無法打印出真實參數。根據這些實踐,確定log4j2是使用無誤生效的,只是org.hibernate這部分的logger一直未起效
參考國內外網站,一直無人回答hibernate4的這個問題,有人指出這部分Hibernate官方文檔只是提了一句,一直未更新相關內容。最后有人提到應該是 jboss-logging 的LoggerProviders這個類的問題,看實現對log4j2已經做支持。最后發現 jboss-logging使用的是3.2.0.beta,對比相關類的源代碼,更改為3.2.0.Final,生效!
P.S 把這個問題提交給Appfuse官網,issue APF-1478,作者標志為4.0版本修復。
新電腦裝上eclipse4.4.2,導入maven項目之后,依賴庫總是有很多錯誤。最后搜索到可能是eclipse的bug(據說是
JAVA_HOME沒有正確傳遞
),查看到eclipse默認的是安裝的jre目錄,修改到jdk目錄下,依賴問題解決。
不過目前版本仍然沒有解決pom文件的“Plugin execution not covered by lifecycle configuration”錯誤,暫時忽略不管吧。
本打算繼承一個API中的Parent類(Parent繼承自GrandParent類),重寫其中的service方法,copy了Parent的service方法。不過發現Parent的service中也有super.service方法。當時考慮直接調用GrandParent的service方法。。。未遂(包括反射也不行)。正好看到老外寫的一篇文章,翻譯:
在Son類里面寫一個test方法:
public void test() {
super.test();
this.test();
}
反編譯之后:
public void test()
{
// 0 0:aload_0
// 1 1:invokespecial #2 <Method void Parent.test()>
// 2 4:aload_0
// 3 5:invokevirtual #3 <Method void test()>
// 4 8:return
}
使用ASM可以完成對GrandParent方法的調用
public class GrandParent {
public void test() {
System.out.println("test of GrandParent");
}
}
public class Parent extends GrandParent{
public void test() {
System.out.println("test of Parent");
}
}
public class Son extends Parent{
public void test() {
System.out.println("test of Son");
}
}
調用Son實例的test方法只會執行Son的test方法。而ASM可以修改class,先寫一個Example類繼承Son,重寫test方法
1 import java.io.FileOutputStream;
2
3 import org.objectweb.asm.ClassWriter;
4 import org.objectweb.asm.MethodVisitor;
5 import org.objectweb.asm.Opcodes;
6
7 public class ASMByteCodeManipulation extends ClassLoader implements Opcodes {
8
9 public static void main(String args[]) throws Exception {
10 ClassWriter cw = new ClassWriter(0);
11 cw.visit(V1_1, ACC_PUBLIC, "Example", null, "Son", null);
12
13 // creates a MethodWriter for the (implicit) constructor
14 MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,null);
15 mw.visitVarInsn(ALOAD, 0);
16 mw.visitMethodInsn(INVOKESPECIAL, "Son", "<init>", "()V");
17 mw.visitInsn(RETURN);
18 mw.visitMaxs(1, 1);
19 mw.visitEnd();
20
21 // creates a MethodWriter for the 'test' method
22 mw = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
23 mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out","Ljava/io/PrintStream;");
24 mw.visitLdcInsn("test of AI3");
25 mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
26 "(Ljava/lang/String;)V");
27 //Call test() of GrandParent
28 mw.visitVarInsn(ALOAD, 0);
29 mw.visitMethodInsn(INVOKESPECIAL, "GrandParent", "test", "()V");
30 //Call test() of GrandParent
31 mw.visitVarInsn(ALOAD, 0);
32 mw.visitMethodInsn(INVOKESPECIAL, "Parent", "test", "()V");
33 //Call test() of GrandParent
34 mw.visitVarInsn(ALOAD, 0);
35 mw.visitMethodInsn(INVOKESPECIAL, "Son", "test", "()V");
36 mw.visitInsn(RETURN);
37 mw.visitMaxs(2, 1);
38 mw.visitEnd();
39
40 byte[] code = cw.toByteArray();
41 FileOutputStream fos = new FileOutputStream("Example.class");
42 fos.write(code);
43 fos.close();
44
45 ASMByteCodeManipulation loader = new ASMByteCodeManipulation();
46 Class<?> exampleClass = loader.defineClass("Example", code, 0,
47 code.length);
48 Object obj = exampleClass.newInstance();
49 exampleClass.getMethod("test", null).invoke(obj, null);
50
51 }
52 }
輸出:
test of AI3
test of GrandParent
test of Parent
test of Son
看看怎樣實現的,11行定義一個新的類Example繼承Son。22行,Example重寫test方法,先打印“test of AI3”,再分別在29、32、35行調用
GrandParent、Parent、Son的test方法。 main方法中,45行創建Example的實例,再用反射調他的test方法。
使用invokespecial這種方式也有局限,只能從子類調用。否則報錯:
Exception in thread "main" java.lang.VerifyError: (class: Example, method: test1 signature: (LAI2;)V) Illegal use of nonvirtual function call
使用Google calendar v3 API的時候,大量發現Builder使用。比如Credential類,查了查Builder模式的講解,始終感覺代碼的實現和標準定義不太相同。最后發現這種實現方式是《Effective java 2nd》中的一種實現(Item 2: Consider a builder when faced with many constructor parameters)。靜態工廠和構造器都有一個通病:對于存在大量可選構造參數的對象,擴展性不好。經典的解決方案是提供多個構造函數,第一個構造函數只有必須的參數,第二個構造函數除了必須參數還有一個可選參數,第三個除了必須參數還有兩個可選參數。。。這樣下去知道最后一個可選參數出現(
telescoping constructor)。這種方案的問題是,當構建對象的時候很容易把其中兩個參數的位置放反。。。。
(難發現的bug)。
另一種解決方案是JavaBean 模式,先調用無參構造函數再調用各個set方法來組裝對象。這種方案的問題是不能強制一致性。如果沒有set某些必須的參數的話,對象可能處于不一致(
inconsistent)的狀態(難發現的bug)。另外一個缺點是JavaBean模式不能讓類immutable,需要程序員額外工作保證線程安全。
第三種方式就是Builder設計模式。這種方式混合了
telescoping constructor模式的安全性和JavaBean模式的可讀性。客戶端調用有所有必填參數的構造器(或靜態工廠),得到一個builder對象。然后調用builder對象的方法去set各個選填參數。最后調用無參的build方法產生一個immutable的對象實例。immutable對象有非常多優點而且可能很有用。builder的set方法都是返回builder本身,所以調用也是可以chained。如:
GoogleCredential credentialNew = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY).setClientSecrets(clientSecrets)
.addRefreshListener(new CredentialStoreRefreshListener(userID, new DBCredentialStore())).build()
.setAccessToken(accessToken).setRefreshToken(refreshToken)
客戶端代碼很好寫,更重要的是易讀。Builder模式模擬了在Ada和Python語言里的命名可選參數(
named optional parameters)。
同時Builder類設置為static也是對Item 22:Favor static member classes over nonstatic的實踐
以典型的客戶端-服務器端授權為例
一 基本流程
使用Google Calendar v3 ,如果以servlet作為代理,可以使用官方示例,自己寫一個類A.java繼承AbstractAuthorizationCodeServlet類,這個類主要用于跳轉到google提供的授權頁面,如果用戶同意授權,則根據A類中的URL(這個必須和注冊的google 回調路徑相同,比如oauth_callback否則報錯)重定向到B類,B.java 繼承AbstractAuthorizationCodeCallbackServlet類,這個訪問路徑類似http://www.example.com/oauth_callback?code=ABC1234。這里我配置oauth_callback為servlet的訪問路徑,B類中的
onSuccess方法將根據獲得的access Token(這是根據傳過來的code獲得的)做業務操作。
二 需要參數的情況
有些業務需要用戶傳參數,
直接傳參數給A,再試圖在B中獲取是不行的!B類中只能獲取某些
固定的參數,如code。要想傳用戶參數,我們可以在A中先獲取,把幾個參數組裝為json格式字符串(還可以繼續base64編碼),把這個字符串作為state的值,再重定向到授權頁面,同意后state參數可以傳到B類,取值解析json字符串(或先base64解碼),得到參數。
由于API中AuthorizationCodeRequestUrl有處理state的方法,而AbstractAuthorizationCodeServlet已經直接封裝,為了使用setState,直接在A類中繼承HttpServlet重寫service方法,復制大部分AbstractAuthorizationCodeServlet的內容,稍作修改:
resp.sendRedirect(flow.newAuthorizationUrl()
.setState(json).setRedirectUri(redirectUri).build());
三 關于refresh token
默認情況下,用戶授權之后token會有一個小時的有效期,之后你可以通過refresh token再重新獲取token。所以,如果不需要用戶再次授權,可以在第一次,保存好token、refresh token、ExpirationTime。實例中用了JDO來實現,自己如果使用數據庫保存,可類似寫一個類實現CredentialStore類。使用的時候,現在數據庫中取出,再創建credential,如:
GoogleCredential credentialNew = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY).setClientSecrets(clientSecrets)
.addRefreshListener(new CredentialStoreRefreshListener(userID, new DBCredentialStore())).build()
.setAccessToken(accessToken).setRefreshToken(refreshToken)
.setExpirationTimeMilliseconds(expirationTimeMilliseconds);
在無效的情況下,Listener會自動去用refresh token請求。
json格式經常需要用到,google提供了一個處理json的項目:GSON,能很方便的處理轉換java對象和JSON表達。他不需要使用annotation,也不需要對象的源代碼就能使用。
以字符串為例介紹:
1 。構造json 字符串
例如要傳送json格式的字符串
String appID = req.getParameter("appID");
String userID = req.getParameter("userID");
Map map = new HashMap();
map.put("appID", appID);
map.put("userID", userID);
Gson gson = new Gson();
String state = gson.toJson(map);
2.解析json字符串
JsonParser jsonparer = new JsonParser();//初始化解析json格式的對象
String state = req.getParameter("state");
String appID = jsonparer.parse(state).getAsJsonObject().get("appID").getAsString();
String userID = jsonparer.parse(state).getAsJsonObject().get("userID").getAsString();
通用協調時(UTC, Universal Time Coordinated),格林尼治平均時(GMT, Greenwich Mean Time) 由于歷史原因,這兩個時間是一樣的。
北京時區是東八區,領先UTC八個小時,在電子郵件信頭的Date域記為+0800。
轉換中,最重要的公式就是:
UTC + 時區差 = 本地時間
public static Calendar convertToGmt(Calendar cal) {
Date date = cal.getTime();
TimeZone tz = cal.getTimeZone();
System.out.println("input calendar has date [" + date + "]");
// Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
long msFromEpochGmt = date.getTime();
// gives you the current offset in ms from GMT at the current date
int offsetFromUTC = tz.getOffset(msFromEpochGmt);
System.out.println("offset is " + offsetFromUTC);
// create a new calendar in GMT timezone, set to this date and add the offset
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
gmtCal.setTime(date);
//根據東西時區,選擇offsetFromUTC為正或負數
gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);
utcCal.setTime(date);
utcCal.add(Calendar.MILLISECOND, offsetFromUTC);
System.out.println("Created GMT cal with date [" + gmtCal.getTime()
+ "==" + utcCal.getTime() + "]");
return gmtCal;
}
Andriod 到3.2版本為止,webview方式下使用<input type="file" />點擊后都沒有反應。實際上頂層是有隱含的接口沒實現的,可以自己重寫這個方法來實現。以phonegap為例:
public class App extends DroidGap {
private ValueCallback<Uri> mUploadMessage;
private final static int FILECHOOSER_RESULTCODE = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.init();
// WebView wv = new WebView(this);
// wv.setWebViewClient(new WebViewClient());
this.appView.setWebChromeClient(new CordovaChromeClient(App.this) {
// For Android 3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
App.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
// The undocumented magic method override
// Eclipse will swear at you if you try to put @Override here
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
App.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), App.FILECHOOSER_RESULTCODE);
}
});
// setContentView(wv);
super.loadUrl("file:///android_asset/www/login.html");
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage)
return;
Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
}
}
如果直接的webview方式,extends WebChromeClient即可。
參考:http://stackoverflow.com/questions/5907369/file-upload-in-webview
weather.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.io.*,java.net.*"%>
<%
StringBuffer sbf = new StringBuffer();
//Access the page
try {
//如果網絡設置了代理
System.setProperty("http.proxyHost", "xxx");
System.setProperty("http.proxyPort", "80");
URL url = new URL("http://www.google.com/ig/api?weather=london");
URLConnection urlConn = url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
sbf.append(inputLine);
in.close();
System.out.println("last="+sbf.toString());
} catch (MalformedURLException e) {
System.out.println("MalformedURLException"+e);
} catch (IOException e) {
System.out.println("IOException"+e);
}
%><%=sbf.toString()%>
前臺js部分:
var childData = function(selector, arg)
{
return selector.find(arg).attr('data');
}
$.ajax({
type : "GET",
data : "where=" ,
url : "weather.jsp",
success : function(data) {
console.debug('data='+data);
forecast = $(data).find('forecast_information');
cCondition = $(data).find('current_conditions');
city = childData(forecast, 'city');
if (city != undefined) {
date = childData(forecast, 'forecast_date');
condition = childData(cCondition, 'condition');
tempC = childData(cCondition, 'temp_c');
humidity = childData(cCondition, 'humidity');
icon = childData(cCondition, 'icon');
$('#city').text(city);
$('#date').text(date);
$('#condition').text(condition);
$('#tempC').html(tempC + '° C');
$('#humidity').text(humidity);
$('#icon').attr({
'src' : 'http://www.google.com' + icon
});
$('#data').stop().show('fast');
} else {
$('#error').stop().show('fast');
}
}
});
1. Code first approach:可能不能完全發揮框架和web services的能量,但能完成目標。減少了學習曲線,不用非常透徹了解web services概念,只要對某個框架有一定了解就能完成任務。
2.Contract first approach:根據服務先寫WSDL文件,寫好之后使用框架的工具把WSDL轉換為依賴框架的代碼。
一 介紹
當客戶端調用你的web service的時候,他會發送一個消息過來(可能是soap 消息),如:
<foo:concatRequest>
<s1>abc</s1>
<s2>123</s2>
</foo:concatRequest>
這時候如果有一個轉換器把這個soap消息轉換成java對象,然后調用你提供的java對象(方法)的話將會是非常方便的。幾個最流行的庫就是充當了這種轉換器功能,比如CXF, Axis2 , Metro (jdk6自帶的有)。
手動創建WSDL文件比較容易出錯,可以使用eclipse進行可視化編輯。
二 生成服務代碼
像CXF這樣的 web service庫可以創建轉換器把進來的SOAP 消息轉換為Java對象,然后作為參數傳給方法。生成這些代碼,只需創建一個main:
1 CXF方式:
public static void main(String[] args) {
WSDLToJava.main(new String[] {
"-server",
"-d", "src/main/java",
"src/main/resources/SimpleService.wsdl" });
System.out.println("Done!");
}
運行后會生成service endpoint interface(SEI),我們再寫一個類(比如SimpleServiceImpl)來實現這個接口,寫入自己的業務。還會生成傳入消息對應的java對象。同時生成一個服務器類:
public class SimpleService_P1_Server {
protected SimpleService_P1_Server() throws Exception {
System.out.println("Starting Server");
Object implementor = new SimpleServiceImpl();
String address = "http://localhost:8080/ss/p1";
Endpoint.publish(address, implementor);
}
public static void main(String args[]) throws Exception {
new SimpleService_P1_Server();
System.out.println("Server ready...");
Thread.sleep(5 * 60 * 1000);
System.out.println("Server exiting");
System.exit(0);
}
}
運行這個類你的web service就可以服務了。
2 Axis2 方式
用類似的寫main方法,或者配置eclipse的axis2插件可生成:在WSDL文件上,右鍵->web service->generate java bean skeleton
界面的上半部分針對服務端,可以根據需要調整生成的級別,下半部分是生成客戶端。具體的級別可參考eclipse的幫助文檔。一路下一步,最后根據命名空間生成包路徑的文件,里面有XXSkeletonInterface.java 文件(如果生成的時候選擇了生成接口的話),還有一個XXSkeleton實現了這個接口,也是我們需要修改這部分代碼完成我們業務的地方。實際上有一個XXMessageReceiverInOut.java的類接收請求的消息,并調用XXSkeletonInterface。使用eclipse的axis2插件的時候,會自動在web-inf文件夾下生成service\xx(你的wsdl服務名),這下面還要一個meta-inf文件夾裝有wsd文件和一個services.xml配置文件。services.xml文件可配置包括XXMessageReceiverInOut類在內的選項。
二 生成客戶端代碼
為了調用這些web service,同樣可以用CXF這些庫來生成在客戶端運行的轉換器(稱為service stub)。當調用stub里的方法的時候,他會把你的數據/對象 轉換為正確的XML格式,然后發送給真正的web service。當他收到響應的時候,又會把XML轉回Java。
1 CXF 方式
和生成服務器端類似,使用方法
WSDLToJava.main(new String[] {
"-client",
"-d", "src/main/java",
"src/main/resources/SimpleService.wsdl" });
運行后會生成客戶端代碼:
public final class SimpleService_P1_Client {
private static final QName SERVICE_NAME = new QName("http://ttdev.com/ss",
"SimpleService");
private SimpleService_P1_Client() {}
public static void main(String args[]) throws Exception {
URL wsdlURL = SimpleService_Service.WSDL_LOCATION;
if (args.length > 0) {
File wsdlFile = new File(args[0]);
try {
if (wsdlFile.exists()) {
wsdlURL = wsdlFile.toURI().toURL();
} else {
wsdlURL = new URL(args[0]);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
SimpleService_Service ss =
new SimpleService_Service(wsdlURL, SERVICE_NAME);
SimpleService port = ss.getP1();
{
System.out.println("Invoking concat...");
com.ttdev.ss.ConcatRequest _concat_parameters = null;
java.lang.String _concat__return = port.concat(_concat_parameters);
System.out.println("concat.result=" + _concat__return);
}
System.exit(0);
}
}
SimpleService_Service是創建的service stub,他模擬了客戶端的服務。我們需要修改這個類中的_concat_parameters部分,加入參數:
com.ttdev.ss.ConcatRequest _concat_parameters = new ConcatRequest();
_concat_parameters.setS1("abc");
_concat_parameters.setS2("123");
現在就可以運行客戶端代碼了。SEI中有一些注解,可以修改,不細說。
2 Axis2 方式
和生成服務端類似,利用eclipse插件直接生成,包路徑類似,有一個XXStub類,這個類里面有包括請求和應答消息在內的內部類。使用的時候,先對請求消息參數類按業務需求賦值,最后調用Stub的請求方法。可以使用Stub的構造函數指定目標endpoint。
有兩種SOAP message風格,document 和RPC,他們定義了SOAP message body的格式。使用document風格時(包括wrapped和unwrapped),在wsdl中有一個非空的types部分,這個部分用XML Schema language定義了web service要用到的類型。wsgen工具從SIB(有SEI就足夠了)中生成與XSD對應的java類。用java代碼生成WSDL文件的時候需要一些java類,wsgen工具可以生成這些Java類,生成的這些java類被稱為wsgen artifacts,底層的JWS類庫會用到這些類,特別是JAX-B系列的包,會用來轉換(marshal)java類實例(that is, Java in-memory objects)為XML類型的XML實例(滿足XML Schema document的XML文檔實例),
The inverse operation is used to convert (unmarshal) an XML document instance to an in-memory
object, an object of a Java type or a comparable type in some other language。因此wsgen工具生成的artifacts,支持了Java為基礎的web service的互操作性。JAX-B類庫提供了Java和XSD類型轉換的底層支持。
For the most part, the wsgen utility can be used without our bothering to inspect the artifacts that it produces. For the most part, JAX-B remains unseen infrastructure.
wsgen artifacts實際上是wsdl message的數據類型,他們和XML Schema type綁定,每個message的XML Schema types從這些java類型得來的。注:在當前的jdk1.6.24中,已經融入wsgen自動生成的過程,不需手動調用。
wsgen工具可用來生成wsdl文件,如:% wsgen -cp "." -wsdl ch01.ts.TimeServerImpl 。這為TimeServer服務生成了wsdl。用wsgen生成的wsdl和通過訪問發布的服務生成的wsdl 有個很大的區別:wsgen生成的沒有endpoint,因為這個URL是在發布服務的時候決定的。其他地方兩個wsdl是相同的。
wsimport(以前叫wsdl2java和 java2wsdl更形象)工具可使用WSDL生成用來幫助寫客戶端的artifacts .
1 先契約再編碼方式
一個例子:得到一個tempConvert.wsdl文件,使用命令 wsimport -keep -p ch02.tc tempConvert.wsdl ,命令根據wsdl的portType生成一個SEI類,把SEI的interface換為class,再把方法改為實現就可變為SIB。把該SIB發布,再使用命令wsimport -keep -p clientTC http://localhost:5599/tc?wsdl,來生成客戶端輔助類
2 編碼優先
服務被發布之后,會自動生成WSDL供客戶端使用。然而,使用annotations可以控制WSDL或WSDL-generated artifacts的生成。
來自
http://stackoverflow.com/questions/1099300/whats-the-difference-between-getpath-getabsolutepath-and-getcanonicalpath
C:\temp\file.txt" - this is a path, an absolute path, a canonical path
.\file.txt This is a path, It's not an absolute path nor canonical path.
C:\temp\myapp\bin\..\\..\file.txt
This is a path, and an absolute path, it's not a canonical path
Canonical path is always an absolute path.
Converting from a path to a canonical path makes it absolute (通常會處理改變當前目錄,所以像. ./file.txt 變為c:/temp/file.txt). The canonical path of a file just "purifies" the path, 去除和解析類似“ ..\” and resolving symlinks(on unixes)
In short:
- getPath() gets the path string that the File object was constructed with, and it may be relative current directory.
- getAbsolutePath() gets the path string after resolving it against the current directory if it's relative, resulting in a fully qualified path.
- getCanonicalPath() gets the path string after resolving any relative path against current directory, and removes any relative pathing (. and ..), and any file system links to return a path which the file system considers the canonical means to reference the file system object to which it points.
Also, each of this has a File equivalent which returns the corresponding File object.
The best way I have found to get a feel for things like this is to try them out:
import java.io.File;
public class PathTesting {
public static void main(String [] args) {
File f = new File("test/.././file.txt");
System.out.println(f.getPath());
System.out.println(f.getAbsolutePath());
try {
System.out.println(f.getCanonicalPath());
}
catch(Exception e) {}
}
}
Your output will be something like:
test\..\.\file.txt
C:\projects\sandbox\trunk\test\..\.\file.txt
C:\projects\sandbox\trunk\file.txt
So, getPath()
gives you the path based on the File object, which may or may not be relative; getAbsolutePath()
gives you an absolute path to the file; and getCanonicalPath()
gives you the unique absolute path to the file. Notice that there are a huge number of absolute paths that point to the same file, but only one canonical path.
When to use each? Depends on what you're trying to accomplish, but if you were trying to see if two
Files
are pointing at the same file on disk, you could compare their canonical paths.
DTDs
Introduced as part of the XML 1.0 specification, DTDs are the oldest constraint model around in the XML world. They're simply to use, but this simplicity comes at a price: DTDs are inflexible, and offer you little for data type validation as well.
XML Schema (XSD)
XML Schema is the W3C's anointed successor to DTDs. XML Schemas are literally orders of magnitude more flexible than DTDs, and offer an almost dizzying array of support for various data types. However, just as DTDs were simple and limited, XML Schemas are flexible, complex, and (some would argue) bloated. It takes a lot of work to write a good schema, even for 50- or 100-line XML documents. For this reason, there's been a lot of dissatisfaction with XML Schema, even though they are widely being used.
[prefix]:[element name]
元素:
root元素必須包含所有文檔中的元素,只能有一個root元素。元素名只能以下劃線或字母開頭,不能有空格,區分大小寫。開元素必須有對應閉元素(也有類似html的簡寫,如<img src="/images/xml.gif" />)。文檔由DTD或schema來限制它是否合格。
屬性:
什么時候用屬性?基本原則:多個值的數據用元素,單值的數據用元素。如果數據有很多值或者比較長,數據最可能屬于元素。他主要被當作文本,容<rss:author>Doug Hally</rss:author> <journal:author>Neil Gaiman</journal:author>易搜索,好用。比如一本書的章節描述。然而如果數據主要作為單值處理的話,最好作為屬性。如果搞不清楚,可以安全的使用元素。
命名空間Namespaces:
xml的命名空間是一種用一個特定的URI來關聯XML文檔里的一個或多個元素的方法。意味著元素是由名字和命名空間一起來識別的。許多復雜的XML文件里,同一個名字會有多個用途。比如,一個RSS feed有一個作者,這個作者同時是每個日記的。雖然這些數據都用author元素來表示,但他們不應該被當作同一個類型的數據。命名空間很好的解決了這個問題,命名空間說明書要求一個前綴和唯一的URI聯合起來區分不同命名空間里的元素。如http://www.neilgaiman.com/entries作為URI,聯合前綴journal用來表示日志相關的元素。
<rdf:RDF xmlns:rss="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:journal="http://www.neilgaiman.com/entries">,然后就可使用了:
<rss:author>Doug Hally</rss:author> <journal:author>Neil Gaiman</journal:author>實際上在使用命名空間前綴的時候再定義也可以的:
<rss:author xmlns:rss="http://www.w3.org/1999/02/22-rdf-syntax-ns#">Doug Hally</rss:author>
如果名字沒有命名空間,不代表在默認命名空間中,而是xml處理器以在任何命名空間以外方式解釋他。要聲明默認命名空間的話就不用后面冒號部分,如<Insured xmlns="使用的一些術語:
-
The name of a namespace (such as http://www.ibm.com/software) is the namespace URI.
-
The element or attribute name can include a prefix and a colon (as in prod:Quantity). A name in that form is called a qualified name, or QName, and the identifier that follows the colon is called a local name. If a prefix is not in use, neither is the colon, and the QName and local name are identical.
-
An XML identifier (such as a local name) that has no colon is sometimes called an NCName. (The NC comes from the phrase no colon.)
Entity references:
用來處理轉義字符,語法是& [entity name] ;XML解析器碰到這種entity reference,就會用對應的值替換掉他。如<(<),>(>),&(&),"("),'(')。注意entity reference是用戶可定義的。比如多處用到版權提示,自己定義后以后更改就方便了:<ora:copyright>&OReillyCopyright;</ora:copyright>。除了用來表示數據中的復雜或特殊字符外,entity reference還會有更多用途。
不解析的數據:
當傳輸大量數據給應用且不用xml解析的時候,CDATA就有用了。當大量的字符需要用entity reference轉義的時候,或空格必須保留的時候,使用CDATA。<![CDATA[….]]>
轉自
http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm,把csv文件按分隔符切割后放在數組中。
// This will parse a delimited string into an array of arrays.
// The default delimiter is the comma, but this
// can be overriden in the second argument.

CSVToArray:function(strData, strDelimiter)
{
// Check to see if the delimiter is defined. If not,
// then default to comma.
strDelimiter = (strDelimiter || ",");
// Create a regular expression to parse the CSV values.
var objPattern = new RegExp(
(
// Delimiters.
"(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
// Quoted fields.
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
// Standard fields.
"([^\"\\" + strDelimiter + "\\r\\n]*))"
),
"gi"
);
// Create an array to hold our data. Give the array
// a default empty first row.
var arrData = [[]];
// Create an array to hold our individual pattern
// matching groups.
var arrMatches = null;
// Keep looping over the regular expression matches
// until we can no longer find a match.
while (arrMatches = objPattern.exec( strData )){
// Get the delimiter that was found.
var strMatchedDelimiter = arrMatches[ 1 ];
// Check to see if the given delimiter has a length
// (is not the start of string) and if it matches
// field delimiter. If id does not, then we know
// that this delimiter is a row delimiter.
if (
strMatchedDelimiter.length &&
(strMatchedDelimiter != strDelimiter)
){
// Since we have reached a new row of data,
// add an empty row to our data array.
arrData.push( [] );
}
// Now that we have our delimiter out of the way,
// let's check to see which kind of value we
// captured (quoted or unquoted).
if (arrMatches[ 2 ]){
// We found a quoted value. When we capture
// this value, unescape any double quotes.
var strMatchedValue = arrMatches[ 2 ].replace(
new RegExp( "\"\"", "g" ),
"\""
);

} else {
// We found a non-quoted value.
var strMatchedValue = arrMatches[ 3 ];
}
// Now that we have our value string, let's add
// it to the data array.
arrData[ arrData.length - 1 ].push( strMatchedValue );
}
// Return the parsed data.
return( arrData );
}
31
while (i != 0)
i >>>= 1; //無符號右移,不管正負左邊都是補0
為了表達式合法,這里的i必須是整型(byte, char, short, int, or long)。謎題的關鍵在于>>>= 是一個復合賦值操作符,不幸的是復合賦值操作符會默默的做narrowing primitive conversions,即從一個數據類型轉換為一個更小的數據類型。Narrowing primitive conversions can lose information about the magnitude or precision of numeric values。為了使問題更具體,假設這樣定義:
short i = -1; 因為初始值i ((short)0xffff) 非零,循環執行。第一步位移會把i提升為int。short, byte, or char類型的操作數都會做這樣的操作。這是widening primitive conversion,沒有信息丟失。這種提升有符號擴展,因此結果是int值0xffffffff。無符號右移一位產生int值0x7fffffff。為了把int值存回short變量,Java執行了可怕的narrowing primitive conversion,即簡單去掉高十六位。這樣又變回了(short)0xffff。如果定義類似short or byte型的負數,都會得到類似結果。你如果定義的是char話,則不會無限循環,因為char值非負,位移之前的寬擴展不會做符號擴展。
總結:不要在short, byte, or char變量上使用復合賦值操作符。這種表達式進行混合類型計算,非常容易混淆。更糟糕的是隱含的窄映射會丟掉信息。
32
while (i <= j && j <= i && i != j) {}
i <= j and j <= i, surely i must equal j?對于實數來說是這樣的。他非常重要,有個名稱:The ≤ relation on the real numbers is said to be antisymmetric。Java's <= operator used to be antisymmetric before release 5.0, but no longer.Java 5之前數據比較符號(<, <=, >, and >=) 需要兩邊的操作數必須為基礎類型(byte, char, short, int, long, float, or double).在Java 5變為兩邊操作數為凡是可轉變為基礎類型的類型。java 5引入autoboxing and auto-unboxing 。The boxed numeric types are Byte, Character, Short, Integer, Long, Float, and Double。具體點,讓上面進入無限循環:
Integer i = new Integer(0);
Integer j = new Integer(0);
(i <= j and j <= i) perform unboxing conversions on i and j and compare the resulting int values numerically。i和j表示0,所以表達式為true。i != j比較的是對象引用,也為true。很奇怪規范沒有把等號改為比較值。原因很簡單:兼容性。當一種語言廣泛應用的時候,不能破壞已存在的規范來改變程序的行為。System.out.println(new Integer(0) == new Integer(0));總是輸出false,所以必須保留。當一個是boxed numeric 類型,另一個是基本類型的時候可以值比較。因為java 5之前這是非法的,具體點:
System.out.println(new Integer(0) == 0); //之前的版本非法,Java 5輸出True
總結:當兩邊的操作數是boxed numeric類型的時候,數字比較符和等于符號是根本不同的:數字比較符是值比較,等號比較的是對象引用。
33
while (i != 0 && i == -i) {}
有負號表示i一定是數字,NaN不行,因為他不等于任何數。事實上,沒有實數可以出現這種情況。但Java的數字類型并沒有完美表達實數。浮點數由一個符號位,一個有效數字(尾數),一個指數構成。浮點數只有0才會和自己的負數相等,所以i肯定是整數。有符號整數用的是二進制補碼計算:取反加一。補碼的一大優勢是用一個唯一的數來表示0。然而有一個對應的缺點:本來可表達偶數個值,現在用一個表達了0,剩下奇數個來表示正負數,意味著正數和負數的數量不一樣。比如int值,他的Integer.MIN_VALUE(-231)。十六進制表達0x8000000。通過補碼計算可知他的負數仍然不變。對他取負數是溢出了的,不過Java在整數計算中忽略了溢出。
總結:Java使用二進制補碼,是不對稱的。有符號整數(int, long, byte, and short) 負數值比整數值多一個。
34
final int START = 2000000000;
int count = 0;
for (float f = START; f < START + 50; f++)
count++;
System.out.println(count);
注意循環變量是float。回憶謎題28 ,明顯f++沒有任何作用。f的初始化值接近Integer.MAX_VALUE,因此需要31位來準確表達,但是float類型只提供了24位精度。增加這樣大的一個float值不會改變值。看起來會死循環?運行程序會發現,輸出0。循環中f和(float)(START + 50)做比較。但int和float比較的時候,自動把int先提示為float。不幸的是三個會引起精度丟失的寬基本類型轉換的其中之一(另外兩個是long到float,long到double)。f的初始值巨大,加50再轉換為float和直接把f轉換為float是一樣的效果,即(float)2000000000 == 2000000050,所以f < START + 50 失敗。只要把float改為int即可修正。
沒有計算器,你怎么知道 2,000,000,050 和float表示2,000,000,000一樣?……
The moral of this puzzle is simple: Do not use floating-point loop indices, because it can lead to unpredictable behavior. If you need a floating-point value in the body of a loop, take the int or long loop index and convert it to a float or double. You may lose precision when converting an int or long to a float or a long to a double, but at least it will not affect the loop itself. When you use floating-point, use double rather than float unless you are certain that float provides enough precision and you have a compelling performance need to use float. The times when it's appropriate to use float rather than double are few and far between。
35
下面程序模擬一個簡單的時鐘
int minutes = 0;
for (int ms = 0; ms < 60*60*1000; ms++)
if (ms % 60*1000 == 0)
minutes++;
System.out.println(minutes);
結果是60000,問題在于布爾表達式ms % 60*1000 == 0,最簡單的修改方法是:if (ms % (60 * 1000) == 0)
更好的方法是用合適命名的常量代替魔力數字:
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int MS_PER_MINUTE = 60 * 1000;
public static void main(String[] args) {
int minutes = 0;
for (int ms = 0; ms < MS_PER_HOUR; ms++)
if (ms % MS_PER_MINUTE == 0)
minutes++;
System.out.println(minutes);
}
絕不要用空格來分組;使用括號來決定優先級
24
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
if (b == 0x90)
System.out.print("Joy!");
}
Ox90 超過了byte的取值范圍-128到127。byte和int比較是一種混合類型比較。考慮個表達式((byte)0x90 == 0x90)得到的是false。byte和int做比較的時候,Java先對byte進行了widening primitive conversion 再比較兩個int值。因為byte是有符號類型,轉變做了符號擴展,把負的byte值轉換為相應的int值。這個例子中(byte)0x90被轉變為-112,當然不等于int值 0x90或者說+144。混合比較總讓人迷惑,因為總是強迫系統去提升一個操作數來和另一種類型匹配。有幾種方式可避免混合比較。可以把int映射為byte,之后比較兩個byte值:
if (b == (byte)0x90)
System.out.println("Joy!");
另外,可用mask抑制符號擴展,把byte轉換為int,之后比較兩個int值:
if ((b & 0xff) == 0x90)
System.out.println("Joy!");
但最好的方法是把常量值移出循環放到常量聲明中。
private static final byte TARGET = 0x90; // Broken!
public static void main(String[] args) {
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++)
if (b == TARGET)
System.out.print("Joy!");
}
不幸的是,上面編譯通不過:0x90對于byte類型來說不是一個有效值。這樣修改即可:
private static final byte TARGET = (byte)0x90;
To summarize: Avoid mixed-type comparisons, because they are inherently confusing (Puzzle 5). To help achieve this goal, use declared constants in place of "magic numbers." You already knew that this was a good idea; it documents the meanings of constants, centralizes their definitions, and eliminates duplicate definitions.現在你知道他還可以強制你為每一個常量定義適用的類型,避免一種混合類型比較的來源。
25
int j = 0;
for (int i = 0; i < 100; i++)
j = j++;
System.out.println(j); //打印出的是0
問題出在 j = j++; 等同于下列操作:
int tmp = j; j = j + 1; j = tmp;
這次的教訓和難題7一樣:在一個表達式中不要給同一個變量賦值超過一次。
26
public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
int count = 0;
for (int i = START; i <= END; i++)
count++;
System.out.println(count);
}
看起來像100,再仔細看循環是小于等于,應該是101?結果是程序沒有輸出任何值,陷入一個死循環。問題出在Integer.MAX_VALUE,當繼續增加的時候,他悄悄變為Integer.MIN_VALUE。如果你需要循環int值邊界,最好用long變量做索引:
for (long i = START; i <= END; i++) //輸出101
教訓是:ints are not integers。無論何時用基本類型,注意邊界值。上溢或下溢會出現什么情況?一般來說最好用大一點的類型(基本類型是byte, char, short, int, and long)。也可以不用long:
int i = START;
do {
count++;
} while (i++ != END);
考慮到清晰和簡單,總是用long索引,除了一種特殊情況:如果要遍歷所有int值,這樣用int索引的話會快兩倍。
一個循環四十億int值,調用方法的常規用法:
// Apply the function f to all four billion int values
int i = Integer.MIN_VALUE;
do {
f(i);
} while (i++ != Integer.MAX_VALUE);
27 位移
記住java是使用二進制補碼計算,在任何有符號基本類型中(byte, short, int, or long)都是用所有位置1來表示-1。
int i = 0;
while (-1 << i != 0) //左位移
i++;
System.out.println(i);
int型的-1用0xffffffff 表示。不斷左移,右邊由0補位。移位32次,變為全0,跳出循環打印32?實際上程序會死循環。問題出在-1<<32不等于0而是等于-1,因為位移符號只用右邊操作數的低五位作為移動距離,如果左操作數是long的話用六位。三個位移操作符:<<,>>,>>>都是這樣。移動距離總是0到31,左邊操作數是long的話0到63。位移距離用32取模,左邊是long則用64取模。給int值位移32位或給long值位移64位只會返回本身。所以不可能用位移完全移除一個數據。幸運的是,有一個簡單的辦法解決這個問題。保存上一次的位移結果,每一次迭代多移動一位。
int distance = 0;
for (int val = -1; val != 0; val <<= 1)
distance++;
System.out.println(distance);
修改后的程序說明了一個原則:位移距離如果可能的話,用常量。
另外一個問題,許多程序員認為右移一個負的移動距離,就和左移一樣,反之亦然。事實上不是這樣,左移是左移,右移就是右移。負數距離只留下低五位(long留六位),其余的置0就變為了正數距離。比如,左移一個int值-1的距離,實際上是左移31位。
28 無窮的表示
for (int i = start; i <= start + 1; i++) {
}
看起來循環兩次就會結束,如果這樣定義呢:
int start = Integer.MAX_VALUE - 1;//死循環
while (i == i + 1) {
}
這個不可能死循環?如果i是無窮呢?Java采用IEEE 754浮點數算術,用double或float來表示無窮。所以可以用任何浮點數計算表達式得出無窮來初始化i。比如:double i = 1.0 / 0.0;
更好的是可以利用標準庫提供的常量:double i = Double.POSITIVE_INFINITY;
事實上,根本用不著用無窮初始化i來引起死循環。只要足夠大的浮點數就夠了:double i = 1.0e40;
這是因為浮點數越大,他的值和下一個數的值距離也就越大。distribution of floating-point values is a consequence of their representation with a fixed number of significant bits. 給足夠大的浮點數加一不會改變值,因為他不能填充這個數和下一個數之間的距離。浮點數操作返回最接近準確數學結果的浮點值。一旦兩個相鄰浮點值之間的距離大于2,加1就不會有效果。float類型來說,超過225(或33,554,432)再加1就無效;對double來說超過254(接近1.8 x 1016)再加1就無效。
相鄰浮點數之間的距離稱為ulp(unit in the last place的縮寫)。在Java 5里 Math.ulp方法被引入來計算float或double值的ulp。
總結:不可能用float或double來表示無窮。另外,在一個大的浮點數上加一個小的浮點數,值不會改變。有點不合常理,實數并不是這樣。記住二進制浮點數計算只是近似于實數計算。
29 NaN
while (i != i) { }
IEEE 754 浮點數計算保留了一個特殊值來來表示不是數字的數量。NaN是浮點計算不能很好定義的數,比如0.0 / 0.0。規范定義NaN不等于任何數包括自己。因此double i = 0.0 / 0.0; 可讓開始的等式不成立。也有標準庫定義的常量:double i = Double.NaN; 如果一個或多個操作數為NaN,那么浮點數計算就會等于NaN。
總結:float和doule存在特殊的值NaN,小心處理。
30
while (i != i + 0) { } 這一次不能使用float或者double。
+操作符除了數字,就只能處理String。+操作符會被重載:對于String類型,他做的是連接操作。如果操作數有非String類型,會先做轉換變為String之后再做連接。i一般用作數字,要是對String型變量這么命名容易引起誤解。
總結:操作符重載非常容易誤導人。好的變量名,方法名,類名和好的注釋對于程序的可讀性一樣重要。
19 單行注釋
public static void main(String[] args) {
System.out.println(classify('n') + classify('+') + classify('2'));
}
static String classify(char ch) {
if ("0123456789".indexOf(ch) >= 0)
return "NUMERAL ";
if ("abcdefghijklmnopqrstuvwxyz".indexOf(ch) >= 0)
return "LETTER ";
/* (Operators not supported yet)
if ("+-
*/&|!=".indexOf(ch) >= 0)
return "OPERATOR ";
*/
return "UNKNOWN ";
}
編譯出錯,塊注釋不能嵌套,在注釋內的文本都不會被特殊對待。
// Code commented out with an if statement - doesn't always work!
if (false) {
/* Add the numbers from 1 to n */
int sum = 0;
for (int i = 1; i <= n; i++)
sum += i;
}
這是語言規范推薦的一種條件編譯的技術,但不是非常適合注釋代碼。除非包含的語句都是有效的表達式,否則這種條件編譯不能用作注釋。最好的注釋代碼方法是用單行注釋。
20 反斜杠
Me.class.getName() 返回的是Me類的完整名,如
"com.javapuzzlers.Me"。
System.out.println( Me.class.getName().replaceAll(".", "/") + ".class");
應該得到
com/javapuzzlers/Me.class?不對。問題出在
String.replaceAll把正則表達式作為第一個參數,而不是字符。正則表達是“.”表示配對任何單獨的字符,所以類名的每一個字符都被斜線替代。為了只匹配句號,必須用反斜線(\)轉義。因為反斜線在字符串中有特殊意義——它是escape sequence的開始——反斜線自身也必須用一個反斜線轉義。
正確:System.out.println( Me.class.getName().replaceAll(
"\\.", "/") + ".class");
為了解決這類問題,java 5提供了一個新的靜態方法
java.util.regex.Pattern.quote。用一個字符串作為參數,增加任何需要的轉義,返回一個和輸入字符串完全匹配的正則表達式字符串:
System.out.println(Me.class.getName().replaceAll(
Pattern.quote("."), "/") + ".class");
這個程序的另外一問題就是依賴于平臺。不是所有的文件系統都是用斜線來組織文件。為了在你運行的平臺取得正確的文件名,你必須使用正確的平臺分隔符來替換斜線。
21
System.out.println(MeToo.class.getName().
replaceAll("\\.", File.separator) + ".class");
java.io.File.separator 是一個公共的
String 屬性,指定用來包含平臺依賴的文件名分隔符。在UNIX上運行打印
com/javapuzzlers/MeToo.class。然而,在Windows上程序拋出異常:
StringIndexOutOfBoundsException: String index out of range: 1
結果是
String.replaceAll 的第二個參數不是普通字符串而是一個在
java.util.regex 規范中定義的
replacement string,反斜線轉義了后面的字符。當在Windows上運行的時候,替換字符是一個單獨的反斜線,無效。JAVA 5提供了兩個新方法來解決這個問題,一個是java.util.regex.Matcher.quoteReplacement,它替換字符串為相應的替換字符串:
System.out.println(MeToo.class.getName().replaceAll(
"\\.", Matcher.quoteReplacement(File.separator))+".class");
第二個方法提供了更好的解決方法。String.replace(CharSequence, CharSequence)和String.replaceAll做同樣的事情,但他把兩個參數都作為字符串處理:System.out.println(MeToo.class.getName().replace(".", File.separator) + ".class");
如果用的是java早期版本就沒有簡單的方法產生替換字符串。完全不用正則表達式,使用String.replace(char, char)跟容易一些:
System.out.println(MeToo.class.getName().replace('.', File.separatorChar) + ".class");
教訓:當用不熟悉的庫方法的時候,小心點。有懷疑的話,查看Javadoc。當然正則表達式也很棘手:他編譯時可能沒問題運行時卻更容易出錯。
22
statement label
認真寫注釋,及時更新。去掉無用代碼。如果有東西看起來奇怪不真實,很有可能是錯誤的。
23
private static Random rnd = new Random();
public static void main(String[] args) {
StringBuffer word = null;
switch(rnd.nextInt(2)) {
case 1: word = new StringBuffer('P');
case 2: word = new StringBuffer('G');
default: word = new StringBuffer('M');
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);
}
在一次又一次的運行中,以相等的概率打印出Pain,Gain或 Main?答案它總是在打印ain。一共有三個bug導致這種情況。
一是 Random.nextInt(int) ,看規范可知這里返回的是0到int值之間的前閉后開區間的隨機數。因此程序中永遠不會返回2。這是一個相當常見的問題源,被熟知為“柵欄柱錯誤(fencepost error)”。這個名字來源于對下面這個問題最常見的但卻是錯誤的答案,如果你要建造一個100英尺長的柵欄,其柵欄柱間隔為10英尺,那么你需要多少根柵欄柱呢?11根或9根都是正確答案,這取決于是否要在柵欄的兩端樹立柵欄柱,但是10根卻是錯誤的。要當心柵欄柱錯誤,每當你在處理長度、范圍或模數的時候,都要仔細確定其端點是否應該被包括在內,并且要確保你的代碼的行為要與其相對應。
第二個bug是 case沒有配套的break。
從5.0版本起,javac提供了-Xlint:fallthrough標志,當你忘記在一個case與下一個case之間添加break語句是,它可以生成警告信息。不要從一個非空的case向下進入了另一個case。這是一種拙劣的風格,因為它并不常用,因此會誤導讀者。十次中有九次它都會包含錯誤。如果Java不是模仿C建模的,那么它倒是有可能不需要break。對語言設計者的教訓是:應該考慮提供一個結構化的switch語句。
最后一個,也是最微妙的一個bug是表達式new StringBuffer(‘M')可能沒有做哪些你希望它做的事情。StringBuffer(char)構造器根本不存在。StringBuffer有一個無參數的構造器,一個接受一個String作為字符串緩沖區初始內容的構造器,以及一個接受一個int作為緩沖區初始容量的構造器。在本例中,編譯器會選擇接受int的構造器,通過拓寬原始類型轉換把字符數值'M'轉換為一個int數值77[JLS 5.1.2]。換句話說,new StringBuffer(‘M')返回的是一個具有初始容量77的空的字符串緩沖區。該程序余下的部分將字符a、i和n添加到了這個空字符串緩沖區中,并打印出該字符串緩沖區那總是ain的內容。 為了避免這類問題,不管在什么時候,都要盡可能使用熟悉的慣用法和API。如果你必須使用不熟悉的API,那么請仔細閱讀其文檔。在本例中,程序應該使用常用的接受一個String的StringBuffer構造器。