1. 程式人生 > >Java程式設計中的一些細節

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 {{}} 這種是相當於建立了一個匿名內部類,內部那個{}相當於其初始化程式碼塊,也就相當於構造函數了,因為匿名函式沒有名字,沒有建構函式。
匿名內部類會呼叫父類的同參建構函式,這和一般的呼叫父類預設構造不一樣