這篇文章是在對《編寫高質量代碼:改善Java程序的151個建議》這本書中提到的內容做筆記。
通用的方法和準則
三元操作符的類型務必唯一
<span style="font-size:18px;">public class Client {
public static void main(String[] args) {
int i = 80;
String s = String.valueOf(i<100?90:100);
String s1 = String.valueOf(i<100?90:100.0);
System.out.println("兩者是否相等:"+s.equals(s1));
}
}</span>
可以看到輸出結果是false,因爲三元操作符的返回值必須唯一s的返回是int型,而s1是浮點型。別人null值和空值影響變長方法
<span style="font-size:18px;">public class Client {
public void methodA(String str,Integer... is){
System.out.println("Integer");
}
public void methodA(String str,String... strs){
System.out.println("String");
}
public static void main(String[] args) {
Client client = new Client();
client.methodA("China", 0);
client.methodA("China", "People");
// client.methodA("China");
// client.methodA("China",null);
}
}</span>
//client.methodA("China") 由於變長方法重載,都滿足只有一個String的參數,編譯器沒辦法知道選在哪個函數,因此編譯不通過。
//client.methodA("China",null) null是沒有類型的。編譯器沒辦法知道選在哪個函數,因此編譯不通過。
可以改用如下寫法:明確告訴編譯器strs類型,編譯通過
public class Client {
public void methodA(String str,Integer... is){
System.out.println("Integer");
}
public void methodA(String str,String... strs){
System.out.println("String");
}
public static void main(String[] args) {
Client client = new Client();
Integer[] strs = null;
client.methodA("China",strs);
}
}
警惕自增的陷阱
public static void main(String[] args) {
int count =0;
for(int i=0;i<10;i++){
count=count++;
}
System.out.println("count="+count);
}
可以看到,count最終的輸出值是0.
public static int mockAdd(int count){
//先保存初始值
int temp =count;
//做自增操作
count = count+1;
//返回原始值
return temp;
}
基本類型
用偶判斷,不用奇判斷
public static void main(String[] args) {
//接收鍵盤輸入參數
Scanner input = new Scanner(System.in);
System.out.print("請輸入多個數字判斷奇偶:");
while(input.hasNextInt()){
int i = input.nextInt();
String str =i+ "->" + (i%2 ==1?"奇數":"偶數");
System.out.println(str);
}
}
可以看到,當輸入數字 -1 時,運行結果爲偶數。模擬下java %符號的運行代碼
<span style="font-size:18px;">//模擬取餘計算,dividend被除數,divisor除數
</span><span style="font-size:14px;">public static int remainder(int dividend,int divisor){
return dividend - dividend / divisor * divisor;
}</span>
可以看到java執行 %操作時原來是這麼取值的。因此修改這段代碼只需改爲 i%2==0?"偶數":"奇數"就可以。
邊界、邊界、邊界
public class Client {
//一個會員擁有產品的最大數量
public final static int LIMIT = 2000;
public static void main(String[] args) {
//會員當前擁有產品數量
int cur = 1000;
Scanner input = new Scanner(System.in);
System.out.print("請輸入需要預定的數量:");
while(input.hasNextInt()){
int order = input.nextInt();
//當前擁有的與準備訂購的產品數量之
if(order>0 && order+cur<=LIMIT){
System.out.println("你已經成功預定的"+order+"個產品!");
}else{
System.out.println("超過限額,預訂失敗!");
}
}
}
}
這段代碼看似沒有問題,但是當輸入人員輸入2147483647時,發現提示訂購成功!沒錯,因爲這個到達了int的邊界,2147483647 + 1000 = -xxxxxxxxxx當然小於2000了。!!所以邊界一定要注意!!!優先使用整形池
ublic static void main(String[] args) {
Scanner input = new Scanner(System.in);
while(input.hasNextInt()){
int ii = input.nextInt();
System.out.println("\n===="+ii+" 的相等判斷======");
//兩個通過new產生的Integer對象
Integer i =new Integer(ii);
Integer j = new Integer(ii);
System.out.println("new產生的對象:" + (i==j));
//基本類型轉爲裝箱類型後比較
i=ii;
j=ii;
System.out.println("基本類型轉換的對象:" + (i==j));
//通過靜態方法生成一個實例
i=Integer.valueOf(ii);
j = Integer.valueOf(ii);
System.out.println("valueOf產生的對象:" + (i==j));
}
}
new產生的對象:false
基本類型轉換的對象:true
valueOf產生的對象:true
====128 的相等判斷======
new產生的對象:false
基本類型轉換的對象:false
valueOf產生的對象:false
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
當數字小128&&大於-127時使用的是整數池中的對象,否則返回的是new的新對象。private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low));
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
因此在包裝類進行比較時要使用equals()方法而不要使用==,如果需要創建包裝對象時,使用valueOf()能夠節約內存並提高性能。類、對象和方法
使用構造代碼塊精煉程序
public class Client {
{
//構造代碼塊
System.out.println("執行構造代碼塊");
}
public Client(){
System.out.println("執行無參構造");
}
public Client(String _str){
System.out.println("執行有參構造");
}
}
當我們new一個Client對象時,發現,會先輸出執行構造代碼塊。由於構造代碼塊無法獨立運行,實際編譯器會把每個構造代碼塊插入到構造函數的最前端,相當於如下代碼:public class Client {
public Client(){
System.out.println("執行構造代碼塊");
System.out.println("執行無參構造");
}
public Client(String _str){
System.out.println("執行構造代碼塊");
System.out.println("執行有參構造");
}
}
是用匿名類的構造函數
public static void main(String[] args) {
String[] strs = {"aaa","bbb","ccc"};
List l1 = new ArrayList();
List l2 = new ArrayList(){};
List l3 = new ArrayList(){{}};
System.out.println(l1.getClass());
System.out.println(l2.getClass());
System.out.println(l3.getClass());
System.out.println(l1.getClass() == l2.getClass());
System.out.println(l2.getClass() == l3.getClass());
System.out.println(l1.getClass() == l3.getClass());
}
可以看到輸出結果爲:public static void main(String[] args) {
//定義一個繼承ArrayList的內部類
class Sub extends ArrayList{
}
//聲明和賦值
List l2 = new Sub();
}
l3分開來看也就會明白:public static void main(String[] args) {
//定義一個繼承ArrayList的內部類
class Sub extends ArrayList{
{
//初始化塊
}
}
//聲明和賦值
List l3 = new Sub();
}
字符串
正確使用String、StringBuffer、StringBuilder
自由使用字符串拼接
數組和集合
不同列表選擇不同的遍歷方式
public static int average(List<Integer> list) {
int sum = 0;
if (list instanceof RandomAccess) {
//可以隨機存取,則使用下標遍歷
for (int i = 0, size = list.size(); i < size; i++) {
sum += list.get(i);
}
} else {
//有序存取,使用foreach方式
for (int i : list) {
sum += i;
}
}
// 除以人數,計算平均值
return sum / list.size();
}
優雅的使用對集合進行運算(並集、交集、差集)
枚舉和註解
推薦使用枚舉定義常量
泛型和反射
Java的泛型是類型擦除的
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abc");
String str = list.get(0);
}
public void doSomething(){
List list = new ArrayList();
list.add("abc");
String str = (String)list.get(0);
}
理解了這個,就可以解釋爲什麼如下代碼的執行結果爲truepublic static void main(String[] args) {
List<String> ls = new ArrayList<String>();
List<Integer> li = new ArrayList<Integer>();
System.out.println(li.getClass() == li.getClass());
}
建議採用的順序是List<T>、List<?>、List<Object>
List<T>、List<?>、List<Object>這三者都可以容納所有的對象,但使用的順序應該首選List<T>、次之List<?>,最後List<Object>原因如下:
(1)List<T>是確定的某一個類型
List<T>表示的是list列表中的所有元素都是T類型,具體類型在運行時決定;List<?>表示是任意類型,與List<T>類似,而List<Object>則表示集合中所有元素都爲Object類型,因爲Object是所有類型的父類,所以List<Object>也可以容納所有的類類型,從這一字面意義上分析,List<T>更符合習慣:編碼者知道他是某一類型,只是在運行時期才確定而已。
(2)List<T>可以進行讀寫操作
List<T>可以進行add,remove等操作,因爲他的類型是固定的T類型,在編碼期不需要進行任何的轉型操作。
List<?>是隻讀類型的,不能進行增加、修改的操作,因爲編譯器不知道List<?>中容納的是什麼類型的元素,也就無法類型是否安全了,而List<?>讀出的全部都是Object類型,需要主動轉型,所以它經常用於泛型方法的返回值。需要注意List<?>雖然無法添加修改,但是可以做刪除操作。比如remove,clear,因爲刪除動作與泛型類型無關。
List<Object>也可以進行讀寫操作,但是他執行寫入操作時需要向上轉型(Up Cast),在讀取數據後需要向下轉型(Down Cast),而此時已經失去了泛型的意義了。
父類引用指向子類對象,而子類引用不能指向父類對象。
異常
提倡異常封裝
public class Client {
public static void main(String[] args) {
try {
doStuff();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void doStuff() throws Exception {
InputStream is = new FileInputStream("無效文件.txt");
/*文件操作*/
}
public static void doStuff2() throws MyBussinessException{
try {
InputStream is = new FileInputStream("無效文件.txt");
} catch (FileNotFoundException e) {
//爲方便開發和維護人員而設置的異常信息
e.printStackTrace();
//拋出業務異常
throw new MyBussinessException(e);
}
/*文件操作*/
}
}
class MyBussinessException extends Exception{
public MyBussinessException(Throwable t){
super(t);
}
}
public class Client {
public static void main(String[] args) {
try {
doStuff();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void doStuff() throws MyException {
List<Throwable> list = new ArrayList<Throwable>();
// 第一個邏輯片段
try {
// Do Something
} catch (Exception e) {
list.add(e);
}
// 第二個邏輯片段
try {
// Do Something
} catch (Exception e) {
list.add(e);
}
if (list.size() > 0) {
throw new MyException(list);
}
}
}
class MyException extends Exception {
// 容納所有的異常
private List<Throwable> causes = new ArrayList<Throwable>();
// 構造函數,傳遞一個異常列表
public MyException(List<? extends Throwable> _causes) {
causes.addAll(_causes);
}
// 讀取所有的異常
public List<Throwable> getExceptions() {
return causes;
}
}
不要再finally代碼塊中處理返回值
public class Client {
public static void main(String[] args) {
try {
doStuff(-1);
doStuff(100);
} catch (Exception e) {
System.out.println("這裏是永遠都不會到達的");
}
}
public static int doStuff(int _p) throws Exception {
try {
if (_p < 0) {
throw new DataFormatException("數據格式錯誤");
} else {
return _p;
}
} catch (Exception e) {
//異常處理
throw e;
} finally {
return -1;
}
}
}
上面這段代碼的doStuff(-1) 和doStuff(100)的返回值都是-1,導致這個問題的原因有兩個:
public class Client {
public static void main(String[] args) {
System.out.println(doStuff());
System.out.println(doStuff2().getName());
}
public static int doStuff() {
int a = 1;
try {
return a;
} catch (Exception e) {
} finally {
//重新修改一下返回值
a = -1;
}
return 0;
}
public static Person doStuff2() {
Person person = new Person();
person.setName("張三");
try {
return person;
} catch (Exception e) {
} finally {
//重新修改一下返回值
person.setName("李四");
}
person.setName("王五");
return person;
}
}
class Person{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
doStuff()永遠都返回1,因爲返回的是int類型,java對於int型返回的是值。 public static void doSomething() {
try {
//正常拋出異常
throw new RuntimeException();
} finally {
return;
}
}
public static void main(String[] args) {
try {
doSomething();
} catch (RuntimeException e) {
System.out.println("這裏永遠都不會到達!");
}
}
上面的finally代碼塊中的return已經告訴jvm,doSomething已經正常執行結束了,也沒有任何異常,所以main中永遠都不會捕獲到異常。
多線程、併發
volatile不能保證數據同步
public static void main(String[] args) throws Exception {
// 理想值,並做爲最大循環次數
int value = 1000;
// 循環次數,防止出現無限循環造成死機情況
int loops = 0;
//主線程組,用於估計活動線程數
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (loops++ < value) {
// 共享資源清零
UnsafeThread ut = new UnsafeThread();
for (int i = 0; i < value; i++) {
new Thread(ut).start();
}
// 先等15毫秒,等待活動線程數量成爲1
do {
Thread.sleep(15);
} while (tg.activeCount() != 1);
// 檢查實際值與理論值是否一致
if (ut.getCount() != value) {
// 出現線程不安全的情況
System.out.println("循環到第 " + loops + " 遍,出現線程不安全情況");
System.out.println("此時,count=" + ut.getCount());
System.exit(0);
}
}
System.out.println("循環結束");
}
}
class UnsafeThread implements Runnable {
// 共享資源
private volatile int count = 0;
@Override
public void run() {
// 爲了增加CPU的繁忙程度
for (int i = 0; i < 1000; i++) {
Math.hypot(Math.pow(92456789, i), Math.cos(i));
}
// 自增運算
count++;
}
public int getCount() {
return count;
}
}
最終的輸出結果異步運算考慮使用Callable接口
public static void main(String[] args) throws Exception {
//生成一個單線程的異步執行器
ExecutorService es = Executors.newSingleThreadExecutor();
//線程執行後的期望值
Future<Integer> future = es.submit(new TaxCalculator(100));
while(!future.isDone()){
//還沒有運算完成,等待10毫秒
TimeUnit.MILLISECONDS.sleep(200);
//輸出進度符號
System.out.print("#");
}
System.out.println("\n計算完成,稅金是:"+ future.get() + " 元");
//關閉異步執行器
es.shutdown();
}
}
//稅款計算器
class TaxCalculator implements Callable<Integer> {
//本金
private int seedMoney;
//接收主線程提供的參數
public TaxCalculator(int _seedMoney) {
seedMoney = _seedMoney;
}
@Override
public Integer call() throws Exception {
//複雜計算,運行一次需要10秒
TimeUnit.MILLISECONDS.sleep(10000);
return seedMoney /10;
}
}
此類異步計算的好處是:
使用CountDownLatch協調子線程
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class Client {
static class Runner implements Callable<Integer> {
//開始信號
private CountDownLatch begin;
//結束信號
private CountDownLatch end;
public Runner(CountDownLatch _begin, CountDownLatch _end) {
begin = _begin;
end = _end;
}
@Override
public Integer call() throws Exception {
// 跑步的成績
int score = new Random().nextInt(25);
// 等待發令槍響起
begin.await();
// 跑步中……
TimeUnit.MILLISECONDS.sleep(score);
// 跑步者已經跑完全程
end.countDown();
return score;
}
}
public static void main(String[] args) throws Exception {
//參加賽跑人數
int num = 10;
// 發令槍只響一次
CountDownLatch begin = new CountDownLatch(1);
// 參與跑步有多個
CountDownLatch end = new CountDownLatch(num);
// 每個跑步者一個跑道
ExecutorService es = Executors.newFixedThreadPool(num);
// 記錄比賽成績
List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
// 跑步者就位,所有線程處於等待狀態
for (int i = 0; i < num; i++) {
futures.add(es.submit(new Runner(begin, end)));
}
// 發令槍響,跑步者開始跑步
begin.countDown();
// 等待所有跑步者跑完全程
end.await();
int count = 0;
// 統計總分
for (Future<Integer> f : futures) {
count += f.get();
}
System.out.println("平均分數爲:" + count / num);
}
}
CountDownLatch是一個倒數的同步計數器,每個線程在執行完畢後執行countDown,表示自己運行結束,這對於多個子任務的計算特別有效。CyclicBarrier讓多線程齊步走
public class Client {
static class Worker implements Runnable {
// 關卡
private CyclicBarrier cb;
public Worker(CyclicBarrier _cb) {
cb = _cb;
}
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + "-到達匯合點");
// 到達匯合點
cb.await();
} catch (Exception e) {
// 異常處理
}
}
}
public static void main(String[] args) throws Exception {
// 設置彙集數量,以及彙集完成後的任務
CyclicBarrier cb = new CyclicBarrier(2, new Runnable() {
public void run() {
System.out.println("隧道已經打通!");
}
});
// 工人1挖隧道
new Thread(new Worker(cb), "工人1").start();
// 工人2挖隧道
new Thread(new Worker(cb), "工人2").start();
}
}
CyclicBarrier可以讓全部線程處於等待狀態(阻塞),然後在滿足條件的情況下全部執行,這就好比是一條起跑線,不管是如何達到起跑線的,只要到達這條起跑線就必須等待其他人員,在人員到齊後再各奔東西,CyclicBarrier關注的是匯合點的信息,而不在乎之前或之後如何處理。
性能和效率
提升java新能的基本方法
public static String toChineseNum(int num){
//中文數字
String[] cns = {"零","壹","貳","叄","肆","伍","陸","柒","捌","玖"};
return cns[num];
}
每次調用該方法時都會重新生成一個cns數組,注意該數組不會改變,屬於不變數組,在這種情況下把它聲明爲類變量,並且加上final static修飾會更合適,在類加載後就生成了該數組,每次方法調用則不再重新生成數組對象了,這有助於提高系統新能,代碼如下: //中文數字
final static String[] cns = {"零","壹","貳","叄","肆","伍","陸","柒","捌","玖"};
public String toChineseNum(int num){
return cns[num];
}
(2)縮小變量的作用範圍