2)解壓縮,下面的命令行如啟動報錯,請自行查略Hive啟動配置
-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
用junit測試任然無法打印出真實參數(shù)。根據(jù)這些實踐,確定log4j2是使用無誤生效的,只是org.hibernate這部分的logger一直未起效
P.S 把這個問題提交給Appfuse官網(wǎng),issue APF-1478,作者標(biāo)志為4.0版本修復(fù)。
JAVA_HOME沒有正確傳遞
),查看到eclipse默認(rèn)的是安裝的jre目錄,修改到j(luò)dk目錄下,依賴問題解決。不過目前版本仍然沒有解決pom文件的“Plugin execution not covered by lifecycle configuration”錯誤,暫時忽略不管吧。
在Son類里面寫一個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
}
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");
}
}
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 GrandParent
test of Parent
test of Son
使用invokespecial這種方式也有局限,只能從子類調(diào)用。否則報錯:
.setJsonFactory(JSON_FACTORY).setClientSecrets(clientSecrets)
.addRefreshListener(new CredentialStoreRefreshListener(userID, new DBCredentialStore())).build()
.setAccessToken(accessToken).setRefreshToken(refreshToken)
同時Builder類設(shè)置為static也是對Item 22:Favor static member classes over nonstatic的實踐
一 基本流程
使用Google Calendar v3 ,如果以servlet作為代理,可以使用官方示例,自己寫一個類A.java繼承AbstractAuthorizationCodeServlet類,這個類主要用于跳轉(zhuǎn)到google提供的授權(quán)頁面,如果用戶同意授權(quán),則根據(jù)A類中的URL(這個必須和注冊的google 回調(diào)路徑相同,比如oauth_callback否則報錯)重定向到B類,B.java 繼承AbstractAuthorizationCodeCallbackServlet類,這個訪問路徑類似http://www.example.com/oauth_callback?code=ABC1234。這里我配置oauth_callback為servlet的訪問路徑,B類中的
二 需要參數(shù)的情況
有些業(yè)務(wù)需要用戶傳參數(shù),直接傳參數(shù)給A,再試圖在B中獲取是不行的!B類中只能獲取某些固定的參數(shù),如code。要想傳用戶參數(shù),我們可以在A中先獲取,把幾個參數(shù)組裝為json格式字符串(還可以繼續(xù)base64編碼),把這個字符串作為state的值,再重定向到授權(quán)頁面,同意后state參數(shù)可以傳到B類,取值解析json字符串(或先base64解碼),得到參數(shù)。
由于API中AuthorizationCodeRequestUrl有處理state的方法,而AbstractAuthorizationCodeServlet已經(jīng)直接封裝,為了使用setState,直接在A類中繼承HttpServlet重寫service方法,復(fù)制大部分AbstractAuthorizationCodeServlet的內(nèi)容,稍作修改:
三 關(guān)于refresh token
默認(rèn)情況下,用戶授權(quán)之后token會有一個小時的有效期,之后你可以通過refresh token再重新獲取token。所以,如果不需要用戶再次授權(quán),可以在第一次,保存好token、refresh token、ExpirationTime。實例中用了JDO來實現(xiàn),自己如果使用數(shù)據(jù)庫保存,可類似寫一個類實現(xiàn)CredentialStore類。使用的時候,現(xiàn)在數(shù)據(jù)庫中取出,再創(chuàng)建credential,如:
.setJsonFactory(JSON_FACTORY).setClientSecrets(clientSecrets)
.addRefreshListener(new CredentialStoreRefreshListener(userID, new DBCredentialStore())).build()
.setAccessToken(accessToken).setRefreshToken(refreshToken)
.setExpirationTimeMilliseconds(expirationTimeMilliseconds);
在無效的情況下,Listener會自動去用refresh token請求。
以字符串為例介紹:
1 。構(gòu)造json 字符串
例如要傳送json格式的字符串
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);
String state = req.getParameter("state");
String appID = jsonparer.parse(state).getAsJsonObject().get("appID").getAsString();
String userID = jsonparer.parse(state).getAsJsonObject().get("userID").getAsString();
UTC + 時區(qū)差 = 本地時間
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);
utcCal.setTime(date);
utcCal.add(Calendar.MILLISECOND, offsetFromUTC);
System.out.println("Created GMT cal with date [" + gmtCal.getTime()
+ "==" + utcCal.getTime() + "]");
return gmtCal;
}
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;
}
}
}
<%@ page import="java.io.*,java.net.*"%>
<%
StringBuffer sbf = new StringBuffer();
//Access the page
try {
//如果網(wǎng)絡(luò)設(shè)置了代理
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部分:
{
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:可能不能完全發(fā)揮框架和web services的能量,但能完成目標(biāo)。減少了學(xué)習(xí)曲線,不用非常透徹了解web services概念,只要對某個框架有一定了解就能完成任務(wù)。
2.Contract first approach:根據(jù)服務(wù)先寫WSDL文件,寫好之后使用框架的工具把WSDL轉(zhuǎn)換為依賴框架的代碼。
一 介紹
當(dāng)客戶端調(diào)用你的web service的時候,他會發(fā)送一個消息過來(可能是soap 消息),如:
<foo:concatRequest>
<s1>abc</s1>
<s2>123</s2>
</foo:concatRequest>
這時候如果有一個轉(zhuǎn)換器把這個soap消息轉(zhuǎn)換成java對象,然后調(diào)用你提供的java對象(方法)的話將會是非常方便的。幾個最流行的庫就是充當(dāng)了這種轉(zhuǎn)換器功能,比如CXF, Axis2 , Metro (jdk6自帶的有)。
手動創(chuàng)建WSDL文件比較容易出錯,可以使用eclipse進(jìn)行可視化編輯。
二 生成服務(wù)代碼
像CXF這樣的 web service庫可以創(chuàng)建轉(zhuǎn)換器把進(jìn)來的SOAP 消息轉(zhuǎn)換為Java對象,然后作為參數(shù)傳給方法。生成這些代碼,只需創(chuàng)建一個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)來實現(xiàn)這個接口,寫入自己的業(yè)務(wù)。還會生成傳入消息對應(yīng)的java對象。同時生成一個服務(wù)器類:
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就可以服務(wù)了。
2 Axis2 方式
用類似的寫main方法,或者配置eclipse的axis2插件可生成:在WSDL文件上,右鍵->web service->generate java bean skeleton
界面的上半部分針對服務(wù)端,可以根據(jù)需要調(diào)整生成的級別,下半部分是生成客戶端。具體的級別可參考eclipse的幫助文檔。一路下一步,最后根據(jù)命名空間生成包路徑的文件,里面有XXSkeletonInterface.java 文件(如果生成的時候選擇了生成接口的話),還有一個XXSkeleton實現(xiàn)了這個接口,也是我們需要修改這部分代碼完成我們業(yè)務(wù)的地方。實際上有一個XXMessageReceiverInOut.java的類接收請求的消息,并調(diào)用XXSkeletonInterface。使用eclipse的axis2插件的時候,會自動在web-inf文件夾下生成service\xx(你的wsdl服務(wù)名),這下面還要一個meta-inf文件夾裝有wsd文件和一個services.xml配置文件。services.xml文件可配置包括XXMessageReceiverInOut類在內(nèi)的選項。
二 生成客戶端代碼
為了調(diào)用這些web service,同樣可以用CXF這些庫來生成在客戶端運行的轉(zhuǎn)換器(稱為service stub)。當(dāng)調(diào)用stub里的方法的時候,他會把你的數(shù)據(jù)/對象 轉(zhuǎn)換為正確的XML格式,然后發(fā)送給真正的web service。當(dāng)他收到響應(yīng)的時候,又會把XML轉(zhuǎn)回Java。
1 CXF 方式
和生成服務(wù)器端類似,使用方法
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是創(chuàng)建的service stub,他模擬了客戶端的服務(wù)。我們需要修改這個類中的_concat_parameters部分,加入?yún)?shù):
com.ttdev.ss.ConcatRequest _concat_parameters = new ConcatRequest();
_concat_parameters.setS1("abc");
_concat_parameters.setS2("123");
現(xiàn)在就可以運行客戶端代碼了。SEI中有一些注解,可以修改,不細(xì)說。
有兩種SOAP message風(fēng)格,document 和RPC,他們定義了SOAP message body的格式。使用document風(fēng)格時(包括wrapped和unwrapped),在wsdl中有一個非空的types部分,這個部分用XML Schema language定義了web service要用到的類型。wsgen工具從SIB(有SEI就足夠了)中生成與XSD對應(yīng)的java類。用java代碼生成WSDL文件的時候需要一些java類,wsgen工具可以生成這些Java類,生成的這些java類被稱為wsgen artifacts,底層的JWS類庫會用到這些類,特別是JAX-B系列的包,會用來轉(zhuǎn)換(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為基礎(chǔ)的web service的互操作性。JAX-B類庫提供了Java和XSD類型轉(zhuǎn)換的底層支持。
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的數(shù)據(jù)類型,他們和XML Schema type綁定,每個message的XML Schema types從這些java類型得來的。注:在當(dāng)前的jdk1.6.24中,已經(jīng)融入wsgen自動生成的過程,不需手動調(diào)用。
wsgen工具可用來生成wsdl文件,如:% wsgen -cp "." -wsdl ch01.ts.TimeServerImpl 。這為TimeServer服務(wù)生成了wsdl。用wsgen生成的wsdl和通過訪問發(fā)布的服務(wù)生成的wsdl 有個很大的區(qū)別:wsgen生成的沒有endpoint,因為這個URL是在發(fā)布服務(wù)的時候決定的。其他地方兩個wsdl是相同的。
wsimport(以前叫wsdl2java和 java2wsdl更形象)工具可使用WSDL生成用來幫助寫客戶端的artifacts .
1 先契約再編碼方式
一個例子:得到一個tempConvert.wsdl文件,使用命令 wsimport -keep -p ch02.tc tempConvert.wsdl ,命令根據(jù)wsdl的portType生成一個SEI類,把SEI的interface換為class,再把方法改為實現(xiàn)就可變?yōu)镾IB。把該SIB發(fā)布,再使用命令wsimport -keep -p clientTC http://localhost:5599/tc?wsdl,來生成客戶端輔助類
2 編碼優(yōu)先
服務(wù)被發(fā)布之后,會自動生成WSDL供客戶端使用。然而,使用annotations可以控制WSDL或WSDL-generated artifacts的生成。
來自
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.
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.
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元素。元素名只能以下劃線或字母開頭,不能有空格,區(qū)分大小寫。開元素必須有對應(yīng)閉元素(也有類似html的簡寫,如<img src="/images/xml.gif" />)。文檔由DTD或schema來限制它是否合格。
屬性:
什么時候用屬性?基本原則:多個值的數(shù)據(jù)用元素,單值的數(shù)據(jù)用元素。如果數(shù)據(jù)有很多值或者比較長,數(shù)據(jù)最可能屬于元素。他主要被當(dāng)作文本,容<rss:author>Doug Hally</rss:author> <journal:author>Neil Gaiman</journal:author>易搜索,好用。比如一本書的章節(jié)描述。然而如果數(shù)據(jù)主要作為單值處理的話,最好作為屬性。如果搞不清楚,可以安全的使用元素。
命名空間Namespaces:
xml的命名空間是一種用一個特定的URI來關(guān)聯(lián)XML文檔里的一個或多個元素的方法。意味著元素是由名字和命名空間一起來識別的。許多復(fù)雜的XML文件里,同一個名字會有多個用途。比如,一個RSS feed有一個作者,這個作者同時是每個日記的。雖然這些數(shù)據(jù)都用author元素來表示,但他們不應(yīng)該被當(dāng)作同一個類型的數(shù)據(jù)。命名空間很好的解決了這個問題,命名空間說明書要求一個前綴和唯一的URI聯(lián)合起來區(qū)分不同命名空間里的元素。如http://www.neilgaiman.com/entries作為URI,聯(lián)合前綴journal用來表示日志相關(guān)的元素。
<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>
如果名字沒有命名空間,不代表在默認(rèn)命名空間中,而是xml處理器以在任何命名空間以外方式解釋他。要聲明默認(rèn)命名空間的話就不用后面冒號部分,如<Insured xmlns="使用的一些術(shù)語:
-
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:
用來處理轉(zhuǎn)義字符,語法是& [entity name] ;XML解析器碰到這種entity reference,就會用對應(yīng)的值替換掉他。如<(<),>(>),&(&),"("),'(')。注意entity reference是用戶可定義的。比如多處用到版權(quán)提示,自己定義后以后更改就方便了:<ora:copyright>&OReillyCopyright;</ora:copyright>。除了用來表示數(shù)據(jù)中的復(fù)雜或特殊字符外,entity reference還會有更多用途。
不解析的數(shù)據(jù):
當(dāng)傳輸大量數(shù)據(jù)給應(yīng)用且不用xml解析的時候,CDATA就有用了。當(dāng)大量的字符需要用entity reference轉(zhuǎn)義的時候,或空格必須保留的時候,使用CDATA。<![CDATA[….]]>

// The default delimiter is the comma, but this
















































































31
while (i != 0)
i >>>= 1; //無符號右移,不管正負(fù)左邊都是補(bǔ)0
為了表達(dá)式合法,這里的i必須是整型(byte, char, short, int, or long)。謎題的關(guān)鍵在于>>>= 是一個復(fù)合賦值操作符,不幸的是復(fù)合賦值操作符會默默的做narrowing primitive conversions,即從一個數(shù)據(jù)類型轉(zhuǎn)換為一個更小的數(shù)據(jù)類型。Narrowing primitive conversions can lose information about the magnitude or precision of numeric values。為了使問題更具體,假設(shè)這樣定義:
short i = -1; 因為初始值i ((short)0xffff) 非零,循環(huán)執(zhí)行。第一步位移會把i提升為int。short, byte, or char類型的操作數(shù)都會做這樣的操作。這是widening primitive conversion,沒有信息丟失。這種提升有符號擴(kuò)展,因此結(jié)果是int值0xffffffff。無符號右移一位產(chǎn)生int值0x7fffffff。為了把int值存回short變量,Java執(zhí)行了可怕的narrowing primitive conversion,即簡單去掉高十六位。這樣又變回了(short)0xffff。如果定義類似short or byte型的負(fù)數(shù),都會得到類似結(jié)果。你如果定義的是char話,則不會無限循環(huán),因為char值非負(fù),位移之前的寬擴(kuò)展不會做符號擴(kuò)展。
總結(jié):不要在short, byte, or char變量上使用復(fù)合賦值操作符。這種表達(dá)式進(jìn)行混合類型計算,非常容易混淆。更糟糕的是隱含的窄映射會丟掉信息。
32
while (i <= j && j <= i && i != j) {}
i <= j and j <= i, surely i must equal j?對于實數(shù)來說是這樣的。他非常重要,有個名稱: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之前數(shù)據(jù)比較符號(<, <=, >, and >=) 需要兩邊的操作數(shù)必須為基礎(chǔ)類型(byte, char, short, int, long, float, or double).在Java 5變?yōu)閮蛇叢僮鲾?shù)為凡是可轉(zhuǎn)變?yōu)榛A(chǔ)類型的類型。java 5引入autoboxing and auto-unboxing 。The boxed numeric types are Byte, Character, Short, Integer, Long, Float, and Double。具體點,讓上面進(jìn)入無限循環(huán):
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,所以表達(dá)式為true。i != j比較的是對象引用,也為true。很奇怪規(guī)范沒有把等號改為比較值。原因很簡單:兼容性。當(dāng)一種語言廣泛應(yīng)用的時候,不能破壞已存在的規(guī)范來改變程序的行為。System.out.println(new Integer(0) == new Integer(0));總是輸出false,所以必須保留。當(dāng)一個是boxed numeric 類型,另一個是基本類型的時候可以值比較。因為java 5之前這是非法的,具體點:
System.out.println(new Integer(0) == 0); //之前的版本非法,Java 5輸出True
總結(jié):當(dāng)兩邊的操作數(shù)是boxed numeric類型的時候,數(shù)字比較符和等于符號是根本不同的:數(shù)字比較符是值比較,等號比較的是對象引用。
33
while (i != 0 && i == -i) {}
有負(fù)號表示i一定是數(shù)字,NaN不行,因為他不等于任何數(shù)。事實上,沒有實數(shù)可以出現(xiàn)這種情況。但Java的數(shù)字類型并沒有完美表達(dá)實數(shù)。浮點數(shù)由一個符號位,一個有效數(shù)字(尾數(shù)),一個指數(shù)構(gòu)成。浮點數(shù)只有0才會和自己的負(fù)數(shù)相等,所以i肯定是整數(shù)。有符號整數(shù)用的是二進(jìn)制補(bǔ)碼計算:取反加一。補(bǔ)碼的一大優(yōu)勢是用一個唯一的數(shù)來表示0。然而有一個對應(yīng)的缺點:本來可表達(dá)偶數(shù)個值,現(xiàn)在用一個表達(dá)了0,剩下奇數(shù)個來表示正負(fù)數(shù),意味著正數(shù)和負(fù)數(shù)的數(shù)量不一樣。比如int值,他的Integer.MIN_VALUE(-231)。十六進(jìn)制表達(dá)0x8000000。通過補(bǔ)碼計算可知他的負(fù)數(shù)仍然不變。對他取負(fù)數(shù)是溢出了的,不過Java在整數(shù)計算中忽略了溢出。
總結(jié):Java使用二進(jìn)制補(bǔ)碼,是不對稱的。有符號整數(shù)(int, long, byte, and short) 負(fù)數(shù)值比整數(shù)值多一個。
34
final int START = 2000000000;
int count = 0;
for (float f = START; f < START + 50; f++)
count++;
System.out.println(count);
注意循環(huán)變量是float。回憶謎題28 ,明顯f++沒有任何作用。f的初始化值接近Integer.MAX_VALUE,因此需要31位來準(zhǔn)確表達(dá),但是float類型只提供了24位精度。增加這樣大的一個float值不會改變值??雌饋頃姥h(huán)?運行程序會發(fā)現(xiàn),輸出0。循環(huán)中f和(float)(START + 50)做比較。但int和float比較的時候,自動把int先提示為float。不幸的是三個會引起精度丟失的寬基本類型轉(zhuǎn)換的其中之一(另外兩個是long到float,long到double)。f的初始值巨大,加50再轉(zhuǎn)換為float和直接把f轉(zhuǎn)換為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);
結(jié)果是60000,問題在于布爾表達(dá)式ms % 60*1000 == 0,最簡單的修改方法是:if (ms % (60 * 1000) == 0)
更好的方法是用合適命名的常量代替魔力數(shù)字:
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);
}
絕不要用空格來分組;使用括號來決定優(yōu)先級
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
if (b == 0x90)
System.out.print("Joy!");
}
Ox90 超過了byte的取值范圍-128到127。byte和int比較是一種混合類型比較。考慮個表達(dá)式((byte)0x90 == 0x90)得到的是false。byte和int做比較的時候,Java先對byte進(jìn)行了widening primitive conversion 再比較兩個int值。因為byte是有符號類型,轉(zhuǎn)變做了符號擴(kuò)展,把負(fù)的byte值轉(zhuǎn)換為相應(yīng)的int值。這個例子中(byte)0x90被轉(zhuǎn)變?yōu)?112,當(dāng)然不等于int值 0x90或者說+144?;旌媳容^總讓人迷惑,因為總是強(qiáng)迫系統(tǒng)去提升一個操作數(shù)來和另一種類型匹配。有幾種方式可避免混合比較??梢园裪nt映射為byte,之后比較兩個byte值:
if (b == (byte)0x90)
System.out.println("Joy!");
另外,可用mask抑制符號擴(kuò)展,把byte轉(zhuǎn)換為int,之后比較兩個int值:
if ((b & 0xff) == 0x90)
System.out.println("Joy!");
但最好的方法是把常量值移出循環(huán)放到常量聲明中。
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.現(xiàn)在你知道他還可以強(qiáng)制你為每一個常量定義適用的類型,避免一種混合類型比較的來源。
25
for (int i = 0; i < 100; i++)
j = j++;
System.out.println(j); //打印出的是0
問題出在 j = j++; 等同于下列操作:
int tmp = j; j = j + 1; j = tmp;
這次的教訓(xùn)和難題7一樣:在一個表達(dá)式中不要給同一個變量賦值超過一次。
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,再仔細(xì)看循環(huán)是小于等于,應(yīng)該是101?結(jié)果是程序沒有輸出任何值,陷入一個死循環(huán)。問題出在Integer.MAX_VALUE,當(dāng)繼續(xù)增加的時候,他悄悄變?yōu)?tt>Integer.MIN_VALUE。如果你需要循環(huán)int值邊界,最好用long變量做索引:
for (long i = START; i <= END; i++) //輸出101
教訓(xùn)是:ints are not integers。無論何時用基本類型,注意邊界值。上溢或下溢會出現(xiàn)什么情況?一般來說最好用大一點的類型(基本類型是byte, char, short, int, and long)。也可以不用long:
int i = START;
do {
count++;
} while (i++ != END);
考慮到清晰和簡單,總是用long索引,除了一種特殊情況:如果要遍歷所有int值,這樣用int索引的話會快兩倍。
一個循環(huán)四十億int值,調(diào)用方法的常規(guī)用法:
// 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是使用二進(jìn)制補(bǔ)碼計算,在任何有符號基本類型中(byte, short, int, or long)都是用所有位置1來表示-1。
int i = 0;
while (-1 << i != 0) //左位移
i++;
System.out.println(i);
int型的-1用0xffffffff 表示。不斷左移,右邊由0補(bǔ)位。移位32次,變?yōu)槿?,跳出循環(huán)打印32?實際上程序會死循環(huán)。問題出在-1<<32不等于0而是等于-1,因為位移符號只用右邊操作數(shù)的低五位作為移動距離,如果左操作數(shù)是long的話用六位。三個位移操作符:<<,>>,>>>都是這樣。移動距離總是0到31,左邊操作數(shù)是long的話0到63。位移距離用32取模,左邊是long則用64取模。給int值位移32位或給long值位移64位只會返回本身。所以不可能用位移完全移除一個數(shù)據(jù)。幸運的是,有一個簡單的辦法解決這個問題。保存上一次的位移結(jié)果,每一次迭代多移動一位。
int distance = 0;
for (int val = -1; val != 0; val <<= 1)
distance++;
System.out.println(distance);
修改后的程序說明了一個原則:位移距離如果可能的話,用常量。
另外一個問題,許多程序員認(rèn)為右移一個負(fù)的移動距離,就和左移一樣,反之亦然。事實上不是這樣,左移是左移,右移就是右移。負(fù)數(shù)距離只留下低五位(long留六位),其余的置0就變?yōu)榱苏龜?shù)距離。比如,左移一個int值-1的距離,實際上是左移31位。
28 無窮的表示
for (int i = start; i <= start + 1; i++) {
}
看起來循環(huán)兩次就會結(jié)束,如果這樣定義呢:
int start = Integer.MAX_VALUE - 1;//死循環(huán)
while (i == i + 1) {
}
這個不可能死循環(huán)?如果i是無窮呢?Java采用IEEE 754浮點數(shù)算術(shù),用double或float來表示無窮。所以可以用任何浮點數(shù)計算表達(dá)式得出無窮來初始化i。比如:double i = 1.0 / 0.0;
更好的是可以利用標(biāo)準(zhǔn)庫提供的常量:double i = Double.POSITIVE_INFINITY;
事實上,根本用不著用無窮初始化i來引起死循環(huán)。只要足夠大的浮點數(shù)就夠了:double i = 1.0e40;
這是因為浮點數(shù)越大,他的值和下一個數(shù)的值距離也就越大。distribution of floating-point values is a consequence of their representation with a fixed number of significant bits. 給足夠大的浮點數(shù)加一不會改變值,因為他不能填充這個數(shù)和下一個數(shù)之間的距離。浮點數(shù)操作返回最接近準(zhǔn)確數(shù)學(xué)結(jié)果的浮點值。一旦兩個相鄰浮點值之間的距離大于2,加1就不會有效果。float類型來說,超過225(或33,554,432)再加1就無效;對double來說超過254(接近1.8 x 1016)再加1就無效。
相鄰浮點數(shù)之間的距離稱為ulp(unit in the last place的縮寫)。在Java 5里 Math.ulp方法被引入來計算float或double值的ulp。
總結(jié):不可能用float或double來表示無窮。另外,在一個大的浮點數(shù)上加一個小的浮點數(shù),值不會改變。有點不合常理,實數(shù)并不是這樣。記住二進(jìn)制浮點數(shù)計算只是近似于實數(shù)計算。
29 NaN
while (i != i) { }
IEEE 754 浮點數(shù)計算保留了一個特殊值來來表示不是數(shù)字的數(shù)量。NaN是浮點計算不能很好定義的數(shù),比如0.0 / 0.0。規(guī)范定義NaN不等于任何數(shù)包括自己。因此double i = 0.0 / 0.0; 可讓開始的等式不成立。也有標(biāo)準(zhǔn)庫定義的常量:double i = Double.NaN; 如果一個或多個操作數(shù)為NaN,那么浮點數(shù)計算就會等于NaN。
總結(jié):float和doule存在特殊的值NaN,小心處理。
30
while (i != i + 0) { } 這一次不能使用float或者double。
+操作符除了數(shù)字,就只能處理String。+操作符會被重載:對于String類型,他做的是連接操作。如果操作數(shù)有非String類型,會先做轉(zhuǎn)換變?yōu)镾tring之后再做連接。i一般用作數(shù)字,要是對String型變量這么命名容易引起誤解。
總結(jié):操作符重載非常容易誤導(dǎo)人。好的變量名,方法名,類名和好的注釋對于程序的可讀性一樣重要。
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 ";
}
編譯出錯,塊注釋不能嵌套,在注釋內(nèi)的文本都不會被特殊對待。
// 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;
}
這是語言規(guī)范推薦的一種條件編譯的技術(shù),但不是非常適合注釋代碼。除非包含的語句都是有效的表達(dá)式,否則這種條件編譯不能用作注釋。最好的注釋代碼方法是用單行注釋。
20 反斜杠
Me.class.getName() 返回的是Me類的完整名,如"com.javapuzzlers.Me"。
System.out.println( Me.class.getName().replaceAll(".", "/") + ".class");
應(yīng)該得到com/javapuzzlers/Me.class?不對。問題出在String.replaceAll把正則表達(dá)式作為第一個參數(shù),而不是字符。正則表達(dá)是“.”表示配對任何單獨的字符,所以類名的每一個字符都被斜線替代。為了只匹配句號,必須用反斜線(\)轉(zhuǎn)義。因為反斜線在字符串中有特殊意義——它是escape sequence的開始——反斜線自身也必須用一個反斜線轉(zhuǎn)義。
正確:System.out.println( Me.class.getName().replaceAll("\\.", "/") + ".class");
為了解決這類問題,java 5提供了一個新的靜態(tài)方法java.util.regex.Pattern.quote。用一個字符串作為參數(shù),增加任何需要的轉(zhuǎn)義,返回一個和輸入字符串完全匹配的正則表達(dá)式字符串:
System.out.println(Me.class.getName().replaceAll(Pattern.quote("."), "/") + ".class");
這個程序的另外一問題就是依賴于平臺。不是所有的文件系統(tǒng)都是用斜線來組織文件。為了在你運行的平臺取得正確的文件名,你必須使用正確的平臺分隔符來替換斜線。
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
結(jié)果是String.replaceAll 的第二個參數(shù)不是普通字符串而是一個在java.util.regex 規(guī)范中定義的 replacement string,反斜線轉(zhuǎn)義了后面的字符。當(dāng)在Windows上運行的時候,替換字符是一個單獨的反斜線,無效。JAVA 5提供了兩個新方法來解決這個問題,一個是java.util.regex.Matcher.quoteReplacement,它替換字符串為相應(yīng)的替換字符串:
System.out.println(MeToo.class.getName().replaceAll(
"\\.", Matcher.quoteReplacement(File.separator))+".class");
第二個方法提供了更好的解決方法。String.replace(CharSequence, CharSequence)和String.replaceAll做同樣的事情,但他把兩個參數(shù)都作為字符串處理:System.out.println(MeToo.class.getName().replace(".", File.separator) + ".class");
如果用的是java早期版本就沒有簡單的方法產(chǎn)生替換字符串。完全不用正則表達(dá)式,使用String.replace(char, char)跟容易一些:
System.out.println(MeToo.class.getName().replace('.', File.separatorChar) + ".class");
教訓(xùn):當(dāng)用不熟悉的庫方法的時候,小心點。有懷疑的話,查看Javadoc。當(dāng)然正則表達(dá)式也很棘手:他編譯時可能沒問題運行時卻更容易出錯。
22 statement label
認(rèn)真寫注釋,及時更新。去掉無用代碼。如果有東西看起來奇怪不真實,很有可能是錯誤的。
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導(dǎo)致這種情況。
一是 Random.nextInt(int) ,看規(guī)范可知這里返回的是0到int值之間的前閉后開區(qū)間的隨機(jī)數(shù)。因此程序中永遠(yuǎn)不會返回2。這是一個相當(dāng)常見的問題源,被熟知為“柵欄柱錯誤(fencepost error)”。這個名字來源于對下面這個問題最常見的但卻是錯誤的答案,如果你要建造一個100英尺長的柵欄,其柵欄柱間隔為10英尺,那么你需要多少根柵欄柱呢?11根或9根都是正確答案,這取決于是否要在柵欄的兩端樹立柵欄柱,但是10根卻是錯誤的。要當(dāng)心柵欄柱錯誤,每當(dāng)你在處理長度、范圍或模數(shù)的時候,都要仔細(xì)確定其端點是否應(yīng)該被包括在內(nèi),并且要確保你的代碼的行為要與其相對應(yīng)。
第二個bug是 case沒有配套的break。從5.0版本起,javac提供了-Xlint:fallthrough標(biāo)志,當(dāng)你忘記在一個case與下一個case之間添加break語句是,它可以生成警告信息。不要從一個非空的case向下進(jìn)入了另一個case。這是一種拙劣的風(fēng)格,因為它并不常用,因此會誤導(dǎo)讀者。十次中有九次它都會包含錯誤。如果Java不是模仿C建模的,那么它倒是有可能不需要break。對語言設(shè)計者的教訓(xùn)是:應(yīng)該考慮提供一個結(jié)構(gòu)化的switch語句。
最后一個,也是最微妙的一個bug是表達(dá)式new StringBuffer(‘M')可能沒有做哪些你希望它做的事情。StringBuffer(char)構(gòu)造器根本不存在。StringBuffer有一個無參數(shù)的構(gòu)造器,一個接受一個String作為字符串緩沖區(qū)初始內(nèi)容的構(gòu)造器,以及一個接受一個int作為緩沖區(qū)初始容量的構(gòu)造器。在本例中,編譯器會選擇接受int的構(gòu)造器,通過拓寬原始類型轉(zhuǎn)換把字符數(shù)值'M'轉(zhuǎn)換為一個int數(shù)值77[JLS 5.1.2]。換句話說,new StringBuffer(‘M')返回的是一個具有初始容量77的空的字符串緩沖區(qū)。該程序余下的部分將字符a、i和n添加到了這個空字符串緩沖區(qū)中,并打印出該字符串緩沖區(qū)那總是ain的內(nèi)容。 為了避免這類問題,不管在什么時候,都要盡可能使用熟悉的慣用法和API。如果你必須使用不熟悉的API,那么請仔細(xì)閱讀其文檔。在本例中,程序應(yīng)該使用常用的接受一個String的StringBuffer構(gòu)造器。
System.out.print("H" + "a");System.out.print('H' + 'a'); //貌似輸出HaHa?
最后輸出的是Ha169。'H' 和'a' 都是 char,不是String,+操作符做的是加法操作而不是拼接字符串。編譯器提升兩個char值到int值,從16位零擴(kuò)展到32位的int。一個是72另一個是97。從語義上說,char值和字符串的相似是非常迷惑的。java語言僅僅把char看做無符號的16位基本整數(shù)。庫不這樣認(rèn)為,他里面有許多方法把char參數(shù)當(dāng)作Unicode字符在處理。怎樣連接字符?可以使用庫方法,如:
StringBuffer sb = new StringBuffer();
sb.append('H');sb.append('a');
System.out.println(sb); //可行但丑陋
有很多方法避免這種繁瑣,只要一個操作符是string就能強(qiáng)制+操作符做拼接操作。習(xí)慣用法是在前面加一個空字符串“”,如:
System.out.print("" + 'H' + 'a');//雖然有用,但還是有點不雅而且容易導(dǎo)致一點困惑
試試:System.out.println("2 + 2 = " + 2+2); 如果用的是java 5還可以使用printf:System.out.printf("%c%c", 'H', 'a');
總結(jié):小心應(yīng)對字符串拼接操作符。“+”只有至少一個是String的時候才做字符串拼接;否則做加法。如果沒有String型,有幾個選擇:加空字符串;用String.valueOf把第一個值顯示轉(zhuǎn)換為String;用String buffer;java 5的話用printf 。
12 字符數(shù)組
String letters = "ABC";
char[] numbers = { '1', '2', '3' };
System.out.println(letters + " easy as " + numbers); //返回ABC easy as [C@16f0472
char 雖是基本整數(shù)類型,但char值常常表示字符而不是整數(shù),很多庫對他特殊對待。比如,把char傳給println 輸出的是Unicode 字符而不是數(shù)字碼。char數(shù)組獲得同樣對待:char[] 的重載是println輸出數(shù)組中的所有字符,char[]對String.valueOf 和StringBuffer.append 的重載也類似。但是字符拼接操作符不是像這些方法,他是對兩邊做字符串轉(zhuǎn)換然后再拼接。對于對象引用包括數(shù)組,字符串轉(zhuǎn)換是這樣定義的:如果應(yīng)用是null,轉(zhuǎn)換為字符串"null",否則調(diào)用被引用對象的toString 無參數(shù)方法;如果toString 方法返回的是null,還是用“null”字符串。非null的char數(shù)組調(diào)用toString做什么操作?數(shù)組從Object 繼承來的toString,定義,“返回字符串含有對象實例類名,字符'@',對象的用無符號十六進(jìn)制表示的hash碼”。 Class.getName 的說明表示對 char[] 該類對象調(diào)用該方法返回 "[C"。兩個方法修正,拼接之前可顯示轉(zhuǎn)換數(shù)組為String:
System.out.println(letters + " easy as " + String.valueOf(numbers));
還可以把the System.out.println 分開成兩個來利用 char[] 對println的重載:
System.out.print(letters + " easy as ");System.out.println(numbers);
注意這些修正只是在你正確重載valueOf 和println方法才起效。換句話說,他們嚴(yán)重依賴編譯時數(shù)組引用的類型。 下面例子看起來用來第二個修正方法,但還是輸出丑陋字符串。因為他調(diào)用了Object對println的重載而不是char[]的重載。
// Broken - invokes the wrong overloading of println!
class Abc {
public static void main(String[] args) {
String letters = "ABC";
Object numbers = new char[] { '1', '2', '3' };
System.out.print(letters + " easy as ");
System.out.println(numbers); // Invokes println(Object)
}
}
總結(jié):char數(shù)組不是字符串。把char數(shù)組轉(zhuǎn)換為字符串,調(diào)用 String.valueOf(char[])。一些庫方法給char數(shù)組提供了像字符串的支持,典型的是給Object提供一個重載再給char[]提供一個重載;后一個才是理想的操作。
13 Interning
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out.println("Animals are equal: " + pig == dog);
compile-time constants of type String are interned.換句話說,任何兩個有相同字符的String類型的常量表達(dá)式是同一個對象引用表示的。所以如果用常量表達(dá)式初始化的話,pig和dog會指向同一個對象,但dog沒用常量表達(dá)式。Java語言限制哪些操作可以出現(xiàn)在常量表達(dá)式中,方法調(diào)用是不允許的。因此,程序應(yīng)該輸出 Animals are equal: false,對吧?事實上不是,運行發(fā)現(xiàn)只輸出false 。操作符的優(yōu)先級體現(xiàn)出來,事實上是
System.out.println(("Animals are equal: " + pig) == dog);
有一種方法能避免這種困難:但使用字符拼接操作符的時候,總是給重要操作數(shù)加上括號:System.out.println("Animals are equal: " + (pig == dog));
辯證的說,這樣還是有問題。Your code should rarely, if ever, depend on the interning of string constants。Interning 只是用來減少虛擬機(jī)內(nèi)存的,不是用來當(dāng)作程序員工具的。由于字符串intern 失敗帶來的bug非常難以檢測。對比兩個對象引用的時候,應(yīng)該用equals方法而不是==操作符除非你是想比較對象identity而不是值。所以我們的例子應(yīng)該這樣:System.out.println("Animals are equal: " + pig.equals(dog));
14 轉(zhuǎn)義符
下面程序使用 Unicode escapes:用他們的十六進(jìn)制數(shù)字碼表示Unicode 字符。
// \u0022 is the Unicode escape for double quote (")
System.out.println("a\u0022.length() + \u0022b".length());
Java provides no special treatment for Unicode escapes within string literals。編譯器在把Unicode escapes程序解析為字符串之前,先變?yōu)榱怂麄儽硎镜淖址???梢允褂?span id="wmqeeuq" class="docEmphasis">escape sequences:即用\"表示雙引號 ,例System.out.println("a\".length() + \"b".length());
還有很多escape sequences : single quote (\'), linefeed (\n), tab (\t), and backslash (\\). escape sequences 等程序先解析為符號之后才處理。ASCII是Unicode的子集。ASCII是最小的字符集,只有128個字符,Unicode有 65,000的字符。Unicode escape 能被用來把Unicode 字符插入只能使用ASCII字符的程序中。一個 Unicode escape意味著和他代表的字符完全相同的東西。但程序員用源文件的字符集不能插入一些字符的時候,可以使用 Unicode escape,主要是把非ASCII字符變?yōu)闃?biāo)志符,字符串,注釋等。
總結(jié):在字符串和字符文字中,用escape sequences不用Unicode escapes 。不要用Unicode escapes 表示ASCII字符。在字符串和字符文字中,用escape sequences;在外面的話直接把ASCII 字符插入源文件。
15
Unicode escapes must be well formed, even if they appear in comments. 下面這個例子編譯出錯
/**
* Generated by the IBM IDL-to-Java compiler, version 1.0
* from F:\TestRoot\apps\a1\units\include\PolicyHome.idl
* Wednesday, June 17, 1998 6:44:40 o'clock AM GMT+00:00
*/
工具在把Windows 文件名放入注釋之前,必須把\去掉。
總之,\u不要出現(xiàn)在有效Unicode escape范圍之外,即使注釋也不行。特別是自動產(chǎn)生代碼的時候。
16
line separator 用來表示分割文本行的字符,每個平臺的line separator 不一樣。Windows 上,CR character (carriage return) followed by the LF character (linefeed)。UNIX上只有LF字符(經(jīng)常被較為newline character)。下面把這些字符傳給println
// Note: \u000A is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
結(jié)果編譯錯誤!仍然是由于注釋中Unicode escape,編譯器再拋棄注釋內(nèi)容和空格之前就把Unicode escapes 轉(zhuǎn)換為字符。\u000A 代表linefeed character,因此程序最后轉(zhuǎn)換為
// Note:
is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
最簡單的修改方法是去掉 Unicode escape ,但更好的方法是用escape sequence 初始化c,而不是用十六進(jìn)制整數(shù),排除注釋的需要
char c = '\n';
System.out.println(c);
這樣改后程序還是有問題,有平臺依賴。在某些平臺,如UNIX,他將輸出兩行完整的分割符;另外一些平臺,如Windows,則不會。雖然肉眼看起來一樣,但如果存在一個文件或管道里供其他程序處理話 是很容易出問題的。 如果打算輸出兩行空白,應(yīng)該調(diào)用println兩次。Java 5,你可以使用printf帶上格式"%n%n"來代替println,每一個出現(xiàn)的 %n是printf打印出平臺相應(yīng)的行分隔符。
道理:非必需盡量不用Unicode escapes
17
Unicode escapes are essential when you need to insert characters that can't be represented in any other way into your program. Avoid them in all other cases.Unicode escapes 減少程序的清晰性,增加bug的出現(xiàn)。對語言設(shè)計者來說,應(yīng)該使Unicode escapes表示ASCII字符非法。
18 字符集
byte bytes[] = new byte[256];
for(int i = 0; i < 256; i++)
bytes[i] = (byte)i;
String str = new String(bytes);
for(int i = 0, n = str.length(); i < n; i++)
System.out.print((int)str.charAt(i) + " ");
在不同的機(jī)器上運行,結(jié)果完全不一樣。原因是String(byte[])構(gòu)造器。規(guī)范說“用平臺默認(rèn)的字符集給指定的byte數(shù)組解碼創(chuàng)建一個新字符串。新字符串的長度是字符集的功能,因此可能和byte數(shù)組的長度不一樣。當(dāng)給定的字節(jié)在默認(rèn)字符集中無效時,該構(gòu)造器行為不確定”。什么是字符集?技術(shù)上說,他是“the combination of a coded character set and a character-encoding scheme”。換句話說,是一堆字符,表達(dá)字符的數(shù)字編碼,以及一序列字符編碼和字節(jié)互相轉(zhuǎn)換的方法。轉(zhuǎn)換方案在不同的字符集中差異巨大。一些字符和字節(jié)一一對應(yīng);大多數(shù)不這樣。只有默認(rèn)字符集是 ISO-8859-1(更多被稱為Latin-1 )的時候上面的程序才會輸出0到255的整數(shù)。J2SE運行環(huán)境的默認(rèn)編碼是由底層操作系統(tǒng)和區(qū)域決定的。在早期的JAVA版本讀取系統(tǒng)屬性 "file.encoding"來得到JRE默認(rèn)編碼,JAVA 5后以后的版本,可使用 java.nio.charset.Charset.defaultCharset()方法。幸運的是,你不是非得要面對默認(rèn)字符集的變化多端。char序列和byte序列互相轉(zhuǎn)換的時候,你可以并且大多數(shù)時候應(yīng)當(dāng)顯式的指定字符集。一個以字符集名字和byte數(shù)組為參數(shù)的String構(gòu)造器可完成此任務(wù)。用下面方法,上面程序就不會受默認(rèn)字符集的影響了:String str = new String(bytes, "ISO-8859-1"); 該構(gòu)造器拋出UnsupportedEncodingException,你必須捕獲,當(dāng)更好的方法是聲明一個main方法來拋出他否則不能編譯通過。事實上,上面的程序不會拋出異常。因為Charset 的規(guī)范指明任何JAVA平臺的實現(xiàn)必須支持某些字符集,ISO-8859-1是其中之一。
教訓(xùn):每次從byte序列轉(zhuǎn)換為String,不管顯示或隱式都在使用一種字符集。如果想程序不出意外,每次使用時顯示指定一種字符集。
誤:public static boolean isOdd(int i) { return i % 2 == 1; } //沒有考慮到負(fù)奇數(shù)的情況
正:return i % 2 != 0; 更好的性能:return (i & 1) != 0;
總結(jié):求余操作需要考慮符號!
2 浮點數(shù)計算
public static void main(String args[]) { System.out.println(2.00 - 1.10); } //天真以為得到0.90
如果熟悉Double.toString 的文檔,估計會覺得 double 會轉(zhuǎn)為string,程序會打印出足夠區(qū)分double值的小數(shù)部分,小數(shù)點前或后面至少一位。這樣說來應(yīng)該是0.9,可惜運行程序發(fā)現(xiàn)是 0.8999999999999999。問題是數(shù)字1.1不能被double準(zhǔn)確表示!只能用最接近的double值表示。遺憾的是結(jié)果不是最接近0.9的double值。更普遍的看這問題是:不是所有的十進(jìn)制數(shù)都能用二進(jìn)制浮點數(shù)準(zhǔn)確的表示 。如果用jdk5或以后版本,你可能會使用printf來準(zhǔn)確設(shè)置:
// Poor solution - still uses binary floating-point!
System.out.printf("%.2f%n", 2.00 - 1.10);
現(xiàn)在打印出來是正確的了,但治標(biāo)不治本:它仍然使用的是double運算(二進(jìn)制浮點),浮點計算在大范圍內(nèi)提供近似計算,但不總是產(chǎn)生準(zhǔn)確的結(jié)果。二進(jìn)制浮點數(shù)特別不適合金融計算,因為他不可能表示0.1——或任何10的負(fù)冪——exactly as a finite-length binary fraction。
一種解決辦法是使用基本類型,比如int long,然后擴(kuò)大操作數(shù)倍數(shù)做計算。如果用這種流程,確?;绢愋妥銐虼髞肀硎灸闼心阌玫降臄?shù)據(jù),這個例子中,int足夠了System.out.println((200 - 110) + " cents");
另一種辦法使用BigDecimal,他進(jìn)行準(zhǔn)確的十進(jìn)制計算,他還能通過JDBC和SQL的DECIMAL類型合作。有一個箴言:總是使用BigDecimal(String)構(gòu)造器,絕不使用BigDecimal(double).后面這個構(gòu)造函數(shù)用參數(shù)的準(zhǔn)確值創(chuàng)建一個實例:new BigDecimal(.1)返回一個BigDecimal表示0.1000000000000000055511151231257827021181583404541015625。正確使用會得到預(yù)期結(jié)果0.90: System.out.println(new BigDecimal("2.00"). subtract(new BigDecimal("1.10"))); 這個例子不是特別漂亮,java沒有給BigDecimal提供語言學(xué)上的支持,BigDecimal也可能比使用基本類型(對大量使用十進(jìn)制計算的程序比較有用)更慢,大多數(shù)情況沒有這個需要。
總結(jié):但需要準(zhǔn)確答案的時候,避免使用float and double;金融計算,使用int, long, or BigDecimal。對語言設(shè)計者來說,提供十進(jìn)制計算的語言支持。一個方法是給操作符重載提供有限的支持,
3 長整型除法
被除數(shù)表示一天的微秒數(shù),除數(shù)表示一天的毫秒數(shù):
public static void main(String[] args) {
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
}
你在想程序應(yīng)該輸出1000,很不幸輸出的是5!問題出在計算MICROS_PER_DAY 時溢出了,雖然結(jié)果是滿足long的,但不滿足int。這個計算過程全部是按int 計算的,計算完之后才轉(zhuǎn)為long。因此很明顯計算過程中溢出。為什么會用int計算?因為因子都是int型的,Java沒有 target typing特性(就是根據(jù)結(jié)果的類型來確定計算過程所用類型)。解決這個問題很簡單,把第一個因子設(shè)置為long,這樣會強(qiáng)制所有以后的計算都用long進(jìn)行。雖然很多地方都不需要這么做,但這是一個好習(xí)慣。
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
我們得到一個教訓(xùn):和大數(shù)據(jù)打交道的時候,小心溢出!一個變量能裝得下結(jié)果,并不代表計算過程中會確保得到正確類型。
4 小學(xué)生都知道的事情
System.out.println(12345 + 5432l); // 毫無疑問的66666? 看仔細(xì)了!輸出17777
教訓(xùn):使用long的時候用大寫的L,絕不用小寫的l,類似的避免用l作為變量名。很難看出輸出的是1還是l
// Bad code - uses el (l) as a variable name
List<String> l = new ArrayList<String>();
l.add("Foo");
System.out.println(1);
5 十六進(jìn)制的快樂
System.out.println(Long.toHexString(0x100000000L + 0xcafebabe)); //輸出cafebabe,最左邊的1丟了!
十進(jìn)制有一個十六或八進(jìn)制都沒有的優(yōu)點:數(shù)字都是正的,想表達(dá)負(fù)數(shù)需要一個負(fù)號。這樣的話寫十進(jìn)制的int或long,不管正負(fù)都很方便。十六或八進(jìn)制就不這樣了,必須由高位來決定正負(fù)。這個例子中,0xcafebabe 是一個int常量,最高位是1,因此是負(fù)數(shù)=十進(jìn)制 -889275714。這里還有一個混合類型計算的額外操作:左邊操作數(shù)是long,右邊是int,計算時Java通過widening primitive conversion 把int變?yōu)閘ong,再加這兩個long。因為int是有符號整型,轉(zhuǎn)變執(zhí)行了一個符號擴(kuò)展:把負(fù)的int值提升為數(shù)值相等的long值。右邊的0xcafebabe被提升為long值 0xffffffffcafebabeL,再加到左邊0x100000000L上。當(dāng)被看作int型的時候,0xcafebabe擴(kuò)展出來的高32位是-1,而左邊操作數(shù)高32位是1,相加之后為0,這解釋了為什么最高位的1丟失。解決方法是把右邊的操作數(shù)也寫上long,這樣就避免了符號擴(kuò)展的破壞。
System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));
教訓(xùn):考慮十六或八進(jìn)制自身帶正負(fù),混合類型計算讓人迷惑。為避免出錯,最好不要使用混合類型計算。對語言設(shè)計者來說,考慮支持無符號整數(shù)類型來去掉符號擴(kuò)展的可能。有人爭論十六或八進(jìn)制負(fù)數(shù)應(yīng)該被禁止,但對于程序員來說非常不好,他們經(jīng)常使用十六進(jìn)制來表示符號沒有意義的數(shù)值。
6 多重映射
System.out.println((int) (char) (byte) -1);
以int 類型的-1開始,映射到byte,到char,最后返回int。第一次32位變到8位,到16位,最后回到32位。最后發(fā)現(xiàn)值并沒有回到原始!輸出65535
問題來自映射時的符號擴(kuò)展問題。int值-1的所有32位都是1,轉(zhuǎn)為8位byte很直觀,只留下低八位就行,仍然是-1.轉(zhuǎn)char的時候,就要小心了,byte是有符號的,char無符號。通常有可能保留數(shù)值的同時把一個整型轉(zhuǎn)到更“寬”的類型,但不可能用char來表示一個負(fù)的byte值。Therefore, the conversion from byte to char is not considered a widening primitive conversion [JLS 5.1.2], but a widening and narrowing primitive conversion [JLS 5.1.4]: The byte is converted to an int and the int to a char??雌饋磔^復(fù)雜,但有一個簡單規(guī)則描述窄變寬轉(zhuǎn)換時的符號擴(kuò)展:原始值有符號就做符號擴(kuò)展;不管轉(zhuǎn)換成什么類型,char只做零擴(kuò)展。因為byte是有符號的,byte -1轉(zhuǎn)成char會有符號擴(kuò)展。結(jié)果是全1的16位,也就是 216 – 1或65,535。char到int寬擴(kuò)展,規(guī)則告訴我們做零擴(kuò)展。int類型的結(jié)果是65535。雖然規(guī)則簡單,但最好不要寫依賴這規(guī)則的程序。如果你是寬轉(zhuǎn)換到char,或從char轉(zhuǎn)換(char總是無符號整數(shù)),最好顯式說明。
如果從char類型的c寬轉(zhuǎn)換,并且不想符號擴(kuò)展,雖然不需要,但為了清晰可以這樣:
int i = c & 0xffff;
還可以寫注釋:
int i = c; // Sign extension is not performed
如果從char類型的c寬轉(zhuǎn)換,并且想符號擴(kuò)展,強(qiáng)制char到short(寬度一樣但有符號)
int i = (short) c; // Cast causes sign extension
byte到char,不要符號,必須用位屏蔽抑制他,這是慣例不用注釋(0xff這種0x開頭的默認(rèn)是int類型的?)
char c = (char) (b & 0xff);
byte to a char ,要符號,寫注釋
char c = (char) b; // Sign extension is performed
這一課很簡單:如果你不能清晰看出程序在干什么,他可能就沒有按你希望的在運行。拼命尋求清晰,雖然整數(shù)轉(zhuǎn)換的符號擴(kuò)展規(guī)則簡單,但大多程序員不知道。如果你的程序依賴于他,讓你的意圖明顯。
7 交換美味
在一個簡單表達(dá)式中,不要對一個變量賦值超過一次。更普遍的說,不要用“聰明”的程序技巧。
8 Dos Equis
char x = 'X';
int i = 0;
System.out.print(true ? x : 0);
System.out.print(false ? i : x);
輸出XX?可惜輸出的是X88。注意第二三個操作數(shù)類型不一樣,第5點說過,混合類型計算讓人迷惑!條件表達(dá)式中是最明顯的地方。雖然覺得兩個表達(dá)式結(jié)果應(yīng)該相同,畢竟他們類型相似,只是位置相反而已,但結(jié)果并不是這樣。
決定條件表達(dá)式結(jié)果類型的規(guī)則很多,但有三個關(guān)鍵點:
1 如果第二三個操作數(shù)類型一樣,表達(dá)式也是這個類型,這樣就避免了混合類型計算。
2 3 復(fù)雜略過 總之第一個表達(dá)式是調(diào)用了PrintStream.print(char),第二個是PrintStream.print(int) 造成結(jié)果不同
總結(jié):最好在條件表達(dá)式中第二三個操作數(shù)用同一種類型
9
x += i; // 等同于x = x + i;?
compound assignment expressions automatically cast the result of the computation they perform to the type of the variable on their left-hand side// 暗含映射
例如 short x = 0;int i = 123456;
x += i; // –7,616,int值123456太大,short裝不下,高位兩個字節(jié)被去掉
x = x + i; // 編譯錯誤- "possible loss of precision"
為避免危險,不要在byte, short, or char上面用復(fù)合賦值符。當(dāng)在int上用時,確保右邊不是long, float, or double類型。在float上用,確保右邊不是double。
10 復(fù)合賦值符需要兩邊操作數(shù)都為基本類型或boxed primitives,如int ,Integer。有一例外:+= 左邊為String的話,允許右邊為任意類型。這時做的是字符串拼接操作。
Object x = "Buy ";
String i = "Effective Java!";
x = x + i; //x+i 為String,和Object兼容,因此表達(dá)式正確
x += i; //非法左邊不是String
注意返回類型:
What Is JDBC ?
JDBC 是java編程中一系列允許簡單連接到很多數(shù)據(jù)庫(特別是關(guān)系型數(shù)據(jù)庫)編程APIs . In Java 2 Platform, Standard Edition (J2SE) 5.0,
JDBC API 由兩個包定義:
java.sql :提供java訪問處理貯存在數(shù)據(jù)源(特別是關(guān)系型數(shù)據(jù)庫)中的數(shù)據(jù),有最基礎(chǔ)常用的對象如Connection, ResultSet, Statement, and PreparedStatement。這個包j2se 和j2ee平臺都可使用。
javax.sql:提供java訪問處理服務(wù)器端數(shù)據(jù)源。這個包給j2ee提供服務(wù),如DataSource 和RowSet。
ODBC bridge是以O(shè)DBC標(biāo)準(zhǔn) C API 方式實現(xiàn)JDBC 的庫。
簡而言之,JDBC是一個和database-independent 的訪問數(shù)據(jù)庫的API。
DriverManager是唯一可以創(chuàng)建數(shù)據(jù)庫連接的類。DriverManager根據(jù)各個廠商(如Oracle, MySQL, and Sybase)提供的驅(qū)動創(chuàng)建數(shù)據(jù)庫。
What Is ODBC ?
Open Database Connectivity (ODBC) 是一種來自微軟的編程接口,他為Windows應(yīng)用程序訪問網(wǎng)絡(luò)上數(shù)據(jù)庫提供了通用語言。 ODBC is a C-based interface
to SQL-based database systems. It provides a consistent interface for communicating with a database and for accessing database metadata (information about the database system vendor and how the tables, views, and data are stored).ODBC作為標(biāo)準(zhǔn)出現(xiàn)。廠商為各自的DBMS提供了各種的驅(qū)動或bridges。從java客戶端訪問ODBC-based數(shù)據(jù)庫,可以使用JDBC-ODBC bridge,因此可以使用JDBC-ODBC bridge訪問支持ODBC的數(shù)據(jù)庫,比如Microsoft Access。微軟為他的操作系統(tǒng)提供ODBC driver manager。ODBC driver manager協(xié)調(diào)訪問ODBC驅(qū)動和對應(yīng)的數(shù)據(jù)源。
問題:如果用c++寫數(shù)據(jù)庫客戶端,你不得不在另一平臺重新寫客戶端;PC版的不能在Macintosh上運行。兩個原因:1.c++不是跨平臺的,很多東西沒有特別指定(如int型用多少位表示)2 更重要的是,想網(wǎng)絡(luò)訪問,GUI框架庫等在各個平臺不同。ODBC的另一個問題是,接口復(fù)雜學(xué)習(xí)時間長。JDBC去除了這些問題,為訪問關(guān)系數(shù)據(jù)庫引入平臺無關(guān)的解決方案。因為性能問題和缺少事務(wù)支持, JDBC-ODBC bridge 驅(qū)動只適合實驗用或沒有其他可選方法。
What Is a JDBC-ODBC Bridge?
簡而言之,JDBC-ODBC bridge通過大多數(shù)ODBC驅(qū)動來提供JDBC訪問。它是一個把JDBC操作轉(zhuǎn)換為ODBC操作的JDBC驅(qū)動。(ODBC操作是由 C-based libraries實現(xiàn)的——ODBC功能仍然在二進(jìn)制代碼庫中;如果數(shù)據(jù)庫或硬件平臺更換,需要替換ODBC庫)。brige作為sun.jdbc.odbc包實現(xiàn),包含一個native library用來訪問ODBC。sun.jdbc.odbc包在/jre/lib/rt.jar中,包含一個sun.jdbc.odbc.JdbcOdbcDriver類,用來JDBC驅(qū)動。注意,JDBC-ODBC bridge是一種“萬能”的方式,因此可能比一些特別設(shè)計的JDBC驅(qū)動慢。
SQL is a Data Manipulation Language (DML—影響數(shù)據(jù)庫對象內(nèi)容的命令集) and a Data Definition Language (DDL—影響數(shù)據(jù)庫對象結(jié)構(gòu)的命令集).SQL also 提供控制事務(wù)命令 (such as commit and rollback)
連接jdbc的過程參考JDBC加載分析 。總之JDBC驅(qū)動的作用是提供各種數(shù)據(jù)庫的具體實現(xiàn)(實現(xiàn)了java.sql.Driver接口),隱藏具體數(shù)據(jù)庫的細(xì)節(jié)(每個數(shù)據(jù)庫廠商可能會為同一個數(shù)據(jù)庫提供不止一個驅(qū)動,這些效率,價格/性能會有不同)。
在fianlly中立刻關(guān)閉/釋放 JDBC資源(such as the ResultSet, Statement, PreparedStatement, and Connection objects),而不是等他們自己關(guān)閉,會改進(jìn)應(yīng)用程序的性能。寫一個工具類釋放這些資源是一個好辦法。
JDBC API主要用來傳SQL statement給數(shù)據(jù)庫,但也能讀寫表格式數(shù)據(jù)源的數(shù)據(jù),這種來自javax.sql.RowSet組接口的讀寫能力可以被定制去使用更新spreadsheet,flat file 類似表格式數(shù)據(jù)源的數(shù)據(jù)。
JDBC有四種類型的驅(qū)動連接數(shù)據(jù)庫。
異常:SQLException:有g(shù)etNextException()可以鏈接一系列異常,還有很多方法可以展示額外的錯誤/異常信息。SQLWarning:SQLException的子類,表示非致命可忽略BatchUpdateException:批量更新時出現(xiàn)的錯誤,除了SQLException提供的信息,還有錯誤發(fā)生前已成功執(zhí)行多少條數(shù)據(jù)DataTruncation:意外truancate 數(shù)據(jù)拋出。
Java Naming and Directory Interface (JNDI) is an API that supports accessing naming and directory services in Java programs.
命名服務(wù)目的:把命名和對象聯(lián)系起來,提供用命名訪問對象的方法。
目錄服務(wù):允許屬性和對象聯(lián)系,比如用戶對象的email地址屬性,(命名服務(wù)不提供),因此能利用目錄服務(wù)訪問對象屬性或以屬性為基礎(chǔ)查找對象。
HTTP在TCP/IP的頂層,他是一種有web特性的網(wǎng)絡(luò)協(xié)議。HTTP會話結(jié)構(gòu)是一種簡單的請求/響應(yīng)序列;瀏覽器請求,服務(wù)器響應(yīng) 。HTTP 響應(yīng)可以 包含HTML,HTTP在響應(yīng)內(nèi)容(服務(wù)器返回的任何東西)之上添加頭信息。瀏覽器利用頭信息來幫助處理html頁面。把hml內(nèi)容看作粘貼在HTTP響應(yīng)中的數(shù)據(jù)。HTTP請求中有幾個方法,最常用的是POST和GET(區(qū)別)。HTTP響應(yīng)中包含狀態(tài)代碼(如404),內(nèi)容類型(也稱為MIME類型,他告訴瀏覽器將會收到什么類型的數(shù)據(jù)以便處理,比如展示圖片,提供html),響應(yīng)的真實內(nèi)容(html,圖片等)。
If a class implements two or more interfaces that call for methods with identical signatures,we need only implement one such method in the implementing class—that method will do “double duty” in satisfying both interfaces’ implementation requirements as far as the compiler is concerned.
定義 屬性,方法參數(shù),返回類型的時候盡可能使用接口,客戶端代碼調(diào)用這樣的類會更加靈活。
List和Set都是Collection接口的子類,使用Collection可以更通用。
如果一定要自己創(chuàng)建集合類而且不通過擴(kuò)展存在的ArrayList等的話,至少實現(xiàn)Collection接口,這樣才能在使用Collection的環(huán)境使用。
靜態(tài)方法不能為abstract,不能調(diào)用非靜態(tài)的屬性或方法。我們經(jīng)常利用靜態(tài)方法,屬性實現(xiàn)一些“工具類”,比如java.lang中的Math.
接口不允許定義變量,除了定義public static final 變量來作為全局常量。但是final類型的變量必須顯示初始化,且初始化的方法必須是在申明時或者在構(gòu)造方法中直接賦值,而不能通過調(diào)用函數(shù)賦值。
j2se 5引入 :import static Administrator.*; 這樣在代碼中可以直接使用Administrator類的靜態(tài)變量。
查詢了下關(guān)于是否用final限定方法參數(shù)以及局部變量的問題,有爭議(http://stackoverflow.com/questions/316352?sort=votes#sort-top),類似習(xí)慣問題,不過對傳入的參數(shù)重新賦值不是好習(xí)慣!否則在方法中使用該參數(shù)的時候你會考慮前面的代碼是否對參數(shù)處理過,還有可能失誤的進(jìn)行了賦值。傾向于方法參數(shù)使用final,局部變量不使用。折中的辦法是設(shè)置eclipse的重賦值警告。
通常有兩種方法可以擴(kuò)展collection 來滿足一些需要:繼承某種集合類型和封裝某種集合類型。第一種的優(yōu)點是初始化的時候在內(nèi)存中只產(chǎn)生一個對象,這是繼承特性決定的。后者的優(yōu)點是我們可以方便控制被封裝集合的各種屬性。
Whenever possible, it’s desirable to bury implementation details inside of a class rather than exposing client code to such details。例:
法1:
public class Student {
private String name;
private String studentId;
private ArrayList<TranscriptEntry> transcript; //成績報告單
public void addTranscriptEntry(TranscriptEntry te) { // 操作transcript達(dá)到記錄成績
// Store the TranscriptEntry in our ArrayList.
transcript.add(te);
}
}
客戶端調(diào)用代碼:
Student s = new Student("1234567", "James Huddleston");
Course c = new Course("LANG 800", "Advanced Language Studies");
TranscriptEntry te = new TranscriptEntry(c, "Fall 2006", "B+");
s.addTranscriptEntry(te);
法2:
建立新對象,封裝一個ArrayList:
public class Transcript {
private ArrayList<TranscriptEntry> transcriptEntries;
public void courseCompleted(Course c, String semester, String grade) {
// Instantiate and insert a brand-new TranscriptEntry object into the
// ArrayList - details hidden away!
transcriptEntries.add(new TranscriptEntry(c, semester, grade);
}
}
public class Student {
private String name;
private String studentId;
// This used to be declared as an ArrayList.
private Transcript transcript;
// etc.
}
客戶端代碼:
s.courseCompleted(c, "Spring 2006", "A+");
第二種方法使Student處理更少的細(xì)節(jié),不用管transcripts怎么表達(dá),看不到TranscriptEntry的存在??蛻舳舜a更簡單。