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

// The default delimiter is the comma, but this
















































































31
while (i != 0)
i >>>= 1; //無(wú)符號(hào)右移,不管正負(fù)左邊都是補(bǔ)0
為了表達(dá)式合法,這里的i必須是整型(byte, char, short, int, or long)。謎題的關(guān)鍵在于>>>= 是一個(gè)復(fù)合賦值操作符,不幸的是復(fù)合賦值操作符會(huì)默默的做narrowing primitive conversions,即從一個(gè)數(shù)據(jù)類型轉(zhuǎn)換為一個(gè)更小的數(shù)據(jù)類型。Narrowing primitive conversions can lose information about the magnitude or precision of numeric values。為了使問題更具體,假設(shè)這樣定義:
short i = -1; 因?yàn)槌跏贾礽 ((short)0xffff) 非零,循環(huán)執(zhí)行。第一步位移會(huì)把i提升為int。short, byte, or char類型的操作數(shù)都會(huì)做這樣的操作。這是widening primitive conversion,沒有信息丟失。這種提升有符號(hào)擴(kuò)展,因此結(jié)果是int值0xffffffff。無(wú)符號(hào)右移一位產(chǎn)生int值0x7fffffff。為了把int值存回short變量,Java執(zhí)行了可怕的narrowing primitive conversion,即簡(jiǎn)單去掉高十六位。這樣又變回了(short)0xffff。如果定義類似short or byte型的負(fù)數(shù),都會(huì)得到類似結(jié)果。你如果定義的是char話,則不會(huì)無(wú)限循環(huán),因?yàn)閏har值非負(fù),位移之前的寬擴(kuò)展不會(huì)做符號(hào)擴(kuò)展。
總結(jié):不要在short, byte, or char變量上使用復(fù)合賦值操作符。這種表達(dá)式進(jìn)行混合類型計(jì)算,非常容易混淆。更糟糕的是隱含的窄映射會(huì)丟掉信息。
32
while (i <= j && j <= i && i != j) {}
i <= j and j <= i, surely i must equal j?對(duì)于實(shí)數(shù)來(lái)說(shuō)是這樣的。他非常重要,有個(gè)名稱: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ù)比較符號(hào)(<, <=, >, 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。具體點(diǎn),讓上面進(jìn)入無(wú)限循環(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比較的是對(duì)象引用,也為true。很奇怪規(guī)范沒有把等號(hào)改為比較值。原因很簡(jiǎn)單:兼容性。當(dāng)一種語(yǔ)言廣泛應(yīng)用的時(shí)候,不能破壞已存在的規(guī)范來(lái)改變程序的行為。System.out.println(new Integer(0) == new Integer(0));總是輸出false,所以必須保留。當(dāng)一個(gè)是boxed numeric 類型,另一個(gè)是基本類型的時(shí)候可以值比較。因?yàn)閖ava 5之前這是非法的,具體點(diǎn):
System.out.println(new Integer(0) == 0); //之前的版本非法,Java 5輸出True
總結(jié):當(dāng)兩邊的操作數(shù)是boxed numeric類型的時(shí)候,數(shù)字比較符和等于符號(hào)是根本不同的:數(shù)字比較符是值比較,等號(hào)比較的是對(duì)象引用。
33
while (i != 0 && i == -i) {}
有負(fù)號(hào)表示i一定是數(shù)字,NaN不行,因?yàn)樗坏扔谌魏螖?shù)。事實(shí)上,沒有實(shí)數(shù)可以出現(xiàn)這種情況。但Java的數(shù)字類型并沒有完美表達(dá)實(shí)數(shù)。浮點(diǎn)數(shù)由一個(gè)符號(hào)位,一個(gè)有效數(shù)字(尾數(shù)),一個(gè)指數(shù)構(gòu)成。浮點(diǎn)數(shù)只有0才會(huì)和自己的負(fù)數(shù)相等,所以i肯定是整數(shù)。有符號(hào)整數(shù)用的是二進(jìn)制補(bǔ)碼計(jì)算:取反加一。補(bǔ)碼的一大優(yōu)勢(shì)是用一個(gè)唯一的數(shù)來(lái)表示0。然而有一個(gè)對(duì)應(yīng)的缺點(diǎn):本來(lái)可表達(dá)偶數(shù)個(gè)值,現(xiàn)在用一個(gè)表達(dá)了0,剩下奇數(shù)個(gè)來(lái)表示正負(fù)數(shù),意味著正數(shù)和負(fù)數(shù)的數(shù)量不一樣。比如int值,他的Integer.MIN_VALUE(-231)。十六進(jìn)制表達(dá)0x8000000。通過(guò)補(bǔ)碼計(jì)算可知他的負(fù)數(shù)仍然不變。對(duì)他取負(fù)數(shù)是溢出了的,不過(guò)Java在整數(shù)計(jì)算中忽略了溢出。
總結(jié):Java使用二進(jìn)制補(bǔ)碼,是不對(duì)稱的。有符號(hào)整數(shù)(int, long, byte, and short) 負(fù)數(shù)值比整數(shù)值多一個(gè)。
34
final int START = 2000000000;
int count = 0;
for (float f = START; f < START + 50; f++)
count++;
System.out.println(count);
注意循環(huán)變量是float?;貞浿i題28 ,明顯f++沒有任何作用。f的初始化值接近Integer.MAX_VALUE,因此需要31位來(lái)準(zhǔn)確表達(dá),但是float類型只提供了24位精度。增加這樣大的一個(gè)float值不會(huì)改變值??雌饋?lái)會(huì)死循環(huán)?運(yùn)行程序會(huì)發(fā)現(xiàn),輸出0。循環(huán)中f和(float)(START + 50)做比較。但int和float比較的時(shí)候,自動(dòng)把int先提示為float。不幸的是三個(gè)會(huì)引起精度丟失的寬基本類型轉(zhuǎn)換的其中之一(另外兩個(gè)是long到float,long到double)。f的初始值巨大,加50再轉(zhuǎn)換為float和直接把f轉(zhuǎn)換為float是一樣的效果,即(float)2000000000 == 2000000050,所以f < START + 50 失敗。只要把float改為int即可修正。
沒有計(jì)算器,你怎么知道 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
下面程序模擬一個(gè)簡(jiǎn)單的時(shí)鐘
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,最簡(jiǎn)單的修改方法是: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);
}
絕不要用空格來(lái)分組;使用括號(hào)來(lái)決定優(yōu)先級(jí)
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
if (b == 0x90)
System.out.print("Joy!");
}
Ox90 超過(guò)了byte的取值范圍-128到127。byte和int比較是一種混合類型比較??紤]個(gè)表達(dá)式((byte)0x90 == 0x90)得到的是false。byte和int做比較的時(shí)候,Java先對(duì)byte進(jìn)行了widening primitive conversion 再比較兩個(gè)int值。因?yàn)閎yte是有符號(hào)類型,轉(zhuǎn)變做了符號(hào)擴(kuò)展,把負(fù)的byte值轉(zhuǎn)換為相應(yīng)的int值。這個(gè)例子中(byte)0x90被轉(zhuǎn)變?yōu)?112,當(dāng)然不等于int值 0x90或者說(shuō)+144。混合比較總讓人迷惑,因?yàn)榭偸菑?qiáng)迫系統(tǒng)去提升一個(gè)操作數(shù)來(lái)和另一種類型匹配。有幾種方式可避免混合比較。可以把int映射為byte,之后比較兩個(gè)byte值:
if (b == (byte)0x90)
System.out.println("Joy!");
另外,可用mask抑制符號(hào)擴(kuò)展,把byte轉(zhuǎn)換為int,之后比較兩個(gè)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!");
}
不幸的是,上面編譯通不過(guò):0x90對(duì)于byte類型來(lái)說(shuō)不是一個(gè)有效值。這樣修改即可:
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)制你為每一個(gè)常量定義適用的類型,避免一種混合類型比較的來(lái)源。
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一樣:在一個(gè)表達(dá)式中不要給同一個(gè)變量賦值超過(guò)一次。
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);
}
看起來(lái)像100,再仔細(xì)看循環(huán)是小于等于,應(yīng)該是101?結(jié)果是程序沒有輸出任何值,陷入一個(gè)死循環(huán)。問題出在Integer.MAX_VALUE,當(dāng)繼續(xù)增加的時(shí)候,他悄悄變?yōu)?tt>Integer.MIN_VALUE。如果你需要循環(huán)int值邊界,最好用long變量做索引:
for (long i = START; i <= END; i++) //輸出101
教訓(xùn)是:ints are not integers。無(wú)論何時(shí)用基本類型,注意邊界值。上溢或下溢會(huì)出現(xiàn)什么情況?一般來(lái)說(shuō)最好用大一點(diǎn)的類型(基本類型是byte, char, short, int, and long)。也可以不用long:
int i = START;
do {
count++;
} while (i++ != END);
考慮到清晰和簡(jiǎn)單,總是用long索引,除了一種特殊情況:如果要遍歷所有int值,這樣用int索引的話會(huì)快兩倍。
一個(gè)循環(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ǔ)碼計(jì)算,在任何有符號(hào)基本類型中(byte, short, int, or long)都是用所有位置1來(lái)表示-1。
int i = 0;
while (-1 << i != 0) //左位移
i++;
System.out.println(i);
int型的-1用0xffffffff 表示。不斷左移,右邊由0補(bǔ)位。移位32次,變?yōu)槿?,跳出循環(huán)打印32?實(shí)際上程序會(huì)死循環(huán)。問題出在-1<<32不等于0而是等于-1,因?yàn)槲?span style="color: #ff0000">移符號(hào)只用右邊操作數(shù)的低五位作為移動(dòng)距離,如果左操作數(shù)是long的話用六位。三個(gè)位移操作符:<<,>>,>>>都是這樣。移動(dòng)距離總是0到31,左邊操作數(shù)是long的話0到63。位移距離用32取模,左邊是long則用64取模。給int值位移32位或給long值位移64位只會(huì)返回本身。所以不可能用位移完全移除一個(gè)數(shù)據(jù)。幸運(yùn)的是,有一個(gè)簡(jiǎn)單的辦法解決這個(gè)問題。保存上一次的位移結(jié)果,每一次迭代多移動(dòng)一位。
int distance = 0;
for (int val = -1; val != 0; val <<= 1)
distance++;
System.out.println(distance);
修改后的程序說(shuō)明了一個(gè)原則:位移距離如果可能的話,用常量。
另外一個(gè)問題,許多程序員認(rèn)為右移一個(gè)負(fù)的移動(dòng)距離,就和左移一樣,反之亦然。事實(shí)上不是這樣,左移是左移,右移就是右移。負(fù)數(shù)距離只留下低五位(long留六位),其余的置0就變?yōu)榱苏龜?shù)距離。比如,左移一個(gè)int值-1的距離,實(shí)際上是左移31位。
28 無(wú)窮的表示
for (int i = start; i <= start + 1; i++) {
}
看起來(lái)循環(huán)兩次就會(huì)結(jié)束,如果這樣定義呢:
int start = Integer.MAX_VALUE - 1;//死循環(huán)
while (i == i + 1) {
}
這個(gè)不可能死循環(huán)?如果i是無(wú)窮呢?Java采用IEEE 754浮點(diǎn)數(shù)算術(shù),用double或float來(lái)表示無(wú)窮。所以可以用任何浮點(diǎn)數(shù)計(jì)算表達(dá)式得出無(wú)窮來(lái)初始化i。比如:double i = 1.0 / 0.0;
更好的是可以利用標(biāo)準(zhǔn)庫(kù)提供的常量:double i = Double.POSITIVE_INFINITY;
事實(shí)上,根本用不著用無(wú)窮初始化i來(lái)引起死循環(huán)。只要足夠大的浮點(diǎn)數(shù)就夠了:double i = 1.0e40;
這是因?yàn)楦↑c(diǎn)數(shù)越大,他的值和下一個(gè)數(shù)的值距離也就越大。distribution of floating-point values is a consequence of their representation with a fixed number of significant bits. 給足夠大的浮點(diǎn)數(shù)加一不會(huì)改變值,因?yàn)樗荒芴畛溥@個(gè)數(shù)和下一個(gè)數(shù)之間的距離。浮點(diǎn)數(shù)操作返回最接近準(zhǔn)確數(shù)學(xué)結(jié)果的浮點(diǎn)值。一旦兩個(gè)相鄰浮點(diǎn)值之間的距離大于2,加1就不會(huì)有效果。float類型來(lái)說(shuō),超過(guò)225(或33,554,432)再加1就無(wú)效;對(duì)double來(lái)說(shuō)超過(guò)254(接近1.8 x 1016)再加1就無(wú)效。
相鄰浮點(diǎn)數(shù)之間的距離稱為ulp(unit in the last place的縮寫)。在Java 5里 Math.ulp方法被引入來(lái)計(jì)算float或double值的ulp。
總結(jié):不可能用float或double來(lái)表示無(wú)窮。另外,在一個(gè)大的浮點(diǎn)數(shù)上加一個(gè)小的浮點(diǎn)數(shù),值不會(huì)改變。有點(diǎn)不合常理,實(shí)數(shù)并不是這樣。記住二進(jìn)制浮點(diǎn)數(shù)計(jì)算只是近似于實(shí)數(shù)計(jì)算。
29 NaN
while (i != i) { }
IEEE 754 浮點(diǎn)數(shù)計(jì)算保留了一個(gè)特殊值來(lái)來(lái)表示不是數(shù)字的數(shù)量。NaN是浮點(diǎn)計(jì)算不能很好定義的數(shù),比如0.0 / 0.0。規(guī)范定義NaN不等于任何數(shù)包括自己。因此double i = 0.0 / 0.0; 可讓開始的等式不成立。也有標(biāo)準(zhǔn)庫(kù)定義的常量:double i = Double.NaN; 如果一個(gè)或多個(gè)操作數(shù)為NaN,那么浮點(diǎn)數(shù)計(jì)算就會(huì)等于NaN。
總結(jié):float和doule存在特殊的值NaN,小心處理。
30
while (i != i + 0) { } 這一次不能使用float或者double。
+操作符除了數(shù)字,就只能處理String。+操作符會(huì)被重載:對(duì)于String類型,他做的是連接操作。如果操作數(shù)有非String類型,會(huì)先做轉(zhuǎn)換變?yōu)镾tring之后再做連接。i一般用作數(shù)字,要是對(duì)String型變量這么命名容易引起誤解。
總結(jié):操作符重載非常容易誤導(dǎo)人。好的變量名,方法名,類名和好的注釋對(duì)于程序的可讀性一樣重要。
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 ";
}
編譯出錯(cuò),塊注釋不能嵌套,在注釋內(nèi)的文本都不會(huì)被特殊對(duì)待。
// 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;
}
這是語(yǔ)言規(guī)范推薦的一種條件編譯的技術(shù),但不是非常適合注釋代碼。除非包含的語(yǔ)句都是有效的表達(dá)式,否則這種條件編譯不能用作注釋。最好的注釋代碼方法是用單行注釋。
20 反斜杠
Me.class.getName() 返回的是Me類的完整名,如"com.javapuzzlers.Me"。
System.out.println( Me.class.getName().replaceAll(".", "/") + ".class");
應(yīng)該得到com/javapuzzlers/Me.class?不對(duì)。問題出在String.replaceAll把正則表達(dá)式作為第一個(gè)參數(shù),而不是字符。正則表達(dá)是“.”表示配對(duì)任何單獨(dú)的字符,所以類名的每一個(gè)字符都被斜線替代。為了只匹配句號(hào),必須用反斜線(\)轉(zhuǎn)義。因?yàn)榉葱本€在字符串中有特殊意義——它是escape sequence的開始——反斜線自身也必須用一個(gè)反斜線轉(zhuǎn)義。
正確:System.out.println( Me.class.getName().replaceAll("\\.", "/") + ".class");
為了解決這類問題,java 5提供了一個(gè)新的靜態(tài)方法java.util.regex.Pattern.quote。用一個(gè)字符串作為參數(shù),增加任何需要的轉(zhuǎn)義,返回一個(gè)和輸入字符串完全匹配的正則表達(dá)式字符串:
System.out.println(Me.class.getName().replaceAll(Pattern.quote("."), "/") + ".class");
這個(gè)程序的另外一問題就是依賴于平臺(tái)。不是所有的文件系統(tǒng)都是用斜線來(lái)組織文件。為了在你運(yùn)行的平臺(tái)取得正確的文件名,你必須使用正確的平臺(tái)分隔符來(lái)替換斜線。
21
System.out.println(MeToo.class.getName().
replaceAll("\\.", File.separator) + ".class");
java.io.File.separator 是一個(gè)公共的String 屬性,指定用來(lái)包含平臺(tái)依賴的文件名分隔符。在UNIX上運(yùn)行打印com/javapuzzlers/MeToo.class。然而,在Windows上程序拋出異常:
StringIndexOutOfBoundsException: String index out of range: 1
結(jié)果是String.replaceAll 的第二個(gè)參數(shù)不是普通字符串而是一個(gè)在java.util.regex 規(guī)范中定義的 replacement string,反斜線轉(zhuǎn)義了后面的字符。當(dāng)在Windows上運(yùn)行的時(shí)候,替換字符是一個(gè)單獨(dú)的反斜線,無(wú)效。JAVA 5提供了兩個(gè)新方法來(lái)解決這個(gè)問題,一個(gè)是java.util.regex.Matcher.quoteReplacement,它替換字符串為相應(yīng)的替換字符串:
System.out.println(MeToo.class.getName().replaceAll(
"\\.", Matcher.quoteReplacement(File.separator))+".class");
第二個(gè)方法提供了更好的解決方法。String.replace(CharSequence, CharSequence)和String.replaceAll做同樣的事情,但他把兩個(gè)參數(shù)都作為字符串處理:System.out.println(MeToo.class.getName().replace(".", File.separator) + ".class");
如果用的是java早期版本就沒有簡(jiǎn)單的方法產(chǎn)生替換字符串。完全不用正則表達(dá)式,使用String.replace(char, char)跟容易一些:
System.out.println(MeToo.class.getName().replace('.', File.separatorChar) + ".class");
教訓(xùn):當(dāng)用不熟悉的庫(kù)方法的時(shí)候,小心點(diǎn)。有懷疑的話,查看Javadoc。當(dāng)然正則表達(dá)式也很棘手:他編譯時(shí)可能沒問題運(yùn)行時(shí)卻更容易出錯(cuò)。
22 statement label
認(rèn)真寫注釋,及時(shí)更新。去掉無(wú)用代碼。如果有東西看起來(lái)奇怪不真實(shí),很有可能是錯(cuò)誤的。
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);
}
在一次又一次的運(yùn)行中,以相等的概率打印出Pain,Gain或 Main?答案它總是在打印ain。一共有三個(gè)bug導(dǎo)致這種情況。
一是 Random.nextInt(int) ,看規(guī)范可知這里返回的是0到int值之間的前閉后開區(qū)間的隨機(jī)數(shù)。因此程序中永遠(yuǎn)不會(huì)返回2。這是一個(gè)相當(dāng)常見的問題源,被熟知為“柵欄柱錯(cuò)誤(fencepost error)”。這個(gè)名字來(lái)源于對(duì)下面這個(gè)問題最常見的但卻是錯(cuò)誤的答案,如果你要建造一個(gè)100英尺長(zhǎng)的柵欄,其柵欄柱間隔為10英尺,那么你需要多少根柵欄柱呢?11根或9根都是正確答案,這取決于是否要在柵欄的兩端樹立柵欄柱,但是10根卻是錯(cuò)誤的。要當(dāng)心柵欄柱錯(cuò)誤,每當(dāng)你在處理長(zhǎng)度、范圍或模數(shù)的時(shí)候,都要仔細(xì)確定其端點(diǎn)是否應(yīng)該被包括在內(nèi),并且要確保你的代碼的行為要與其相對(duì)應(yīng)。
第二個(gè)bug是 case沒有配套的break。從5.0版本起,javac提供了-Xlint:fallthrough標(biāo)志,當(dāng)你忘記在一個(gè)case與下一個(gè)case之間添加break語(yǔ)句是,它可以生成警告信息。不要從一個(gè)非空的case向下進(jìn)入了另一個(gè)case。這是一種拙劣的風(fēng)格,因?yàn)樗⒉怀S?,因此?huì)誤導(dǎo)讀者。十次中有九次它都會(huì)包含錯(cuò)誤。如果Java不是模仿C建模的,那么它倒是有可能不需要break。對(duì)語(yǔ)言設(shè)計(jì)者的教訓(xùn)是:應(yīng)該考慮提供一個(gè)結(jié)構(gòu)化的switch語(yǔ)句。
最后一個(gè),也是最微妙的一個(gè)bug是表達(dá)式new StringBuffer(‘M')可能沒有做哪些你希望它做的事情。StringBuffer(char)構(gòu)造器根本不存在。StringBuffer有一個(gè)無(wú)參數(shù)的構(gòu)造器,一個(gè)接受一個(gè)String作為字符串緩沖區(qū)初始內(nèi)容的構(gòu)造器,以及一個(gè)接受一個(gè)int作為緩沖區(qū)初始容量的構(gòu)造器。在本例中,編譯器會(huì)選擇接受int的構(gòu)造器,通過(guò)拓寬原始類型轉(zhuǎn)換把字符數(shù)值'M'轉(zhuǎn)換為一個(gè)int數(shù)值77[JLS 5.1.2]。換句話說(shuō),new StringBuffer(‘M')返回的是一個(gè)具有初始容量77的空的字符串緩沖區(qū)。該程序余下的部分將字符a、i和n添加到了這個(gè)空字符串緩沖區(qū)中,并打印出該字符串緩沖區(qū)那總是ain的內(nèi)容。 為了避免這類問題,不管在什么時(shí)候,都要盡可能使用熟悉的慣用法和API。如果你必須使用不熟悉的API,那么請(qǐng)仔細(xì)閱讀其文檔。在本例中,程序應(yīng)該使用常用的接受一個(gè)String的StringBuffer構(gòu)造器。
System.out.print("H" + "a");System.out.print('H' + 'a'); //貌似輸出HaHa?
最后輸出的是Ha169。'H' 和'a' 都是 char,不是String,+操作符做的是加法操作而不是拼接字符串。編譯器提升兩個(gè)char值到int值,從16位零擴(kuò)展到32位的int。一個(gè)是72另一個(gè)是97。從語(yǔ)義上說(shuō),char值和字符串的相似是非常迷惑的。java語(yǔ)言僅僅把char看做無(wú)符號(hào)的16位基本整數(shù)。庫(kù)不這樣認(rèn)為,他里面有許多方法把char參數(shù)當(dāng)作Unicode字符在處理。怎樣連接字符?可以使用庫(kù)方法,如:
StringBuffer sb = new StringBuffer();
sb.append('H');sb.append('a');
System.out.println(sb); //可行但丑陋
有很多方法避免這種繁瑣,只要一個(gè)操作符是string就能強(qiáng)制+操作符做拼接操作。習(xí)慣用法是在前面加一個(gè)空字符串“”,如:
System.out.print("" + 'H' + 'a');//雖然有用,但還是有點(diǎn)不雅而且容易導(dǎo)致一點(diǎn)困惑
試試:System.out.println("2 + 2 = " + 2+2); 如果用的是java 5還可以使用printf:System.out.printf("%c%c", 'H', 'a');
總結(jié):小心應(yīng)對(duì)字符串拼接操作符。“+”只有至少一個(gè)是String的時(shí)候才做字符串拼接;否則做加法。如果沒有String型,有幾個(gè)選擇:加空字符串;用String.valueOf把第一個(gè)值顯示轉(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ù),很多庫(kù)對(duì)他特殊對(duì)待。比如,把char傳給println 輸出的是Unicode 字符而不是數(shù)字碼。char數(shù)組獲得同樣對(duì)待:char[] 的重載是println輸出數(shù)組中的所有字符,char[]對(duì)String.valueOf 和StringBuffer.append 的重載也類似。但是字符拼接操作符不是像這些方法,他是對(duì)兩邊做字符串轉(zhuǎn)換然后再拼接。對(duì)于對(duì)象引用包括數(shù)組,字符串轉(zhuǎn)換是這樣定義的:如果應(yīng)用是null,轉(zhuǎn)換為字符串"null",否則調(diào)用被引用對(duì)象的toString 無(wú)參數(shù)方法;如果toString 方法返回的是null,還是用“null”字符串。非null的char數(shù)組調(diào)用toString做什么操作?數(shù)組從Object 繼承來(lái)的toString,定義,“返回字符串含有對(duì)象實(shí)例類名,字符'@',對(duì)象的用無(wú)符號(hào)十六進(jìn)制表示的hash碼”。 Class.getName 的說(shuō)明表示對(duì) char[] 該類對(duì)象調(diào)用該方法返回 "[C"。兩個(gè)方法修正,拼接之前可顯示轉(zhuǎn)換數(shù)組為String:
System.out.println(letters + " easy as " + String.valueOf(numbers));
還可以把the System.out.println 分開成兩個(gè)來(lái)利用 char[] 對(duì)println的重載:
System.out.print(letters + " easy as ");System.out.println(numbers);
注意這些修正只是在你正確重載valueOf 和println方法才起效。換句話說(shuō),他們嚴(yán)重依賴編譯時(shí)數(shù)組引用的類型。 下面例子看起來(lái)用來(lái)第二個(gè)修正方法,但還是輸出丑陋字符串。因?yàn)樗{(diào)用了Object對(duì)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[])。一些庫(kù)方法給char數(shù)組提供了像字符串的支持,典型的是給Object提供一個(gè)重載再給char[]提供一個(gè)重載;后一個(gè)才是理想的操作。
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.換句話說(shuō),任何兩個(gè)有相同字符的String類型的常量表達(dá)式是同一個(gè)對(duì)象引用表示的。所以如果用常量表達(dá)式初始化的話,pig和dog會(huì)指向同一個(gè)對(duì)象,但dog沒用常量表達(dá)式。Java語(yǔ)言限制哪些操作可以出現(xiàn)在常量表達(dá)式中,方法調(diào)用是不允許的。因此,程序應(yīng)該輸出 Animals are equal: false,對(duì)吧?事實(shí)上不是,運(yùn)行發(fā)現(xiàn)只輸出false 。操作符的優(yōu)先級(jí)體現(xiàn)出來(lái),事實(shí)上是
System.out.println(("Animals are equal: " + pig) == dog);
有一種方法能避免這種困難:但使用字符拼接操作符的時(shí)候,總是給重要操作數(shù)加上括號(hào):System.out.println("Animals are equal: " + (pig == dog));
辯證的說(shuō),這樣還是有問題。Your code should rarely, if ever, depend on the interning of string constants。Interning 只是用來(lái)減少虛擬機(jī)內(nèi)存的,不是用來(lái)當(dāng)作程序員工具的。由于字符串intern 失敗帶來(lái)的bug非常難以檢測(cè)。對(duì)比兩個(gè)對(duì)象引用的時(shí)候,應(yīng)該用equals方法而不是==操作符除非你是想比較對(duì)象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:即用\"表示雙引號(hào) ,例System.out.println("a\".length() + \"b".length());
還有很多escape sequences : single quote (\'), linefeed (\n), tab (\t), and backslash (\\). escape sequences 等程序先解析為符號(hào)之后才處理。ASCII是Unicode的子集。ASCII是最小的字符集,只有128個(gè)字符,Unicode有 65,000的字符。Unicode escape 能被用來(lái)把Unicode 字符插入只能使用ASCII字符的程序中。一個(gè) Unicode escape意味著和他代表的字符完全相同的東西。但程序員用源文件的字符集不能插入一些字符的時(shí)候,可以使用 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. 下面這個(gè)例子編譯出錯(cuò)
/**
* 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范圍之外,即使注釋也不行。特別是自動(dòng)產(chǎn)生代碼的時(shí)候。
16
line separator 用來(lái)表示分割文本行的字符,每個(gè)平臺(tái)的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é)果編譯錯(cuò)誤!仍然是由于注釋中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);
最簡(jiǎn)單的修改方法是去掉 Unicode escape ,但更好的方法是用escape sequence 初始化c,而不是用十六進(jìn)制整數(shù),排除注釋的需要
char c = '\n';
System.out.println(c);
這樣改后程序還是有問題,有平臺(tái)依賴。在某些平臺(tái),如UNIX,他將輸出兩行完整的分割符;另外一些平臺(tái),如Windows,則不會(huì)。雖然肉眼看起來(lái)一樣,但如果存在一個(gè)文件或管道里供其他程序處理話 是很容易出問題的。 如果打算輸出兩行空白,應(yīng)該調(diào)用println兩次。Java 5,你可以使用printf帶上格式"%n%n"來(lái)代替println,每一個(gè)出現(xiàn)的 %n是printf打印出平臺(tái)相應(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)。對(duì)語(yǔ)言設(shè)計(jì)者來(lái)說(shuō),應(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ī)器上運(yùn)行,結(jié)果完全不一樣。原因是String(byte[])構(gòu)造器。規(guī)范說(shuō)“用平臺(tái)默認(rèn)的字符集給指定的byte數(shù)組解碼創(chuàng)建一個(gè)新字符串。新字符串的長(zhǎng)度是字符集的功能,因此可能和byte數(shù)組的長(zhǎng)度不一樣。當(dāng)給定的字節(jié)在默認(rèn)字符集中無(wú)效時(shí),該構(gòu)造器行為不確定”。什么是字符集?技術(shù)上說(shuō),他是“the combination of a coded character set and a character-encoding scheme”。換句話說(shuō),是一堆字符,表達(dá)字符的數(shù)字編碼,以及一序列字符編碼和字節(jié)互相轉(zhuǎn)換的方法。轉(zhuǎn)換方案在不同的字符集中差異巨大。一些字符和字節(jié)一一對(duì)應(yīng);大多數(shù)不這樣。只有默認(rèn)字符集是 ISO-8859-1(更多被稱為L(zhǎng)atin-1 )的時(shí)候上面的程序才會(huì)輸出0到255的整數(shù)。J2SE運(yùn)行環(huán)境的默認(rèn)編碼是由底層操作系統(tǒng)和區(qū)域決定的。在早期的JAVA版本讀取系統(tǒng)屬性 "file.encoding"來(lái)得到JRE默認(rèn)編碼,JAVA 5后以后的版本,可使用 java.nio.charset.Charset.defaultCharset()方法。幸運(yùn)的是,你不是非得要面對(duì)默認(rèn)字符集的變化多端。char序列和byte序列互相轉(zhuǎn)換的時(shí)候,你可以并且大多數(shù)時(shí)候應(yīng)當(dāng)顯式的指定字符集。一個(gè)以字符集名字和byte數(shù)組為參數(shù)的String構(gòu)造器可完成此任務(wù)。用下面方法,上面程序就不會(huì)受默認(rèn)字符集的影響了:String str = new String(bytes, "ISO-8859-1"); 該構(gòu)造器拋出UnsupportedEncodingException,你必須捕獲,當(dāng)更好的方法是聲明一個(gè)main方法來(lái)拋出他否則不能編譯通過(guò)。事實(shí)上,上面的程序不會(huì)拋出異常。因?yàn)?tt>Charset 的規(guī)范指明任何JAVA平臺(tái)的實(shí)現(xiàn)必須支持某些字符集,ISO-8859-1是其中之一。
教訓(xùn):每次從byte序列轉(zhuǎn)換為String,不管顯示或隱式都在使用一種字符集。如果想程序不出意外,每次使用時(shí)顯示指定一種字符集。
誤:public static boolean isOdd(int i) { return i % 2 == 1; } //沒有考慮到負(fù)奇數(shù)的情況
正:return i % 2 != 0; 更好的性能:return (i & 1) != 0;
總結(jié):求余操作需要考慮符號(hào)!
2 浮點(diǎn)數(shù)計(jì)算
public static void main(String args[]) { System.out.println(2.00 - 1.10); } //天真以為得到0.90
如果熟悉Double.toString 的文檔,估計(jì)會(huì)覺得 double 會(huì)轉(zhuǎn)為string,程序會(huì)打印出足夠區(qū)分double值的小數(shù)部分,小數(shù)點(diǎn)前或后面至少一位。這樣說(shuō)來(lái)應(yīng)該是0.9,可惜運(yùn)行程序發(fā)現(xiàn)是 0.8999999999999999。問題是數(shù)字1.1不能被double準(zhǔn)確表示!只能用最接近的double值表示。遺憾的是結(jié)果不是最接近0.9的double值。更普遍的看這問題是:不是所有的十進(jìn)制數(shù)都能用二進(jìn)制浮點(diǎn)數(shù)準(zhǔn)確的表示 。如果用jdk5或以后版本,你可能會(huì)使用printf來(lái)準(zhǔn)確設(shè)置:
// Poor solution - still uses binary floating-point!
System.out.printf("%.2f%n", 2.00 - 1.10);
現(xiàn)在打印出來(lái)是正確的了,但治標(biāo)不治本:它仍然使用的是double運(yùn)算(二進(jìn)制浮點(diǎn)),浮點(diǎn)計(jì)算在大范圍內(nèi)提供近似計(jì)算,但不總是產(chǎn)生準(zhǔn)確的結(jié)果。二進(jìn)制浮點(diǎn)數(shù)特別不適合金融計(jì)算,因?yàn)樗豢赡鼙硎?.1——或任何10的負(fù)冪——exactly as a finite-length binary fraction。
一種解決辦法是使用基本類型,比如int long,然后擴(kuò)大操作數(shù)倍數(shù)做計(jì)算。如果用這種流程,確?;绢愋妥銐虼髞?lái)表示你所有你用到的數(shù)據(jù),這個(gè)例子中,int足夠了System.out.println((200 - 110) + " cents");
另一種辦法使用BigDecimal,他進(jìn)行準(zhǔn)確的十進(jìn)制計(jì)算,他還能通過(guò)JDBC和SQL的DECIMAL類型合作。有一個(gè)箴言:總是使用BigDecimal(String)構(gòu)造器,絕不使用BigDecimal(double).后面這個(gè)構(gòu)造函數(shù)用參數(shù)的準(zhǔn)確值創(chuàng)建一個(gè)實(shí)例:new BigDecimal(.1)返回一個(gè)BigDecimal表示0.1000000000000000055511151231257827021181583404541015625。正確使用會(huì)得到預(yù)期結(jié)果0.90: System.out.println(new BigDecimal("2.00"). subtract(new BigDecimal("1.10"))); 這個(gè)例子不是特別漂亮,java沒有給BigDecimal提供語(yǔ)言學(xué)上的支持,BigDecimal也可能比使用基本類型(對(duì)大量使用十進(jìn)制計(jì)算的程序比較有用)更慢,大多數(shù)情況沒有這個(gè)需要。
總結(jié):但需要準(zhǔn)確答案的時(shí)候,避免使用float and double;金融計(jì)算,使用int, long, or BigDecimal。對(duì)語(yǔ)言設(shè)計(jì)者來(lái)說(shuō),提供十進(jìn)制計(jì)算的語(yǔ)言支持。一個(gè)方法是給操作符重載提供有限的支持,
3 長(zhǎng)整型除法
被除數(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!問題出在計(jì)算MICROS_PER_DAY 時(shí)溢出了,雖然結(jié)果是滿足long的,但不滿足int。這個(gè)計(jì)算過(guò)程全部是按int 計(jì)算的,計(jì)算完之后才轉(zhuǎn)為long。因此很明顯計(jì)算過(guò)程中溢出。為什么會(huì)用int計(jì)算?因?yàn)橐蜃佣际莍nt型的,Java沒有 target typing特性(就是根據(jù)結(jié)果的類型來(lái)確定計(jì)算過(guò)程所用類型)。解決這個(gè)問題很簡(jiǎn)單,把第一個(gè)因子設(shè)置為long,這樣會(huì)強(qiáng)制所有以后的計(jì)算都用long進(jìn)行。雖然很多地方都不需要這么做,但這是一個(gè)好習(xí)慣。
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
我們得到一個(gè)教訓(xùn):和大數(shù)據(jù)打交道的時(shí)候,小心溢出!一個(gè)變量能裝得下結(jié)果,并不代表計(jì)算過(guò)程中會(huì)確保得到正確類型。
4 小學(xué)生都知道的事情
System.out.println(12345 + 5432l); // 毫無(wú)疑問的66666? 看仔細(xì)了!輸出17777
教訓(xùn):使用long的時(shí)候用大寫的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)制有一個(gè)十六或八進(jìn)制都沒有的優(yōu)點(diǎn):數(shù)字都是正的,想表達(dá)負(fù)數(shù)需要一個(gè)負(fù)號(hào)。這樣的話寫十進(jìn)制的int或long,不管正負(fù)都很方便。十六或八進(jìn)制就不這樣了,必須由高位來(lái)決定正負(fù)。這個(gè)例子中,0xcafebabe 是一個(gè)int常量,最高位是1,因此是負(fù)數(shù)=十進(jìn)制 -889275714。這里還有一個(gè)混合類型計(jì)算的額外操作:左邊操作數(shù)是long,右邊是int,計(jì)算時(shí)Java通過(guò)widening primitive conversion 把int變?yōu)閘ong,再加這兩個(gè)long。因?yàn)閕nt是有符號(hào)整型,轉(zhuǎn)變執(zhí)行了一個(gè)符號(hào)擴(kuò)展:把負(fù)的int值提升為數(shù)值相等的long值。右邊的0xcafebabe被提升為long值 0xffffffffcafebabeL,再加到左邊0x100000000L上。當(dāng)被看作int型的時(shí)候,0xcafebabe擴(kuò)展出來(lái)的高32位是-1,而左邊操作數(shù)高32位是1,相加之后為0,這解釋了為什么最高位的1丟失。解決方法是把右邊的操作數(shù)也寫上long,這樣就避免了符號(hào)擴(kuò)展的破壞。
System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));
教訓(xùn):考慮十六或八進(jìn)制自身帶正負(fù),混合類型計(jì)算讓人迷惑。為避免出錯(cuò),最好不要使用混合類型計(jì)算。對(duì)語(yǔ)言設(shè)計(jì)者來(lái)說(shuō),考慮支持無(wú)符號(hào)整數(shù)類型來(lái)去掉符號(hào)擴(kuò)展的可能。有人爭(zhēng)論十六或八進(jìn)制負(fù)數(shù)應(yīng)該被禁止,但對(duì)于程序員來(lái)說(shuō)非常不好,他們經(jīng)常使用十六進(jìn)制來(lái)表示符號(hào)沒有意義的數(shù)值。
6 多重映射
System.out.println((int) (char) (byte) -1);
以int 類型的-1開始,映射到byte,到char,最后返回int。第一次32位變到8位,到16位,最后回到32位。最后發(fā)現(xiàn)值并沒有回到原始!輸出65535
問題來(lái)自映射時(shí)的符號(hào)擴(kuò)展問題。int值-1的所有32位都是1,轉(zhuǎn)為8位byte很直觀,只留下低八位就行,仍然是-1.轉(zhuǎn)char的時(shí)候,就要小心了,byte是有符號(hào)的,char無(wú)符號(hào)。通常有可能保留數(shù)值的同時(shí)把一個(gè)整型轉(zhuǎn)到更“寬”的類型,但不可能用char來(lái)表示一個(gè)負(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。看起來(lái)較復(fù)雜,但有一個(gè)簡(jiǎn)單規(guī)則描述窄變寬轉(zhuǎn)換時(shí)的符號(hào)擴(kuò)展:原始值有符號(hào)就做符號(hào)擴(kuò)展;不管轉(zhuǎn)換成什么類型,char只做零擴(kuò)展。因?yàn)閎yte是有符號(hào)的,byte -1轉(zhuǎn)成char會(huì)有符號(hào)擴(kuò)展。結(jié)果是全1的16位,也就是 216 – 1或65,535。char到int寬擴(kuò)展,規(guī)則告訴我們做零擴(kuò)展。int類型的結(jié)果是65535。雖然規(guī)則簡(jiǎn)單,但最好不要寫依賴這規(guī)則的程序。如果你是寬轉(zhuǎn)換到char,或從char轉(zhuǎn)換(char總是無(wú)符號(hào)整數(shù)),最好顯式說(shuō)明。
如果從char類型的c寬轉(zhuǎn)換,并且不想符號(hào)擴(kuò)展,雖然不需要,但為了清晰可以這樣:
int i = c & 0xffff;
還可以寫注釋:
int i = c; // Sign extension is not performed
如果從char類型的c寬轉(zhuǎn)換,并且想符號(hào)擴(kuò)展,強(qiáng)制char到short(寬度一樣但有符號(hào))
int i = (short) c; // Cast causes sign extension
byte到char,不要符號(hào),必須用位屏蔽抑制他,這是慣例不用注釋(0xff這種0x開頭的默認(rèn)是int類型的?)
char c = (char) (b & 0xff);
byte to a char ,要符號(hào),寫注釋
char c = (char) b; // Sign extension is performed
這一課很簡(jiǎn)單:如果你不能清晰看出程序在干什么,他可能就沒有按你希望的在運(yùn)行。拼命尋求清晰,雖然整數(shù)轉(zhuǎn)換的符號(hào)擴(kuò)展規(guī)則簡(jiǎn)單,但大多程序員不知道。如果你的程序依賴于他,讓你的意圖明顯。
7 交換美味
在一個(gè)簡(jiǎn)單表達(dá)式中,不要對(duì)一個(gè)變量賦值超過(guò)一次。更普遍的說(shuō),不要用“聰明”的程序技巧。
8 Dos Equis
char x = 'X';
int i = 0;
System.out.print(true ? x : 0);
System.out.print(false ? i : x);
輸出XX?可惜輸出的是X88。注意第二三個(gè)操作數(shù)類型不一樣,第5點(diǎn)說(shuō)過(guò),混合類型計(jì)算讓人迷惑!條件表達(dá)式中是最明顯的地方。雖然覺得兩個(gè)表達(dá)式結(jié)果應(yīng)該相同,畢竟他們類型相似,只是位置相反而已,但結(jié)果并不是這樣。
決定條件表達(dá)式結(jié)果類型的規(guī)則很多,但有三個(gè)關(guān)鍵點(diǎn):
1 如果第二三個(gè)操作數(shù)類型一樣,表達(dá)式也是這個(gè)類型,這樣就避免了混合類型計(jì)算。
2 3 復(fù)雜略過(guò) 總之第一個(gè)表達(dá)式是調(diào)用了PrintStream.print(char),第二個(gè)是PrintStream.print(int) 造成結(jié)果不同
總結(jié):最好在條件表達(dá)式中第二三個(gè)操作數(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裝不下,高位兩個(gè)字節(jié)被去掉
x = x + i; // 編譯錯(cuò)誤- "possible loss of precision"
為避免危險(xiǎn),不要在byte, short, or char上面用復(fù)合賦值符。當(dāng)在int上用時(shí),確保右邊不是long, float, or double類型。在float上用,確保右邊不是double。
10 復(fù)合賦值符需要兩邊操作數(shù)都為基本類型或boxed primitives,如int ,Integer。有一例外:+= 左邊為String的話,允許右邊為任意類型。這時(shí)做的是字符串拼接操作。
Object x = "Buy ";
String i = "Effective Java!";
x = x + i; //x+i 為String,和Object兼容,因此表達(dá)式正確
x += i; //非法左邊不是String
注意返回類型:
What Is JDBC ?
JDBC 是java編程中一系列允許簡(jiǎn)單連接到很多數(shù)據(jù)庫(kù)(特別是關(guān)系型數(shù)據(jù)庫(kù))編程APIs . In Java 2 Platform, Standard Edition (J2SE) 5.0,
JDBC API 由兩個(gè)包定義:
java.sql :提供java訪問處理貯存在數(shù)據(jù)源(特別是關(guān)系型數(shù)據(jù)庫(kù))中的數(shù)據(jù),有最基礎(chǔ)常用的對(duì)象如Connection, ResultSet, Statement, and PreparedStatement。這個(gè)包j2se 和j2ee平臺(tái)都可使用。
javax.sql:提供java訪問處理服務(wù)器端數(shù)據(jù)源。這個(gè)包給j2ee提供服務(wù),如DataSource 和RowSet。
ODBC bridge是以O(shè)DBC標(biāo)準(zhǔn) C API 方式實(shí)現(xiàn)JDBC 的庫(kù)。
簡(jiǎn)而言之,JDBC是一個(gè)和database-independent 的訪問數(shù)據(jù)庫(kù)的API。
DriverManager是唯一可以創(chuàng)建數(shù)據(jù)庫(kù)連接的類。DriverManager根據(jù)各個(gè)廠商(如Oracle, MySQL, and Sybase)提供的驅(qū)動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)。
What Is ODBC ?
Open Database Connectivity (ODBC) 是一種來(lái)自微軟的編程接口,他為Windows應(yīng)用程序訪問網(wǎng)絡(luò)上數(shù)據(jù)庫(kù)提供了通用語(yǔ)言。 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ū)動(dòng)或bridges。從java客戶端訪問ODBC-based數(shù)據(jù)庫(kù),可以使用JDBC-ODBC bridge,因此可以使用JDBC-ODBC bridge訪問支持ODBC的數(shù)據(jù)庫(kù),比如Microsoft Access。微軟為他的操作系統(tǒng)提供ODBC driver manager。ODBC driver manager協(xié)調(diào)訪問ODBC驅(qū)動(dòng)和對(duì)應(yīng)的數(shù)據(jù)源。
問題:如果用c++寫數(shù)據(jù)庫(kù)客戶端,你不得不在另一平臺(tái)重新寫客戶端;PC版的不能在Macintosh上運(yùn)行。兩個(gè)原因:1.c++不是跨平臺(tái)的,很多東西沒有特別指定(如int型用多少位表示)2 更重要的是,想網(wǎng)絡(luò)訪問,GUI框架庫(kù)等在各個(gè)平臺(tái)不同。ODBC的另一個(gè)問題是,接口復(fù)雜學(xué)習(xí)時(shí)間長(zhǎng)。JDBC去除了這些問題,為訪問關(guān)系數(shù)據(jù)庫(kù)引入平臺(tái)無(wú)關(guān)的解決方案。因?yàn)樾阅軉栴}和缺少事務(wù)支持, JDBC-ODBC bridge 驅(qū)動(dòng)只適合實(shí)驗(yàn)用或沒有其他可選方法。
What Is a JDBC-ODBC Bridge?
簡(jiǎn)而言之,JDBC-ODBC bridge通過(guò)大多數(shù)ODBC驅(qū)動(dòng)來(lái)提供JDBC訪問。它是一個(gè)把JDBC操作轉(zhuǎn)換為ODBC操作的JDBC驅(qū)動(dòng)。(ODBC操作是由 C-based libraries實(shí)現(xiàn)的——ODBC功能仍然在二進(jìn)制代碼庫(kù)中;如果數(shù)據(jù)庫(kù)或硬件平臺(tái)更換,需要替換ODBC庫(kù))。brige作為sun.jdbc.odbc包實(shí)現(xiàn),包含一個(gè)native library用來(lái)訪問ODBC。sun.jdbc.odbc包在/jre/lib/rt.jar中,包含一個(gè)sun.jdbc.odbc.JdbcOdbcDriver類,用來(lái)JDBC驅(qū)動(dòng)。注意,JDBC-ODBC bridge是一種“萬(wàn)能”的方式,因此可能比一些特別設(shè)計(jì)的JDBC驅(qū)動(dòng)慢。
SQL is a Data Manipulation Language (DML—影響數(shù)據(jù)庫(kù)對(duì)象內(nèi)容的命令集) and a Data Definition Language (DDL—影響數(shù)據(jù)庫(kù)對(duì)象結(jié)構(gòu)的命令集).SQL also 提供控制事務(wù)命令 (such as commit and rollback)
連接jdbc的過(guò)程參考JDBC加載分析 ??傊甁DBC驅(qū)動(dòng)的作用是提供各種數(shù)據(jù)庫(kù)的具體實(shí)現(xiàn)(實(shí)現(xiàn)了java.sql.Driver接口),隱藏具體數(shù)據(jù)庫(kù)的細(xì)節(jié)(每個(gè)數(shù)據(jù)庫(kù)廠商可能會(huì)為同一個(gè)數(shù)據(jù)庫(kù)提供不止一個(gè)驅(qū)動(dòng),這些效率,價(jià)格/性能會(huì)有不同)。
在fianlly中立刻關(guān)閉/釋放 JDBC資源(such as the ResultSet, Statement, PreparedStatement, and Connection objects),而不是等他們自己關(guān)閉,會(huì)改進(jìn)應(yīng)用程序的性能。寫一個(gè)工具類釋放這些資源是一個(gè)好辦法。
JDBC API主要用來(lái)傳SQL statement給數(shù)據(jù)庫(kù),但也能讀寫表格式數(shù)據(jù)源的數(shù)據(jù),這種來(lái)自javax.sql.RowSet組接口的讀寫能力可以被定制去使用更新spreadsheet,flat file 類似表格式數(shù)據(jù)源的數(shù)據(jù)。
JDBC有四種類型的驅(qū)動(dòng)連接數(shù)據(jù)庫(kù)。
異常:SQLException:有g(shù)etNextException()可以鏈接一系列異常,還有很多方法可以展示額外的錯(cuò)誤/異常信息。SQLWarning:SQLException的子類,表示非致命可忽略BatchUpdateException:批量更新時(shí)出現(xiàn)的錯(cuò)誤,除了SQLException提供的信息,還有錯(cuò)誤發(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ù)目的:把命名和對(duì)象聯(lián)系起來(lái),提供用命名訪問對(duì)象的方法。
目錄服務(wù):允許屬性和對(duì)象聯(lián)系,比如用戶對(duì)象的email地址屬性,(命名服務(wù)不提供),因此能利用目錄服務(wù)訪問對(duì)象屬性或以屬性為基礎(chǔ)查找對(duì)象。
HTTP在TCP/IP的頂層,他是一種有web特性的網(wǎng)絡(luò)協(xié)議。HTTP會(huì)話結(jié)構(gòu)是一種簡(jiǎn)單的請(qǐng)求/響應(yīng)序列;瀏覽器請(qǐng)求,服務(wù)器響應(yīng) 。HTTP 響應(yīng)可以 包含HTML,HTTP在響應(yīng)內(nèi)容(服務(wù)器返回的任何東西)之上添加頭信息。瀏覽器利用頭信息來(lái)幫助處理html頁(yè)面。把hml內(nèi)容看作粘貼在HTTP響應(yīng)中的數(shù)據(jù)。HTTP請(qǐng)求中有幾個(gè)方法,最常用的是POST和GET(區(qū)別)。HTTP響應(yīng)中包含狀態(tài)代碼(如404),內(nèi)容類型(也稱為MIME類型,他告訴瀏覽器將會(huì)收到什么類型的數(shù)據(jù)以便處理,比如展示圖片,提供html),響應(yīng)的真實(shí)內(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ù),返回類型的時(shí)候盡可能使用接口,客戶端代碼調(diào)用這樣的類會(huì)更加靈活。
List和Set都是Collection接口的子類,使用Collection可以更通用。
如果一定要自己創(chuàng)建集合類而且不通過(guò)擴(kuò)展存在的ArrayList等的話,至少實(shí)現(xiàn)Collection接口,這樣才能在使用Collection的環(huán)境使用。
靜態(tài)方法不能為abstract,不能調(diào)用非靜態(tài)的屬性或方法。我們經(jīng)常利用靜態(tài)方法,屬性實(shí)現(xiàn)一些“工具類”,比如java.lang中的Math.
接口不允許定義變量,除了定義public static final 變量來(lái)作為全局常量。但是final類型的變量必須顯示初始化,且初始化的方法必須是在申明時(shí)或者在構(gòu)造方法中直接賦值,而不能通過(guò)調(diào)用函數(shù)賦值。
j2se 5引入 :import static Administrator.*; 這樣在代碼中可以直接使用Administrator類的靜態(tài)變量。
查詢了下關(guān)于是否用final限定方法參數(shù)以及局部變量的問題,有爭(zhēng)議(http://stackoverflow.com/questions/316352?sort=votes#sort-top),類似習(xí)慣問題,不過(guò)對(duì)傳入的參數(shù)重新賦值不是好習(xí)慣!否則在方法中使用該參數(shù)的時(shí)候你會(huì)考慮前面的代碼是否對(duì)參數(shù)處理過(guò),還有可能失誤的進(jìn)行了賦值。傾向于方法參數(shù)使用final,局部變量不使用。折中的辦法是設(shè)置eclipse的重賦值警告。
通常有兩種方法可以擴(kuò)展collection 來(lái)滿足一些需要:繼承某種集合類型和封裝某種集合類型。第一種的優(yōu)點(diǎn)是初始化的時(shí)候在內(nèi)存中只產(chǎn)生一個(gè)對(duì)象,這是繼承特性決定的。后者的優(yōu)點(diǎn)是我們可以方便控制被封裝集合的各種屬性。
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; //成績(jī)報(bào)告單
public void addTranscriptEntry(TranscriptEntry te) { // 操作transcript達(dá)到記錄成績(jī)
// 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:
建立新對(duì)象,封裝一個(gè)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更簡(jiǎn)單。
Note that these aggregation statements appear very similar to associations, where the name of the association just so happens to be is composed of or contains. That’s because an aggregation is an association in the broad sense of the term(aggregation 是association的一種特殊表現(xiàn)形式)!aggregation 和associations UML表現(xiàn)不同但最終代碼表現(xiàn)形式一樣
Composition is a strong form of aggregation, in which the “parts” cannot exist without the “whole.” As an example, given the relationship “a Book is composed of many Chapters”, we could argue that a chapter cannot exist if the book to which it belongs ceases to exist; whereas given the relationship “a Car is composed of many Wheels”, we know that a wheel can be removed from a car and still serve a useful purpose. Thus, we’d categorize the Book–Chapter relationship as composition and the Car–Wheel relationship as aggregation.
繼承沒留意的好處:
• Best of all, we can derive a new class from an existing class even if we don’t own the source code for the latter! As long as we have the compiled bytecode version of a class, the inheritance mechanism works just fine; we don’t need the original source code of a class in order to extend it. This is one of the most dramatic ways to achieve productivity with an objectoriented language: find a class (either one written by someone else or one that is built into the language) that does much of what you need, and create a subclass of that class,adding just those features that you need for your own purposes.
• classification is the natural way that humans organize information; so, it only makes sense that we’d organize software along the same lines, making it much more intuitive and hence easier to develop, maintain,extend, and communicate with users about.
繼承與Association, aggregation異同(P186):
Association, aggregation, and inheritance are all said to be relationships between classes. Where inheritance differs from association and aggregation is at the object level.inheritance is indeed a relationship between classes, but not between distinct objects.
注意:避免連鎖反應(yīng),Whenever possible, avoid adding features to non-leaf classes once they have been established in code form in an application, to avoid ripple effects throughout an inheritance hierarchy. 說(shuō)比做容易,這就要求在編碼之前盡可多的花時(shí)間在需求分析和對(duì)象建模階段,雖然不能避免新需求出現(xiàn),但至少避免忽視遺漏了當(dāng)前的需求。
Overriding:子類繼承父類,重寫唯一能改變的是方法的訪問控制,而且只能比父類更寬松,如父類用的是private,子類可以用public。參考了下thinking in java 4th P202 發(fā)現(xiàn)這種說(shuō)法不對(duì),而且是一個(gè)陷阱:父類的該方法根本對(duì)子類不可見!子類的該方法實(shí)際上是一個(gè)全新的方法,連重載都算不上。所以只能重寫non-private方法。遇到private方法你得小心,沒有編譯錯(cuò)誤,但不會(huì)像你想象的工作,最好給方法重新取名,避免陷阱。
不要做的事情:
We shouldn’t change the semantics—that is, the intention, or meaning—of a feature.For example:
• If the print method of a superclass such as Student is intended to display the values of all of an object’s attributes in the command window, then the print method of a subclass such as GraduateStudent shouldn’t, for example, be overridden so that it directs all of its output to a file instead.
• If the name attribute of a superclass such as Person is intended to store a person’s name in “last name, first name” order, then the name attribute of a subclass such as Student should be used in the same fashion.
We can’t physically eliminate features, nor should we effectively eliminate them by ignoring them. To attempt to do so would break the spirit of the “is a” hierarchy. By definition, inheritance requires that all features of all ancestors of a class A must also apply to class A itself in order for A to truly be a proper subclass. If a GraduateStudent could eliminate the degreeSought attribute that it inherits from Student, for example, is a GraduateStudent really a Student after all? Strictly speaking, the answer is no.
進(jìn)一步從實(shí)用角度說(shuō),如果我們重寫一個(gè)方法但不在這方法里做任何事情,其他繼承我們類的人就會(huì)出問題:他們覺得我們的方法是有意義的(特別是他們不能看到我們?cè)创a的時(shí)候)。而我們則打破了“is a” 原則,所以絕不要這樣做!
protected關(guān)鍵字的運(yùn)用,用于控制繼承的訪問控制。
運(yùn)用super(arguments) 減少子類構(gòu)造函數(shù)重復(fù)父類構(gòu)造函數(shù)代碼,和this類似必須在構(gòu)造函數(shù)最開始調(diào)用。
Student s = new Student("Fred", "123-45-6789"); 執(zhí)行這段代碼,Object的構(gòu)造函數(shù)會(huì)自動(dòng)執(zhí)行,接著Student 的父類Person構(gòu)造函數(shù)仔細(xì),然后是我們調(diào)用的Student構(gòu)造函數(shù),如果調(diào)用的Student構(gòu)造函數(shù)沒有顯示調(diào)用父類構(gòu)造函數(shù),則相當(dāng)于默認(rèn)調(diào)用super() 。
java沒有類的多繼承,多繼承很復(fù)雜的一點(diǎn),如果兩個(gè)父類都有相同的方法,子類怎么處理?
We may wish to instantiate additional objects related to the Student object:
初始化與對(duì)象相關(guān)的一些額外對(duì)象:
public class Student() {
// Every Student maintains a handle on his/her own individual Transcript object.
private Transcript transcript;
public Student() {
// Create a new Transcript object for this new Student.
transcript = new Transcript();
// etc.
}
}
讀取數(shù)據(jù)庫(kù)來(lái)初始化對(duì)象屬性:
public class Student {
// Attributes.
String studentId;
String name;
double gpa;
// etc.
// Constructor.
public Student(String id) {
studentId = id;
// Pseudocode.
use studentId as a primary key to retrieve data from the Student table of a
relational database;
if (studentId found in Student table) {
retrieve all data in the Student record;
name = name retrieved from database;
gpa = value retrieved from database;
// etc.
}
}
// etc.
}
和其他已存在的對(duì)象交流:
public class Student {
// Details omitted.
// Constructor.
public Student(String major) {
// Alert the student's designated major department that a new student has
// joined the university.
// Pseudocode.
majorDept.notify(about this student ...);
// etc.
}
// etc.
}
好習(xí)慣:如果需要有參數(shù)的構(gòu)造函數(shù),最好同時(shí)顯示聲明一個(gè)無(wú)參構(gòu)造函數(shù)。
容易出現(xiàn)的bug:如果給構(gòu)造函數(shù)加上void編譯會(huì)通過(guò)!不過(guò)會(huì)被當(dāng)作方法而不是構(gòu)造函數(shù)!
當(dāng)有多個(gè)構(gòu)造函數(shù),而且都有共同的初始化內(nèi)容時(shí),就會(huì)出現(xiàn)很多重復(fù)的代碼,比如構(gòu)造一個(gè)新學(xué)生,我們會(huì)做:
1 通知登記辦公室學(xué)生的存在
2 給學(xué)生創(chuàng)建學(xué)生成績(jī)報(bào)告單
重復(fù)引起以后修改必須修改多處,如果使用this 會(huì)得到改善
public class Student {
// Attribute details omitted.
// Constructor #1.
public Student() {
// Assign default values to selected attributes ... details omitted.
// Do the things common to all three constructors in this first
// constructor ...
// Pseudocode.
alert the registrar's office of this student's existence
// Create a transcript for this student.
transcript = new Transcript();
}
// Constructor #2.
public Student(String s) {
// ... then, REUSE the code of the first constructor within the second!
this();
// Then, do whatever else extra is necessary for constructor #2.
this.setSsn(s);
}
// Constructor #3.
public Student(String s, String n, int i) {
// ... and REUSE the code of the first constructor within the third!
this();
// Then, do whatever else extra is necessary for constructor #3.
this.setSsn(s);
this.setName(n);
this.setAge(i);
}
// etc.
}