史上最全的 Java 新手問題彙總

Java譯站發表於2015-10-15

Java是目前最流行的程式語言之一——它可以用來編寫Windows程式或者是Web應用,移動應用,網路程式,消費電子產品,機頂盒裝置,它無處不在。

有超過30億的裝置是執行在Java之上的。根據Oracle的統計資料,光是使用中的Java Card就有有50億。

超過900萬程式設計師選擇使用Java進行開發,它是最受開發人員歡迎的語言,同時也是最流行的開發平臺。

本文為那些準Java程式設計師們準備了一系列廣為流傳的Java最佳程式設計實踐

優先返回空集合而非null

如果程式要返回一個不包含任何值的集合,確保返回的是空集合而不是null。這能節省大量的”if else”檢查。

public class getLocationName {
    return (null==cityName ? "": cityName);
}

謹慎操作字串

如果兩個字串在for迴圈中使用+操作符進行拼接,那麼每次迴圈都會產生一個新的字串物件。這不僅浪費記憶體空間同時還會影響效能。類似的,如果初始化字串物件,儘量不要使用構造方法,而應該直接初始化。比方說:

//Slower Instantiation
String bad = new String("Yet another string object");

//Faster Instantiation
String good = "Yet another string object"

避免無用物件

建立物件是Java中最昂貴的操作之一。因此最好在有需要的時候再進行物件的建立/初始化。如下:

import java.util.ArrayList;
import java.util.List;

public class Employees {

    private List Employees;

    public List getEmployees() {

        //initialize only when required
        if(null == Employees) {
            Employees = new ArrayList();
        }
        return Employees;
    }
}

陣列與ArrayList之爭

開發人員經常會發現很難在陣列和ArrayList間做選擇。它們二者互有優劣。如何選擇應該視情況而定。

import java.util.ArrayList;

public class arrayVsArrayList {

    public static void main(String[] args) {
        int[] myArray = new int[6];
        myArray[7]= 10; // ArraysOutOfBoundException

        //Declaration of ArrayList. Add and Remove of elements is easy.
        ArrayList<Integer> myArrayList = new ArrayList<>();
        myArrayList.add(1);
        myArrayList.add(2);
        myArrayList.add(3);
        myArrayList.add(4);
        myArrayList.add(5);
        myArrayList.remove(0);

        for(int i = 0; i < myArrayList.size(); i++) {
        System.out.println("Element: " + myArrayList.get(i));
        }

        //Multi-dimensional Array 
        int[][][] multiArray = new int [3][3][3]; 
    }
}
  • 陣列是定長的,而ArrayList是變長的。由於陣列長度是固定的,因此在宣告陣列時就已經分配好記憶體了。而陣列的操作則會更快一些。另一方面,如果我們不知道資料的大小,那麼過多的資料便會導致ArrayOutOfBoundException,而少了又會浪費儲存空間。
  • ArrayList在增刪元素方面要比陣列簡單。
  • 陣列可以是多維的,但ArrayList只能是一維的。
  • try塊的finally塊沒有被執行

看下下面這段程式碼:

public class shutDownHooksDemo {
    public static void main(String[] args) {
        for(int i=0;i<5;i++)
        {
            try {
                if(i==4) {
                    System.out.println("Inside Try Block.Exiting without executing Finally block.");
                    System.exit(0);
                }
            }
            finally {
                System.out.println("Inside Finally Block.");
            }
        }
    }
}

從程式碼來看,貌似finally塊中的println語句應該會被執行5次。但當程式執行後,你會發現finally塊只執行了4次。第5次迭代的時候會觸發exit函式的呼叫,於是這第5次的finally便永遠也觸發不到了。原因便是——System.exit會掛起所有執行緒的執行,包括當前執行緒。即便是try語句後的finally塊,只要是執行了exit,便也無力迴天了。

在呼叫System.exit時,JVM會在關閉前執行兩個結束任務:

首先,它會執行完所有通過Runtime.addShutdownHook註冊進來的終止的鉤子程式。這一點很關鍵,因為它會釋放JVM外部的資源。

接下來的便是Finalizer了。可能是System.runFinalizersOnExit也可能是Runtime.runFinalizersOnExit。finalizer的使用已經被廢棄有很長一段時間了。finalizer可以在存活物件上進行呼叫,即便是這些物件仍在被其它執行緒所使用。而這會導致不可預期的結果甚至是死鎖。

public class shutDownHooksDemo {

    public static void main(String[] args) {
            for(int i=0;i<5;i++)
            {
                    final int final_i = i;
                    try {
                            Runtime.getRuntime().addShutdownHook(
                                            new Thread() {
                                            public void run() {
                                            if(final_i==4) {
                                            System.out.println("Inside Try Block.Exiting without executing Finally block.");
                                            System.exit(0);
                                            }
                                            }
                                            });
                    }
                    finally {
                            System.out.println("Inside Finally Block.");
                    }

            }
    }
}

判斷奇數

看下這幾行程式碼,看看它們是否能用來準確地判斷一個數是奇數?

public boolean oddOrNot(int num) {
    return num % 2 == 1;
}

看似是對的,但是每執行四便會有一個錯誤的結果(用資料說話)。考慮到負奇數的情況,它除以2的結果就不會是1。因此,返回值是false,而這樣是不對的。

程式碼可以修改成這樣:

public boolean oddOrNot(int num) {
    return (num & 1) != 0;
}

這麼寫不光是負奇數的問題解決了,並且還是經過充分優化過的。因為算術運算和邏輯執行要比乘除運算更高效,計算的結果也會更快。

單引號與雙引號的區別

public class Haha {
    public static void main(String args[]) {
    System.out.print("H" + "a");
    System.out.print('H' + 'a');
    }
}

看起來這段程式碼會返回”Haha”,但實際返回的是Ha169。原因就是用了雙引號的時候,字元會被當作字串處理,而如果是單引號的話,字元值會通過一個叫做基礎型別拓寬的操作來轉換成整型值。然後再將值相加得到169。

一些防止記憶體洩露的小技巧

記憶體洩露會導致軟體的效能降級。由於Java是自動管理記憶體的,因此開發人員並沒有太多辦法介入。不過還是有一些方法能夠用來防止記憶體洩露的。

  • 查詢完資料後立即釋放資料庫連線
  • 儘可能使用finally塊
  • 釋放靜態變數中的例項
  • 避免死鎖

死鎖出現的原因有很多。避免死鎖不是一句話就能解決的。通常來說,當某個同步物件在等待另一個同步物件所擁有的資源上的鎖時,便會產生死鎖。

試著執行下下面的程式。它會告訴你什麼是死鎖。這個死鎖是由於兩個執行緒都在等待對方所擁有的資源,因此會產生死鎖。它們會一直等待,沒有誰會先放手。

public class DeadlockDemo {
   public static Object addLock = new Object();
   public static Object subLock = new Object();

   public static void main(String args[]) {

      MyAdditionThread add = new MyAdditionThread();
      MySubtractionThread sub = new MySubtractionThread();
      add.start();
      sub.start();
   }
private static class MyAdditionThread extends Thread {
      public void run() {
         synchronized (addLock) {
        int a = 10, b = 3;
        int c = a + b;
            System.out.println("Addition Thread: " + c);
            System.out.println("Holding First Lock...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Addition Thread: Waiting for AddLock...");
            synchronized (subLock) {
               System.out.println("Threads: Holding Add and Sub Locks...");
            }
         }
      }
   }
   private static class MySubtractionThread extends Thread {
      public void run() {
         synchronized (subLock) {
        int a = 10, b = 3;
        int c = a - b;
            System.out.println("Subtraction Thread: " + c);
            System.out.println("Holding Second Lock...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Subtraction  Thread: Waiting for SubLock...");
            synchronized (addLock) {
               System.out.println("Threads: Holding Add and Sub Locks...");
            }
         }
      }
   }
}

輸出:

Addition Thread: 13
Subtraction Thread: 7
Holding First Lock...
Holding Second Lock...
Addition Thread: Waiting for AddLock...
Subtraction  Thread: Waiting for SubLock...

但如果呼叫的順序變一下的話,死鎖的問題就解決了。

public class DeadlockSolutionDemo {
   public static Object addLock = new Object();
   public static Object subLock = new Object();

   public static void main(String args[]) {

      MyAdditionThread add = new MyAdditionThread();
      MySubtractionThread sub = new MySubtractionThread();
      add.start();
      sub.start();
   }

private static class MyAdditionThread extends Thread {
      public void run() {
         synchronized (addLock) {
        int a = 10, b = 3;
        int c = a + b;
            System.out.println("Addition Thread: " + c);
            System.out.println("Holding First Lock...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Addition Thread: Waiting for AddLock...");
            synchronized (subLock) {
               System.out.println("Threads: Holding Add and Sub Locks...");
            }
         }
      }
   }

   private static class MySubtractionThread extends Thread {
      public void run() {
         synchronized (addLock) {
        int a = 10, b = 3;
        int c = a - b;
            System.out.println("Subtraction Thread: " + c);
            System.out.println("Holding Second Lock...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Subtraction  Thread: Waiting for SubLock...");
            synchronized (subLock) {
               System.out.println("Threads: Holding Add and Sub Locks...");
            }
         }
      }
   }
}

輸出:

Addition Thread: 13
Holding First Lock...
Addition Thread: Waiting for AddLock...
Threads: Holding Add and Sub Locks...
Subtraction Thread: 7
Holding Second Lock...
Subtraction  Thread: Waiting for SubLock...
Threads: Holding Add and Sub Locks...

替Java省點記憶體

某些Java程式是CPU密集型的,但它們會需要大量的記憶體。這類程式通常執行得很緩慢,因為它們對記憶體的需求很大。為了能提升這類應用的效能,可得給它們多留點記憶體。因此,假設我們有一臺擁有10G記憶體的Tomcat伺服器。在這臺機器上,我們可以用如下的這條命令來分配記憶體:

export JAVA_OPTS="$JAVA_OPTS -Xms5000m -Xmx6000m -XX:PermSize=1024m -XX:MaxPermSize=2048m"
  • Xms = 最小記憶體分配
  • Xmx = 最大記憶體分配
  • XX:PermSize = JVM啟動時的初始大小
  • XX:MaxPermSize = JVM啟動後可分配的最大空間
  • 如何計算Java中操作的耗時

在Java中進行操作計時有兩個標準的方法:System.currentTimeMillis()和System.nanoTime()。問題就在於,什麼情況下該用哪個。從本質上來講,他們的作用都是一樣的,但有以下幾點不同:

  1. System.currentTimeMillis()的精度在千分之一秒到千分之15秒之間(取決於系統)而System.nanoTime()則能到納秒級。
  2. System.currentTimeMillis讀操作耗時在數個CPU時鐘左右。而System.nanoTime()則需要上百個。
  3. System.currentTimeMillis對應的是絕對時間(1970年1 月1日所經歷的毫秒數),而System.nanoTime()則不與任何時間點相關。
  4. Float還是double
資料型別 所用位元組 有效位數
float 4 7
double 8 15

在對精度要求高的場景下,double型別相對float要更流行一些,理由如下:

大多數處理器在處理float和double上所需的時間都是差不多的。而計算時間一樣的前提下,double型別卻能提供更高的精度。

冪運算

Java是通過異或操作來進行冪運算的。Java對於冪運算有兩種處理方式:

乘積:

double square = double a * double a;                           // Optimized
double cube = double a * double a * double a;                   // Non-optimized
double cube = double a * double square;                       // Optimized
double quad = double a * double a * double a * double a;          // Non-optimized
double quad = double square * double square;                  // Optimized

pow方法:在無法使用乘積的情況下可以使用pow方法。

double cube = Math.pow(base, exponent);

不到萬不得已不要使用Math.pow。比方說,當指數是小數的時候。因為Math.pow要比乘積慢300-600倍左右。

如何處理空指標異常

空指標異常是Java中很常見的異常。當你嘗試呼叫一個null物件上的方法時便會丟擲這個異常。比如:

int noOfStudents = school.listStudents().count;

在上述例子中,school為空或者listStudents()為空都可能會丟擲了NullPointerException。因此最好檢查下物件是否為空以避免類似情況。

private int getListOfStudents(File[] files) {
      if (files == null)
        throw new NullPointerException("File list cannot be null");
    }

JSON編碼

JSON是資料儲存及傳輸的一種協議。與XML相比,它更易於使用。由於它非常輕量級以及自身的一些特性,現在JSON在網路上已經是越來越流行了。常見的資料結構都可以編碼成JSON然後在各個網頁間自由地傳輸。不過在開始編碼前,你得先安裝一個JSON解析器。在下面的例子中,我們將使用json.simple庫來完成這項工作 (https://code.google.com/p/json-simple/)。

下面是編碼成JSON串的一個簡單的例子。

import org.json.simple.JSONObject;
import org.json.simple.JSONArray;

public class JsonEncodeDemo {

    public static void main(String[] args) {

        JSONObject obj = new JSONObject();
        obj.put("Novel Name", "Godaan");
        obj.put("Author", "Munshi Premchand");

        JSONArray novelDetails = new JSONArray();
        novelDetails.add("Language: Hindi");
        novelDetails.add("Year of Publication: 1936");
        novelDetails.add("Publisher: Lokmanya Press");

        obj.put("Novel Details", novelDetails);

        System.out.print(obj);
    }
}

輸出:

{"Novel Name":"Godaan","Novel Details":["Language: Hindi","Year of Publication: 1936","Publisher: Lokmanya Press"],"Author":"Munshi Premchand"}

JSON解析

開發人員要想解析JSON串,首先你得知道它的格式。下面例子有助於你來理解這一點:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

public class JsonParseTest {

    private static final String filePath = "//home//user//Documents//jsonDemoFile.json";

    public static void main(String[] args) {

        try {
            // read the json file
            FileReader reader = new FileReader(filePath);
            JSONParser jsonParser = new JSONParser();
            JSONObject jsonObject = (JSONObject)jsonParser.parse(reader);

            // get a number from the JSON object
            Long id =  (Long) jsonObject.get("id");
            System.out.println("The id is: " + id);           

            // get a String from the JSON object
            String   type = (String) jsonObject.get("type");
            System.out.println("The type is: " + type);

            // get a String from the JSON object
            String   name = (String) jsonObject.get("name");
            System.out.println("The name is: " + name);

            // get a number from the JSON object
            Double ppu =  (Double) jsonObject.get("ppu");
            System.out.println("The PPU is: " + ppu);

            // get an array from the JSON object
            System.out.println("Batters:");
            JSONArray batterArray= (JSONArray) jsonObject.get("batters");
            Iterator i = batterArray.iterator();
            // take each value from the json array separately
            while (i.hasNext()) {
                JSONObject innerObj = (JSONObject) i.next();
                System.out.println("ID "+ innerObj.get("id") + 
                        " type " + innerObj.get("type"));
            }

            // get an array from the JSON object
            System.out.println("Topping:");
            JSONArray toppingArray= (JSONArray) jsonObject.get("topping");
            Iterator j = toppingArray.iterator();
            // take each value from the json array separately
            while (j.hasNext()) {
                JSONObject innerObj = (JSONObject) j.next();
                System.out.println("ID "+ innerObj.get("id") + 
                        " type " + innerObj.get("type"));
            }

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ParseException ex) {
            ex.printStackTrace();
        } catch (NullPointerException ex) {
            ex.printStackTrace();
        }

    }

}

jsonDemoFile.json

{
    "id": 0001,
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    "batters":
        [
            { "id": 1001, "type": "Regular" },
            { "id": 1002, "type": "Chocolate" },
            { "id": 1003, "type": "Blueberry" },
            { "id": 1004, "type": "Devil's Food" }
        ],
    "topping":
        [
            { "id": 5001, "type": "None" },
            { "id": 5002, "type": "Glazed" },
            { "id": 5005, "type": "Sugar" },
            { "id": 5007, "type": "Powdered Sugar" },
            { "id": 5006, "type": "Chocolate with Sprinkles" },
            { "id": 5003, "type": "Chocolate" },
            { "id": 5004, "type": "Maple" }
        ]
}
The id is: 1
The type is: donut
The name is: Cake
The PPU is: 0.55
Batters:
ID 1001 type Regular
ID 1002 type Chocolate
ID 1003 type Blueberry
ID 1004 type Devil's Food
Topping:
ID 5001 type None
ID 5002 type Glazed
ID 5005 type Sugar
ID 5007 type Powdered Sugar
ID 5006 type Chocolate with Sprinkles
ID 5003 type Chocolate
ID 5004 type Maple

簡單字串查詢

Java提供了一個庫函式叫做indexOf()。這個方法可以用在String物件上,它返回的是要查詢的字串所在的位置序號。如果查詢不到則會返回-1。

列出目錄下的檔案

你可以用下面的程式碼來列出目錄下的檔案。這個程式會遍歷某個目錄下的所有子目錄及檔案,並儲存到一個陣列裡,然後通過遍歷陣列來列出所有檔案。

import java.io.*;

public class ListContents {
    public static void main(String[] args) {
        File file = new File("//home//user//Documents/");
        String[] files = file.list();

        System.out.println("Listing contents of " + file.getPath());
        for(int i=0 ; i < files.length ; i++)
        {
            System.out.println(files[i]);
        }
    }
}

一個簡單的IO程式

Java提供了FileInputStream以及FileOutputStream類來進行檔案的讀寫操作。FileInputStream的構造方法會接收輸入檔案的路徑作為入參然後建立出一個檔案的輸入流。同樣的,FileOutputStream的構造方法也會接收一個檔案路徑作為入參然後建立出檔案的輸出流。在處理完檔案之後,一個很重要的操作就是要記得”close”掉這些流。

import java.io.*;

public class myIODemo {
    public static void main(String args[]) throws IOException {
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("//home//user//Documents//InputFile.txt");
            out = new FileOutputStream("//home//user//Documents//OutputFile.txt");

            int c;
            while((c = in.read()) != -1) {
                out.write(c);
            }
        } finally {
            if(in != null) {
                in.close();
            }
            if(out != null) {
                out.close();
            }
        }
    }
}

在Java中執行某個shell命令

Java提供了Runtime類來執行shell命令。由於這些是外部的命令,因此異常處理就顯得異常重要。在下面的例子中,我們將通過一個簡單的例子來演示一下。我們會在shell命令列中開啟一個pdf檔案。

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ShellCommandExec {

    public static void main(String[] args) {
        String gnomeOpenCommand = "gnome-open //home//user//Documents//MyDoc.pdf";

        try {
            Runtime rt = Runtime.getRuntime();
            Process processObj = rt.exec(gnomeOpenCommand);

            InputStream stdin = processObj.getErrorStream();
            InputStreamReader isr = new InputStreamReader(stdin);
            BufferedReader br = new BufferedReader(isr);

            String myoutput = "";

            while ((myoutput=br.readLine()) != null) {
                myoutput = myoutput+"/n";
            }
            System.out.println(myoutput);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用正則

正規表示式的結構摘錄如下(來源: Oracle官網)

字元

x 字元x
/ 反斜槓
/0n 8進位制值為0n的字元(0<=n<=7)
/0nn
/0mnn 8進位制值為0mnn的字元(0 <= m <= 3, 0<=n<=7)
/xhh 16進位制值為0xhh的字元
/uhhhh 16進位制值為0xhhhh的字元
/x{h…h} 16進位制值為0xh…h的字元(Character.MINCODEPOINT <= 0xh…h <= Character.MAXCODEPOINT)
/t 製表符(‘/u0009′)
/n 換行符(‘/u000A’)
/r 回車(‘/u000D’)
/f 分頁符(‘/u000C’)
/a 警告符(‘/u0007′)
/e ESC(‘/u001B’)
/cx ctrl+x

字元分類

[abc] a, b或c
[^abc] abc以外的任意字元
[a-zA-Z] a到z以及A到Z
[a-d[m-p]] a到d或者m到p[a-dm-p]則是取並集
[a-z&&[def]] d,e或f(交集)
[ad-z]
[a-z&&[^bc]] a到z但不包括b和c
[a-z&&[^m-p]] a到z但不包括mp:也就是[a-lq-z]

預定義字元

. 任意字元,有可能包括換行符
/d 0到9的數字
/D 0到9以外的字元
/s 空格符[ /t/n/x0B/f/r]
/S 非空格符[^/s]
/w 字母[a-zA-Z_0-9]
/W 非字母[^/w]

邊界匹配

^ 行首
$ 行末
/b 單詞邊界
/A 輸入的起始位置
/G 前一個匹配的末尾
/Z 輸入的結束位置,僅用於最後的結束符
/z 輸入的結束位置
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexMatches
{
    private static String pattern =  "^[_A-Za-z0-9-]+(//.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(//.[A-Za-z0-9]+)*(//.[A-Za-z]{2,})$";
    private static Pattern mypattern = Pattern.compile(pattern);

    public static void main( String args[] ){

        String valEmail1 = "testemail@domain.com";
        String invalEmail1 = "....@domain.com";
        String invalEmail2 = ".$$%%@domain.com";
        String valEmail2 = "test.email@domain.com";

        System.out.println("Is Email ID1 valid? "+validateEMailID(valEmail1));
        System.out.println("Is Email ID1 valid? "+validateEMailID(invalEmail1));
        System.out.println("Is Email ID1 valid? "+validateEMailID(invalEmail2));
        System.out.println("Is Email ID1 valid? "+validateEMailID(valEmail2));

    }

    public static boolean validateEMailID(String emailID) {
        Matcher mtch = mypattern.matcher(emailID);
        if(mtch.matches()){
            return true;
        }
        return false;
    }    
}

Java Swing的簡單示例

有了Java的swing,你便可以編寫GUI應用了。Java所提供的javax包中就包含了swing。使用swing來編寫GUI程式首先需要繼承下JFrame。然後在裡面新增Box,然後便可以往裡面新增諸如按鈕,多選按鈕,文字框等控制元件了。這些Box是放在Container的最外層的。

import java.awt.*; 
import javax.swing.*;  

public class SwingsDemo extends JFrame 
{ 
    public SwingsDemo() 
    {
        String path = "//home//user//Documents//images";
        Container contentPane = getContentPane(); 
        contentPane.setLayout(new FlowLayout());   

        Box myHorizontalBox = Box. createHorizontalBox();  
        Box myVerticleBox = Box. createVerticalBox();   

        myHorizontalBox.add(new JButton("My Button 1")); 
        myHorizontalBox.add(new JButton("My Button 2")); 
        myHorizontalBox.add(new JButton("My Button 3"));   

        myVerticleBox.add(new JButton(new ImageIcon(path + "//Image1.jpg"))); 
        myVerticleBox.add(new JButton(new ImageIcon(path + "//Image2.jpg"))); 
        myVerticleBox.add(new JButton(new ImageIcon(path + "//Image3.jpg")));   

        contentPane.add(myHorizontalBox); 
        contentPane.add(myVerticleBox);   

        pack(); 
        setVisible(true);
    } 

    public static void main(String args[]) { 
        new SwingsDemo(); 
    }  
}

使用Java播放音訊

在Java中,播放音訊是一個很常見的需求,尤其是在遊戲開發裡面。

下面這個DEMO演示瞭如何在Java中播放音訊。

import java.io.*;
import java.net.URL;
import javax.sound.sampled.*;
import javax.swing.*;

// To play sound using Clip, the process need to be alive.
// Hence, we use a Swing application.
public class playSoundDemo extends JFrame {

   // Constructor
   public playSoundDemo() {
      this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      this.setTitle("Play Sound Demo");
      this.setSize(300, 200);
      this.setVisible(true);

      try {
         URL url = this.getClass().getResource("MyAudio.wav");
         AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);
         Clip clip = AudioSystem.getClip();
         clip.open(audioIn);
         clip.start();
      } catch (UnsupportedAudioFileException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      } catch (LineUnavailableException e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) {
      new playSoundDemo();
   }
}

匯出PDF檔案

將表格匯出成pdf也是一個比較常見的需求。通過itextpdf,匯出pdf也不是什麼難事。

import java.io.FileOutputStream;
import com.itextpdf.text.Document;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;

public class DrawPdf {

      public static void main(String[] args) throws Exception {
        Document document = new Document();
        PdfWriter.getInstance(document, new FileOutputStream("Employee.pdf"));
        document.open();

        Paragraph para = new Paragraph("Employee Table");
        para.setSpacingAfter(20);
        document.add(para);

        PdfPTable table = new PdfPTable(3);
        PdfPCell cell = new PdfPCell(new Paragraph("First Name"));

        table.addCell(cell);
        table.addCell("Last Name");
        table.addCell("Gender");
        table.addCell("Ram");
        table.addCell("Kumar");
        table.addCell("Male");
        table.addCell("Lakshmi");
        table.addCell("Devi");
        table.addCell("Female");

        document.add(table);

        document.close();
      }
    }

郵件傳送

在Java中傳送郵件也很簡單。你只需裝一下Java Mail這個jar包,放到你的類路徑裡即可。在下面的程式碼中,我們設定了幾個基礎屬性,然後便可以傳送郵件了:

import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;

public class SendEmail
{
    public static void main(String [] args)
    {    
        String to = "recipient@gmail.com";
        String from = "sender@gmail.com";
        String host = "localhost";

        Properties properties = System.getProperties();
        properties.setProperty("mail.smtp.host", host);
        Session session = Session.getDefaultInstance(properties);

        try{
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(from));

            message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));

            message.setSubject("My Email Subject");
            message.setText("My Message Body");
            Transport.send(message);
            System.out.println("Sent successfully!");
        }
        catch (MessagingException ex) {
            ex.printStackTrace();
        }
    }
}

計算時間

許多程式都需要精確的時間計量。Java提供了一個System的靜態方法來支援這一功能:

currentTimeMillis():返回當前時間自新紀元時間以來的毫秒值,long型別。

long startTime = System.currentTimeMillis();
long estimatedTime = System.currentTimeMillis() - startTime;

nanoTime():返回系統計時器當前的精確時間,納秒值,這也是long型別。nanoTime()主要是用於計算相對時間而非絕對時間。

long startTime = System.nanoTime();
long estimatedTime = System.nanoTime() - startTime;

圖片縮放

圖片縮放可以通過AffineTransform來完成。首先要生成一個輸入圖片的圖片緩衝,然後通過它來渲染出縮放後的圖片。

import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class RescaleImage {
  public static void main(String[] args) throws Exception {
    BufferedImage imgSource = ImageIO.read(new File("images//Image3.jpg"));
    BufferedImage imgDestination = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = imgDestination.createGraphics();
    AffineTransform affinetransformation = AffineTransform.getScaleInstance(2, 2);
    g.drawRenderedImage(imgSource, affinetransformation);
    ImageIO.write(imgDestination, "JPG", new File("outImage.jpg"));
  }
}

捕獲滑鼠動作

實現了MouseMotionListner介面後,便可以捕獲滑鼠事件了。 當滑鼠進入到某個特定區域時便會觸發MouseMoved事件,你便能捕獲到這個移動的動作了。通過一個例子來看下:

import java.awt.event.*;
import javax.swing.*;

public class MouseCaptureDemo extends JFrame implements MouseMotionListener
{
    public JLabel mouseHoverStatus;

    public static void main(String args[]) 
    {
        new MouseCaptureDemo();
    }

    MouseCaptureDemo() 
    {
        setSize(500, 500);
        setTitle("Frame displaying Coordinates of Mouse Motion");

        mouseHoverStatus = new JLabel("No Mouse Hover Detected.", JLabel.CENTER);
        add(mouseHoverStatus);
        addMouseMotionListener(this);
        setVisible(true);
    }

    public void mouseMoved(MouseEvent e) 
    {
        mouseHoverStatus.setText("Mouse Cursor Coordinates => X:"+e.getX()+" | Y:"+e.getY());
    }

    public void mouseDragged(MouseEvent e) 
    {}
}

FileOutputStream Vs. FileWriter

在Java中有兩種寫檔案的方式:FileOutputStream與FileWriter。開發人員經常會在它們之間猶豫不決。下面這個例子能幫忙你更好地理解在不同的場景下應該選擇何種方案。首先我們來看一下實現:

使用FileOutputStream:

File foutput = new File(file_location_string);
FileOutputStream fos = new FileOutputStream(foutput);
BufferedWriter output = new BufferedWriter(new OutputStreamWriter(fos));
output.write("Buffered Content");

使用FileWriter:

FileWriter fstream = new FileWriter(file_location_string);
BufferedWriter output = new BufferedWriter(fstream);
output.write("Buffered Content");

根據Java的介面規範:

FileOutputStream是用於寫入原始位元組流比如圖片流資料。如果是要寫入字元流,則應該考慮使用FileWriter。

這樣就很清楚了,寫圖片應該使用FileOutputStream而寫文字則應該選擇FileWriter。

附加建議

集合的使用

Java提供了許多集合類——比如,Vector,Stack,Hashtable等。所以鼓勵開發人員儘可能地使用這些集合類有如下原因:

  • 使用集合使得程式碼的可重用度更高。
  • 集合類使得程式碼的結構更良好,更易於理解與維護。
  • 最重要的是這些集合類都經過充分的測試,程式碼質量很高。

1-50-500規則

在大型軟體系統中,程式碼的可維護性是件很有挑戰的工作。新加入的開發人員經常會抱怨這些情況:單片程式碼(Monolithic Code),義大利麵式程式碼(spaghetti code, 常用於描述捆綁在一起並且低內聚的類和方法)。保持程式碼的整潔與可維護有一條很簡單的規則:

  • 10:包內的類不超過10個
  • 50:方法的程式碼行數不超過50
  • 500:類的程式碼行數不超過500
  1. SOLID設計準則
  2. SOLID是Robert Martin提出的一套設計準則的簡稱。根據他的準則:

一個類應當有僅只有一個任務/職責。執行多個任務的類會讓人覺得困惑。

單一職責原則
開閉原則 開發人員應當優先考慮擴充套件現有的軟體功能,而不是是修改它。
里氏替換原則 子類必須能夠替換掉他們的父型別
介面隔離原則 和單一職責原則類似,但它特指的是介面層。每個介面都應當只負責一項任務。
依賴反轉原則 依賴抽象而不是具體實現。也就是說每個模組都應當通過一個抽象層與其它模組進行解耦。

設計模式的使用

設計模式能幫助開發人員更好地在軟體中應用軟體的設計準則。它還為開發人員提供了跨語言的通用平臺。設計模式中的標準術語能讓開發人員更容易進行溝通。

關於文件

不要上來就開始寫程式碼。制定計劃,準備,編寫文件,檢查然後再去實現。首先,先把需求記下來。然後去準備設計文件。合理地去假設舉證。互相review方案然後進行確認。

使用equals而非==

==是用來比較物件引用的,它會檢查兩個運算元指向的是不是同一個物件(不是相同的物件,而是同一個物件)。而”equals”則比較的是兩個字串是不是相同(假設是字串物件)。

避免使用浮點數

只有當確實有必要的時候才使用浮點數。比方說,使用浮點數來表示盧比或者派薩就很容易產生問題——這種情況應當使用BigDecimal。而浮點數更多地是用於測量。

相關文章