Java程式設計中的一些細節
程式設計過程也是一項細心地活動,需要考慮到的地方很多很多,比如下面的各種細節。
Java成員變數(基本資料型別和物件)會被虛擬機器初始化為0或null,而區域性變數不會被虛擬機器初始化,沒有手動初始化就是用會報錯,這是Java中規定的。原理:JVM在堆中申請物件記憶體的時候(或者靜態方法區的靜態變數),(在類載入過程的連結中的準備階段做的)順便將所有成員都置為0(物件就是null),再賦值(賦了兩次值),沒有賦值的保持原樣。而JVM在棧申請區域性變數後沒有置為0這個操作,因為太多,影響效能,變數的值是不確定(比如有可能是複用其它區域性變數,值就是那個變數的值),所以才需要手動賦值。
String中用到正則式的方法:
matches, matche.regonMatches();
split;
replaceFirst,replaceAll() (replace不是);
Java記憶體分割槽中還有一個PC暫存器 別忘了。
定義泛型類的內部,使用該類主要有兩種方式:
一、將物件傳進來,不操作它內部的任何東西,如容器。
二、物件傳進來,只操作它內部部分內容,類族的泛型。
Java有一個無符號右移運算子 >>>,右移之後,高位值只補零。
以下來自《編寫高質量程式碼:改善Java程式的151個建議》
三元操作符的兩個返回值的型別務必一致,不一致會導致自動型別轉換,型別提升int->float->double;
比如
int i = 80;
return i > 100 ? 90 : 100.0;
此時返回的90是float而非int
用a%b判斷奇偶在Java中,若a是負數判斷奇數得到的結果是-1
這是因為,Java的取餘是這樣的
mod = a - a / b * b
// -3 - (-3 / 2 * 2) = -1,和整數是不一樣的。
所以判斷的時候要用 ==0 來判斷
不要讓型別默默轉換
在運算中是int,其結果是long,這樣的轉換會出錯。
比如
constant int V = 30 * 10000 * 1000;
long dis = V * 8 * 60;
轉換出錯,在表示式中已經溢位了,賦值給long,自然是錯的。
四捨五入會有微小誤差,如果是大量計算(金融行業,上億的交易),誤差會變很大(銀行虧),所以有一個銀行家舍入演算法,精確的減小誤差。在Java的round()函式中提供了相應的功能。HALF_EVEN模式。
包裝類要注意的幾個問題:
基本型別的包裝類放到集合中時,要注意它的NULL值,如果獲取集合中的值時使用的是自動拆箱,而自動拆箱過程是呼叫物件的xxValue方法,因此會產生自動拆箱會產生NullPointerException。比如下面的程式碼
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(null);
int count = 0;
for (int i : list) {
count += i;
}
不能拿包裝類直接比較相等,可能會不相等,因為比較的是物件地址,然而它們卻可以比較大小。Java這點設計不太科學。
final變數序列化與反序列化問題:
通常,final變數都希望它的值就是程式碼中指定的值,但是如果版本變動之後final的值變了,如果是在建構函式之外,反序列化會重新賦值,而如果在建構函式之內,則不會。這是因為反序列化不會呼叫建構函式。所以需要序列化/反序列化的類最好不要在建構函式中為final變數賦值。
Java中易變業務可以用指令碼語言寫,然後執行,Java提供相應的支援。
instanceof 使用幾個規則:
instanceof 只能在同一類族中比較使用,不同的編譯器會報錯。
null incetanceof xxx 返回false
子類例項 instanceof 父類 返回true 反之則是false
泛型裡面的T instanceof
int 等基本型別 instanceof 是無效的 ,int不是類,沒有類物件,int.getClass()不存在
順便的 基本型別指的是不是類的型別,String不是他們的
整數:
byte short int long 位元組數依次 *2
浮點
float double
最後是 char 和 boolean
Java中使用斷言:
assert xxx: “xxx”
最後是提示資訊,是可選項
但是注意assert需要Java機器啟用才有效,所以只能在開發測試等生產環境中使用,在實際執行環境中是關閉了的。所以斷言語句裡面不能放入任何被依賴的,與實際執行有關的程式碼,只能放入判斷語句。
switch中多個case處理一樣的寫法:
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
/* 0xxxxxxx*/
count++;
chararr[chararr_count++]=(char)c;
break;
case 12: case 13:
/* 110x xxxx 10xx xxxx*/
count += 2;
...
Java序列化中的UID是通過報名,繼承關係,非私有屬性,引數返回值等諸多因子計算得出的,十分複雜,基本上這個算出來的值是唯一的。
謹慎使用動態編譯
動態編譯在框架中要謹慎使用,也不適合在高效能的框架中使用,要考慮安全問題,因為如果在Web應用中動態編譯網路傳入的程式碼就可能形成注入漏洞,並且如果要進行動態編譯:需要記錄動態編譯的日誌資訊,以便維護解決。
前端校驗是無效的
在web開發中,前端資料校驗只能對普通使用者產生作用,而對於瞭解的人,是可以繞過前端校驗傳送資料的,所以後臺必須再進行嚴格的資料校驗。
注意運算式子進行值校驗時,運算溢位的問題,不要當做一般的單個數那樣進行校驗
比如要使用者輸入資料x
;要校驗使用者輸入的x
與當前值cur
相加之後的結果小於limit
,一般會怎麼寫程式碼, 可能如下
int LIMIT = 100;
if (x > 0 && x + cur < LIMIT) {
...
}
這樣的程式碼是有問題的,x 如果取到int的最值附近,x + cur就溢位了,判斷條件成了,x逃過了校驗!這就是個很嚴重的問題,如果放在重要的系統中,比如商品,本來最多使用者只能買100個,結果卻賣了20多億個,系統會垮掉的。
善用使用靜態內部類可以提高封裝性
善用初始化程式碼塊構造物件
建構函式不要寫得過於複雜
List list = new ArrayList {{}} 這種是相當於建立了一個匿名內部類,內部那個{}相當於其初始化程式碼塊,也就相當於構造函數了,因為匿名函式沒有名字,沒有建構函式。
匿名內部類會呼叫父類的同參建構函式,這和一般的呼叫父類預設構造不一樣