顯示具有 Java 標籤的文章。 顯示所有文章
顯示具有 Java 標籤的文章。 顯示所有文章

2012年11月12日 星期一

資料結構容量大小的調整

StringBuilder 和 StringBuffer 底層都使用char[]來做為資料儲存庫
而且常常需要調整容量大小
調整容量大小的方法就是配置一個容量更大的新的char[]
接著將舊資料複製過來後再拋棄舊的char[]
類似的調整過程也可能發生在使用陣列當作底層資料儲存庫的Java SE Collection

依OpenJDK的實作方式
當儲存文字超過底層資料儲存庫的容量時
會配置一個原本容量兩倍大的新char陣列,而底層的char陣列大小預設為16
實務上很少StringBuffer 或StringBuilder的物件最後只使用了16以內的char元素
要避免StringBuffer 或StringBuilder調整大小的動作,我們可以指定大小給其建構子

如ArrayList、Vector、HashMap和ConcurrenthashMap等Collections因使用陣列儲存資料
同StringBuffer 或StringBuilder,很容易在增加元素時引發調整大小的動作
需要額外的CPU週期來配置新陣列、複製舊陣列的資料,將來也需要回收舊陣列
以HashMap為例,預設建構子的容量是16筆資料,也是經常不夠用
其資料擴張時也是使用原本陣列2倍大的新陣列
又因每次調整時都要計算雜湊(hash)值,當資料量大時耗費的成本也更驚人
遇到這種情形時,比較好的處理方式就是直接將計算容量的算式傳給HashMap的建構子

2012年10月24日 星期三

Java 數值轉換函式區別 - 以Integer.parseInt()、Integer.valueOf()為例

之前對於Java在數值轉換中為什麼會提供這兩種功能幾乎一樣的方法感到疑惑
看了原始碼就可以得到解答
public static Integer valueOf(String s) throws NumberFormatException {
  return new Integer(parseInt(s, 10));
}

public static int parseInt(String s) throws NumberFormatException {
  return parseInt(s,10);
}

答案在於回傳型別的不同
parseInt()回傳 int 基本型別,valueOf()則回傳Integer型別
除了Integer外,其他的數值型別也有同樣的方法(Double、Float...etc)

在Java1.4版本以前這兩種方法有差異,但在後來版本裡因為有auto boxing機制而沒差了

2012年10月22日 星期一

Excel 開啟產出的 .CSV檔為亂碼的解決方式

CSV檔為以逗號區隔欄位,用來儲存資料的純文字格式
指定了編碼的純文字格式應該是與亂碼無緣
但有時以微軟的Excel開啟.csv檔卻會發現內容為亂碼
發生的原因是微軟內部系統處理格式時會在開頭暗中寫入一串判別的字碼
而微軟系統就以文字檔案中是否含這串暗碼來判定要以UTF-8或ASCII格式讀取檔案

所以我們若要讓我們產出的CSV檔能正確的被Excel讀取,就要在檔案開頭加入暗碼


public CSVWriter(OutputStream out){
    this.out=out;

    //Corresponding to Microsoft Excel, make file won't open with scrambles in Excel
    byte[] BOM_UTF8 = { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF };
    try {
    out.write(BOM_UTF8);
    } catch (IOException e) {
    System.err.println("BOM寫入CSV失敗");
    }
    ....
}

2012年10月3日 星期三

[轉載] POI設置EXCEL單元格格式為文本、小數、百分比、貨幣、日期、科學計數法和中文大寫

轉載來源


以下將要介紹的每一種都會用到這三行中的變量

            HSSFWorkbook demoWorkBook = new HSSFWorkbook();  
            HSSFSheet demoSheet = demoWorkBook.createSheet("The World's 500 Enterprises");  
            HSSFCell cell = demoSheet.createRow(0).createCell(0);

第一種:日期格式

            cell.setCellValue(new Date(2008,5,5));
            //set date format
            HSSFCellStyle cellStyle = demoWorkBook.createCellStyle();
            HSSFDataFormat format= demoWorkBook.createDataFormat();
            cellStyle.setDataFormat(format.getFormat("yyyy年m月d日"));
            cell.setCellStyle(cellStyle);

第二種:保留兩位小數格式
            cell.setCellValue(1.2);
            HSSFCellStyle cellStyle = demoWorkBook.createCellStyle();
            cellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00"));
            cell.setCellStyle(cellStyle);

這裡與上面有所不同,用的是HSSFDataFormat.getBuiltinFormat()方法,之所以用這個,是因為0.00是Excel內嵌的格式

第三種:貨幣格式

            cell.setCellValue(20000);
            HSSFCellStyle cellStyle = demoWorkBook.createCellStyle();
            HSSFDataFormat format= demoWorkBook.createDataFormat();
            cellStyle.setDataFormat(format.getFormat("¥#,##0"));
            cell.setCellStyle(cellStyle);

第四種:百分比格式

            cell.setCellValue(20);
            HSSFCellStyle cellStyle = demoWorkBook.createCellStyle();
            cellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00%"));
            cell.setCellStyle(cellStyle);
  此種情況跟第二種一樣

第五種:中文大寫格式

            cell.setCellValue(20000);
            HSSFCellStyle cellStyle = demoWorkBook.createCellStyle();
            HSSFDataFormat format= demoWorkBook.createDataFormat();
            cellStyle.setDataFormat(format.getFormat("[DbNum2][$-804]0"));
            cell.setCellStyle(cellStyle);

第六種:科學計數法格式

            cell.setCellValue(20000);
            HSSFCellStyle cellStyle = demoWorkBook.createCellStyle();
            cellStyle.setDataFormat( HSSFDataFormat.getBuiltinFormat("0.00E+00"));
            cell.setCellStyle(cellStyle);
此種情況也與第二種情況一樣

2012年10月2日 星期二

[轉載] Java SE 6.0 內建 Javascript 引擎測試

轉載來源

前言 :
Java SE 6.0 採納 JSR 223 建議,使得 Java 能夠與眾多 Script 語言協同運作
透過 Java 定義的標準 API,能夠讓 Script 直接存取和控制 Java 的物件
而Java 應用程式當中也能夠直接內嵌 Script 環境

範例說明 :
底下將會帶幾個範例, 來說明這個 Java 的 JavaScript 引擎能夠達成的工作 :
- 範例01 : 第一個 Hello World 程式, 直接將 JavaScript 代碼丟入 JS Engine 的 eval 函式執行.
package john.js;

import javax.script.*;

public class JSHello {
    public static void main(String args[]) {
        ScriptEngineManager mgr = new ScriptEngineManager(); // 建立一個 Script 管理員
        ScriptEngine jsEngine = mgr.getEngineByName("JavaScript"); // 取得 Javascript 引擎
        try {
          jsEngine.eval("print('Hello, world!')"); // 透過 Javascript 引擎解譯 print('hello, world') 命令
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }    
    }
}

- 範例02:執行外部 javasript 檔案, 這裡是直接把含 JavaScript 代碼(Hello.js) 的內容透過 InputStream 丟給 JS Engine 的 eval API 執行.
package john.js;

import javax.script.*;
import java.io.*;

public class JSRunExtendFile {
    public JSRunExtendFile() {
        ScriptEngineManager engineMgr = new ScriptEngineManager();
        ScriptEngine engine = engineMgr.getEngineByName("ECMAScript");      
        try {
            InputStream is = new FileInputStream(new File("Hello.js"));
            Reader reader = new InputStreamReader(is);
            engine.eval(reader);
        } catch (ScriptException ex) {
            ex.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        JSRunExtendFile ref = new JSRunExtendFile();  
    }
}

- 範例03:透過 Invocable 介面喚起 Javascript 函數.
package john.js;

import javax.script.*;

public class JSInvokeFunction {

    public static void main(String[] args) {
        ScriptEngineManager engineMgr = new ScriptEngineManager();
        ScriptEngine jsEngine = engineMgr.getEngineByName("ECMAScript");
        try {
            jsEngine.eval("function sayHello() {"
                    + "  println('Hello, world!');" + "}");
            Invocable invocableEngine = (Invocable) jsEngine;
            invocableEngine.invokeFunction("sayHello");
        } catch (ScriptException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException nsme) {
            nsme.printStackTrace();
        }
    }
}

- 範例04:Script 直接存取 Java 物件
package john.js;

import javax.script.*;
import java.util.*;

public class JSAccessJavaObject {

    public static void main(String[] args) {
        List namesList = new ArrayList();
        namesList.add("Jill");
        namesList.add("Bob");
        namesList.add("Laureen");
        namesList.add("Ed");
        ScriptEngineManager engineMgr = new ScriptEngineManager();
        ScriptEngine jsEngine = engineMgr.getEngineByName("ECMAScript");
        jsEngine.put("namesListKey", namesList); // 將 java 變數指定給 javascript 環境中的
                                                    // nameListKey 變數
        System.out.println("Executing in script environment...");
        try {
            // 列印 Java 的 nameList 變數內容,並新增一個 Dana 字串資料到 List 尾端
            jsEngine.eval("var x;" + "var names = namesListKey.toArray();"
                    + "for(x in names) {" + "  println(names[x]);" + "}"
                    + "namesListKey.add(\"Dana\");");
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
        System.out.println("Executing in Java environment...");
        for (String name : namesList) {
            System.out.println(name);
        }
    }
}

- 範例05:透過 Invocable 將參數值傳遞給 Javascript 中的函數
package john.js;

import javax.script.*;
import java.util.*;

public class JSPassValueToFunction {

    public static void main(String[] args) {
        List namesList = new ArrayList();
        namesList.add("Jill");
        namesList.add("Bob");
        namesList.add("Laureen");
        namesList.add("Ed");
        ScriptEngineManager engineMgr = new ScriptEngineManager();
        ScriptEngine jsEngine = engineMgr.getEngineByName("ECMAScript");
        Invocable invocableEngine = (Invocable) jsEngine;
        try {
            jsEngine.eval("function printNames1(namesList) {" + "  var x;"
                    + "  var names = namesList.toArray();"
                    + "  for(x in names) {" + "    println(names[x]);" + "  }"
                    + "}" + "function addName(namesList, name) {println('addName: Add '+name);"
                    + "  namesList.add(name);" + "}");
            invocableEngine.invokeFunction("printNames1", namesList);
            invocableEngine.invokeFunction("addName", namesList, "Dana");
        } catch (ScriptException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        }
    }
}

補充說明 :
* 純種 Java 與 Javascript 結合
將Script整合進應用程式所能達到的彈性空間的確十分強大
如果能在我們開發的系統也提供Scripting的能力,除了強化擴充性之外,整個系統的質感也是提升不少…爽度up!up!
但以往要達成這目標,有個難處,就是解釋並執行Script用的Interpreting Engine那裡來,自己寫顯然是太累了…。
Java 6這回帶來了好消息:Scripting API,在Java系統中結合Scripting再也不難了,你只需要import javax.script.*; ...

[轉載] javax.script包探秘 javascript與java交互執行

轉載來源

這個包用來和JavaScript進行互操作,比如JAVA類可以調用JavaScript中的方法,而JS也可調用 JAVA中的方法.

1、可用的腳本引擎
Java 6提供對執行腳本語言的支持,這個支持來自於JSR223規範,對應的包是javax.script
預設情況下,Java 6只支持javascript腳本,它底層的實現是Mozilla Rhino,它是個純Java的javascript實現
可以通過下面的代碼列出當前環境中支持的腳本引擎:

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
// evaluate JavaScript code from String
engine.eval("print('Welocme to java world')");
System.out.println();
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = manager.getEngineFactories();
for (ScriptEngineFactory f : factories) {
    System.out.println("egine name:" + f.getEngineName() + "\n"
            + "engine version:" + f.getEngineVersion() + "\n"
            + "language name:" + f.getLanguageName());
}

結果輸出:
Welocme to java world
egine name:Mozilla Rhino
engine version:1.6 release 2
language name:ECMAScript

可以看到,Java內置只支持javascript一種腳本
但是,只要遵循 JSR223,便可以擴展支持多種腳本語言
可以從https://scripting.dev.java.net/上查找當前已被支持的腳本的第三方庫

2、hello script
示例如上面的代碼所示。
使用的API還是很簡單的,ScriptEngineManager是ScriptEngine的工廠,實例化該工廠的時候會加載可用的所有腳本引擎
從工廠中創建ScriptEngine可以使用getEngineByName、getEngineByExtension或 getEngineByMimeType來得到
只要參數名字能對上。執行腳本調用eval方法即可(效果等同於javascript中的eval)

3、傳遞變量
可以向腳本中傳遞變量,使得Java代碼可以和腳本代碼交互,示例如下:

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
engine.put("a", 4);
engine.put("b", 6);
try {
    Object maxNum = engine
            .eval("function max_num(a,b){return (a>b)?a:b;}max_num(a,b);");
    System.out.println("max_num:" + maxNum);
} catch (Exception e) {
    e.printStackTrace();
}
輸出內容:max_num:6

對於上面put的變量,它作用於自身engine範圍內,也就是ScriptContext.ENGINE_SCOPE
put 的變量放到一個叫Bindings的Map中,可以通過 engine.getBindings(ScriptContext.ENGINE_SCOPE).get(「a」);得到put的內容
和 ENGINE_SCOPE相對,還有個ScriptContext.GLOBAL_SCOPE 作用域
其作用的變量是由同一ScriptEngineFactory創建的所有ScriptEngine共享的全局作用域。

4、動態調用
上面的例子中定義了一個javascript函數max_num,可以通過Invocable接口來多次調用腳本庫中的函數
Invocable接口是 ScriptEngine可選實現的接口。下面是個使用示例:

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
try {
    engine.eval("function max_num(a,b){return (a>b)?a:b;}");
    Invocable invoke = (Invocable) engine;
    Object maxNum = invoke.invokeFunction("max_num", 4, 6);
    System.out.println(maxNum);
    maxNum = invoke.invokeFunction("max_num", 7, 6);
    System.out.println(maxNum);
} catch (Exception e) {
    // TODO: handle exception
}

上面的invokeFunction,第一個參數調用的腳本函數名,後面跟的可變參數是對應的腳本函數參數
Invocable還有個很酷的功能,就是動態實現接口,它可以從腳本引擎中得到Java Interface 的實例
也就是說,可以定義個一個Java接口,其實現是由腳本完成
以上面的例子為例,定義接口JSLib,該接口中的函數和javascript中的函數簽名保持一致:

public interface JSLib {
    public int max_num(int a, int b);
}
[java] view plaincopyprint?
// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
try {
    engine.eval("function max_num(a,b){return (a>b)?a:b;}");
    Invocable invoke = (Invocable) engine;
    JSLib jslib = invoke.getInterface(JSLib.class);
    int maxNum = jslib.max_num(4, 6);
    System.out.println(maxNum);
} catch (Exception e) {
    // TODO: handle exception
}

5、使用Java對象
可以在javascript中使用Java代碼,這確實是很酷的事情
在Rhino中,可以通過importClass導入一個類,也可以通過importPackage導入一個包,也可以直接使用全路經的類
在創建對象時,new也不是必須的。示例代碼如下:

try {
    String script = "var list = java.util.ArrayList();list.add(\"kafka0102\");print(list.get(0));";
    engine.eval(script);
} catch (Exception e) {
    e.printStackTrace();
}

又例如:

try {
    engine.eval("importPackage(javax.swing);" + "var optionPane = "
            + "  JOptionPane.showMessageDialog(null, 'Hello, world!');");
} catch (ScriptException ex) {
    ex.printStackTrace();
}

6、編譯執行
腳本引擎默認是解釋執行的,如果需要反覆執行腳本,可以使用它的可選接口Compilable來編譯執行腳本,以獲得更好的性能,示例代碼如下:

try {
    Compilable compEngine = (Compilable) engine;
    CompiledScript script = compEngine
            .compile("function max_num(a,b){return (a>b)?a:b;}");
    script.eval();
    Invocable invoke = (Invocable) engine;
    Object maxNum = invoke.invokeFunction("max_num", 4, 6);
    System.out.println(maxNum);
} catch (Exception e) {
    e.printStackTrace();
}

7、總結
除了上面提到的特性,腳本引擎還有一些不錯的功能
比如可以執行腳本文件,可以由多線程異步執行腳本等功能
引入腳本引擎,可以對一些配置擴展和業務規則做更強大而靈活的支持,也方便使用者選擇自己熟悉的腳本語言來編寫業務規則等

public class JSTest {
    public static void main(String[] args) throws Exception {
        // 根據js的後綴名生成一個解析JS的腳本解析引擎

        ScriptEngine engin = new ScriptEngineManager()
                .getEngineByExtension("js");
        // 查詢一下這個引擎是否實現了下面很實用的接口

        System.out.println(engin instanceof Invocable);
        // 聲明兩個對象,傳入到JS裡面去

        JFrame jf = new JFrame("test");
        List list = new ArrayList();
        // 得到綁定的鍵值對象,把當前的兩個JAVA對象放進去

        Bindings bind = engin.createBindings();
        bind.put("jf", jf);
        bind.put("list", list);
        // 把綁定下的鍵值對象放進去,作用域是當前引擎的範圍

        engin.setBindings(bind, ScriptContext.ENGINE_SCOPE);
        // 用引擎執行一段寫在JS文件裡面的代碼

        Object obj = engin.eval(new FileReader("test.js"));
        // 這個時候返回值當然 是null了

        System.out.println(obj);
        // 把當前的引擎強制轉為Invocable,這樣就可以調用定義在JS文件裡面的一個一個函數了

        Invocable in = (Invocable) engin;
        // 得到了從JS裡面返回來的對象

        List l = (List) in.invokeFunction("getNames");
        System.out.println(l);
        // 調用一下定義在JS裡面的另一個函數

        in.invokeFunction("testJS");
        // 最後調用一個函數,該函數可以使我們前面定義的窗體顯示出來

        in.invokeFunction("doSth");
    }
}
下面是定義在test.js裡面的內容
[javascript] view plaincopyprint?
function doSth()
{
    jf.setSize(500,300);
    jf.setVisible(true);
    jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE);  
}  
function getNames()
{   
    list.add("doSth");
    list.add("getNames");
    return list;
}  
function testJS()
{
    print('Hello world!');
}
我們可以看到,在JAVA運行了以後,窗體會顯示出來,並且我們可以接收到從JS解析引擎裡面傳回的數據
當然我們也可以調用一個很普通的JS函數
想像一下,如果我們把我們程序運行時的一些對象都設到Bindings裡面去,那麼我們JS豈不是有很大的自由度了嗎?
因為JS裡面也可以操作我們的Java對象了,並且我們可以像ava編程一樣的對JS編程了,還不用再編譯,馬上就可以運行.靈活性豈不是變得更高了嗎?

參考自:
【1】javax.script包探秘 javascript與java交互執行
http://blog.csdn.net/lvshow/article/details/7252236
【2】Scripting for the Java Platform
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/

2012年9月20日 星期四

POI 自訂方法

copyRow() - 複製Row的method
updateFormula() - 計算Excel公式結果的方法

複製Row的method

POI有cloneSheet()這個複製sheet的方便方法
但是卻缺少複製sheet裡的row的方法
當然考量到公式的計算與合併的儲存格的位置
這個方法須自訂是不太令人意外

private void copyRow(HSSFSheet worksheet, int sourceRowNum, int destinationRowNum) {
    // Get the source & new row
    HSSFRow newRow = worksheet.getRow(destinationRowNum);
    HSSFRow sourceRow = worksheet.getRow(sourceRowNum);

    // If the row exist in destination, push down all rows by 1 else create a new row
    if (newRow != null) {
        worksheet.shiftRows(destinationRowNum, worksheet.getLastRowNum(), 1);
    } else {
        newRow = worksheet.createRow(destinationRowNum);
    }

    // Loop through source columns to add to new row
    for (int i = 0; i < sourceRow.getLastCellNum(); i++) {
        // Grab a copy of the old/new cell
        HSSFCell oldCell = sourceRow.getCell(i);
        HSSFCell newCell = newRow.createCell(i);

        // If the old cell is null jump to next cell
        if (oldCell == null) {
            newCell = null;
            continue;
        }

        // Use old cell style
        newCell.setCellStyle(oldCell.getCellStyle());

        // If there is a cell comment, copy
        if (newCell.getCellComment() != null) {
            newCell.setCellComment(oldCell.getCellComment());
        }

        // If there is a cell hyperlink, copy
        if (oldCell.getHyperlink() != null) {
            newCell.setHyperlink(oldCell.getHyperlink());
        }

        // Set the cell data type
        newCell.setCellType(oldCell.getCellType());

        // Set the cell data value
        switch (oldCell.getCellType()) {
            case Cell.CELL_TYPE_BLANK:
                newCell.setCellValue(oldCell.getStringCellValue());
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                newCell.setCellValue(oldCell.getBooleanCellValue());
                break;
            case Cell.CELL_TYPE_ERROR:
                newCell.setCellErrorValue(oldCell.getErrorCellValue());
                break;
            case Cell.CELL_TYPE_FORMULA:
                newCell.setCellFormula(oldCell.getCellFormula());
                break;
            case Cell.CELL_TYPE_NUMERIC:
                newCell.setCellValue(oldCell.getNumericCellValue());
                break;
            case Cell.CELL_TYPE_STRING:
                newCell.setCellValue(oldCell.getRichStringCellValue());
                break;
        }
    }

    // If there are are any merged regions in the source row, copy to new row
    for (int i = 0; i < worksheet.getNumMergedRegions(); i++) {
        CellRangeAddress cellRangeAddress = worksheet.getMergedRegion(i);
        if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) {
            CellRangeAddress newCellRangeAddress =
                  new CellRangeAddress(newRow.getRowNum(),
                    (newRow.getRowNum() +
                    (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())
                     ),
                    cellRangeAddress.getFirstColumn(),
                    cellRangeAddress.getLastColumn());
            worksheet.addMergedRegion(newCellRangeAddress);
        }
    }
}


計算Excel公式結果的方法

使用POI填入公式時,Excel表並不會將計算後的結果立刻展示
它的作法是顯示出公式(舊版office)或待開啟編輯權限後才計算(office 2010)
以下方法能計算特定行數的Excel公式


private void updateFormula(HSSFWorkbook wb, HSSFSheet sheet, int row){    
    Row r = sheet.getRow(row);    
    Cell cell = null;    
    HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(wb);    
    for(int i = r.getFirstCellNum(); i < r.getLastCellNum(); i++){    
        cell = r.getCell(i);    
        if(cell.getCellType()== Cell.CELL_TYPE_FORMULA)    
            eval.evaluateFormulaCell(cell);    
    }    
}


HSSFFormulaEvaluator提供了evaluateFormulaCell(Cell cell)方法
計算公式保存結果,但不改變公式
而evaluateInCell(Cell cell)方法是計算公式,並將原公式替換為計算結果,
也就是說該單元格的類型從Cell.CELL_TYPE_FORMULA改為Cell.CELL_TYPE_NUMBERIC

HSSFFormulaEvaluator提供了靜態方法evaluateAllFormulaCells(HSSFWorkbook wb)
計算一個Excel文件的所有公式,用起來很方便

2011年6月16日 星期四

Java Inner Class 記事

inner class的作用並不只是隱藏名稱或程式碼
他最大的效果是能有效及完整的達到"多重繼承"-實際上繼承多個non-interface
以下做深入的筆記

inner class的個用途是當建立一個inner class時
inner class可以存取外圍物件的所有元素而無需添加任何飾詞,如下範例:

interface Selector {
  boolean end();
  Object current();
  void next();
}   

public class Sequence {
  private Object[] items;
  private int next = 0;
  public Sequence(int size) { items = new Object[size]; }
  public void add(Object x) {
    if(next < items.length)
      items[next++] = x;
  }
  private class SequenceSelector implements Selector {
    private int i = 0;
    public boolean end() { return i == items.length; }
    public Object current() { return items[i]; }
    public void next() { if(i < items.length) i++; }
  }
  public Selector selector() {
    return new SequenceSelector();
  }   
  public static void main(String[] args) {
    Sequence sequence = new Sequence(10);
    for(int i = 0; i < 10; i++)
      sequence.add(Integer.toString(i));
    Selector selector = sequence.selector();
    while(!selector.end()) {
      System.out.print(selector.current() + " ");
      selector.next();
    }
  }
} /* Output:
0 1 2 3 4 5 6 7 8 9
*/

 範例的inner class的SequenceSelector可以存取其外部的Sequence的類別成員
不論inner class內放置的層次有多深
所有的outer classes的成員,即使是private修飾也都可以被他存取

inner class必須記錄一個reference指向enclosing class(外圍類別)
當取用外部成員時,編譯器會自動用那個隱藏的reference取得
也因為inner class物件產生時必須要關聯至其enclosing class的某個物件
建構inner class物件的同時必須要有enclosing class的物件

inner class可以寫在程式的任意位置
這不是說他們只會在執行那個段落時產生
他們一樣會跟其他class一起被編譯出來,只是在該段落以外無法被使用

假如不需要inner class物件和enclosing class物件之間的連結關係
可以將inner class宣告為static,而這種class被稱為nested class
nested class在生成物件時不需要外圍class的物件,也無法存取外圍class的非靜態物件
一般的inner class不能擁有static data、static fields或nested class,但nested class都可以有

一般而言,interface內不能加入任何程式碼,但nested class卻可以是interface的一部分
任何加到interface的class會自動成為public和static
甚至可以在inner class中時做外圍的interface
如果想寫出讓某interface的不同實作都可以共用的程式碼時可以這麼做,如下:

public interface ClassInInterface {
  void howdy();
  class Test implements ClassInInterface {
    public void howdy() {
      System.out.println("Howdy!");
    }
    public static void main(String[] args) {
      new Test().howdy();
    }
  }
} /* Output:
Howdy!
*/

繼承inner class則必須注意要先建立外圍類別
指向outer class的reference也必須在建構式由我們建立,如下:

class WithInner {
  class Inner {}
}

public class InheritInner extends WithInner.Inner {
  //! InheritInner() {} // Won't compile
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
}

Java Interface 記事

Interface存在的原因有二:
1.能夠向上轉型為多個型別
2.讓客戶端無法產生物件,確保"介面"的設計意義

如果使用的base class不帶任何函式定義式或任何成員變數
應優先選用Interface,其次才考慮abstract class
基本上在確定某些東西會成為base class,優先考量就是使他成為Interface
過度使用Interfaces會帶來額外的複雜度,必須確定有需求再使用

在類別實作多個Interface時
有不同的Interface間採用相同的method命名會導致出錯,應避免此情形

將Interfaces置於某個class中會形成巢狀的Interfaces
巢狀的Interfaces可以為private,意義是實作他的類別在外部不能向上轉型
而巢狀位於另一個Interface內時,那些Interfaces會自動成為public

2011年6月14日 星期二

Java使用多形時可能犯的錯誤

1.覆寫 private method

import static net.mindview.util.Print.*;

public class PrivateOverride{
    private void f(){  print("private f()"); }
    public static void main(String[] args){
        privateOverride po = new Derived();
        po.f();
    }
}

class Derived extends PrivateOverride{
    public void f(){  print("public f()"); }
}
//output:private f()

private method會自動成為final,也會被derived class遮蔽
例子中的Derived的f()是新的method

2.fields及static methods

物件只有一般的method可以是多形
假如base class 與 derived class 有相同的data member
derived class實際上包含了兩個同名的field,其中一個是繼承而來的
只是要呼叫base class的field就必須使用 super.field 的形式來取得
不過在實務上幾乎不會發生,將fields都宣告為private而不直接存取才是避免混淆的方法

static methos是伴隨著物件,而非個別的objects

Java關鍵字-final

final這個關鍵字依照使用的地方不同會有不同的效果及意義
以3個可以使用final的地方來分析:data、method、classes

final data-

告訴編譯器某塊資料是固定不變
但是在用來修飾object reference時須注意
final 會讓reference固定指向一個物件,但該物件的內容卻還是可以更動,array也是相同情況
Java允許產生留白的finals(blank final)
能宣告資料成員為final但不給予初始值,以保持彈性
例如:

public class BlankFinal{
    private final int i = 0;  //Initialized final
    private final int j;         //Blank final
  
    public BlankFianl(){
         j = 1;    //Initialized blank final
    }
    public BlankFianl(int x){
         j = x;    //Initialized blank final

    }
}

編譯器會強迫必須對所有的finals賦值
如果不是在定義處,就得在建構式中以運算式設定其值
此外,Java也允許將引述宣告為final,意指無法在函式中另該引數改指他處
如:  void doSth(final objectA a){...}

final methods-

使用final methods的原因是鎖住這個函式,使繼承者無法進行override
class中所有的private函式自然就會是final
所以將final用在private函式上沒有意義
假使嘗試要override private method,編譯器只會將其視為該class新建立的函式而不會override

final classes-

將class宣告為final表示此class不允許被繼承

2011年6月10日 星期五

Java基本型別資訊

Java在運作上還是會將基本型別轉換成物件
透過自動封箱(autoboxing)將基本型別轉換成外覆類別
Java在class的data member為基本型別時會給予預設值
但在區域變數則不成立並會引發錯誤
所以在建立基本型別的物件時最好還是給訂初始值

基本型別              大小           外覆類型            預設值

boolean                  -               Boolean              false
char                    16-bit           Character           '\u0000'(null)
byte                      8-bit           Byte                   (byte)0
short                   16-bit           Short                  (short)0
int                       32-bit           Integer                0
long                    64-bit           Long                   0L
float                    32-bit           Float                   0.0f
double                64-bit           Double                0.0d

高精度數值
BigInteger       -  提供的整數支援任意精度,可以很精確的表達任意長度的整數數值
BigDecimal    -  提供任意精度的定點數,在需要精確小數點後計算的場合中適用

高精度的以上兩個類別雖然可以被視為外覆類別,但沒有對應的基本型別
在int或float型別能做到的在這兩個型別也能做到
只是必須以函式叫用方式取代基本型別的運算子,且速度比較慢
屬於以速度換取精度

2010年12月8日 星期三

範例 - 刪除目錄及其底下所有文件

      public void deleteAll(File path){
          if(path.exists()){
              if(path.isFile()){ 
                  path.delete(); 
              }else{ 
                  File[] files = path.listFiles();     
                  for(int i = 0; i < files.length; i++){ 
                        deleteAll(files[i]); 
                  }
 
                  path.delete(); 
              }
          }
      }

範例 - 計算硬碟中的資料大小

int uAppendSize = 0;                                     //store uploaded data size info
                                     
File[] fList =TargetDir.listFiles();

for (int j = 0; j < fList.length; j++){
      FileInputStream in = new FileInputStream(fList[j]);
      uAppendSize += in.available();
      in.close();         //must close FileInputStream after use it
}

這個範例會檢查寫入檔案所在的資料夾,並加該資料夾內的所有檔案大小相加
FileInputStream 開啟後務必要記得關閉
否則檔案的刪除、修改等動作會因為檔案被FileInputStream佔用而失敗

2010年9月28日 星期二

Java 物件清除與 finalize() 函式使用

垃圾回收器(garbage collection,簡稱GC)回收不再被使用的物件的記憶體空間
這點是自  C\C++ 以來,高階程式語言很大的改變之一

使用 C\C++ 語言撰寫時,memory leak 是最常發生的嚴重錯誤之一
如果使用完的物件沒有被清除,記憶體空間的資源也無法被釋放
Java的垃圾回收器能回收不再被使用的記憶體空間
但是有些重點必須要注意,垃圾回收器只能回收經由new產生的物件
被占用的特殊記憶體,例如從外部的其他語言獲得的物件,清除方式就不能透過GC進行
針對這類的物件,Java提供了 filanlize() method來因應

class 檔中可以定義 finalize() method
當垃圾回收器要開始釋放被物件占用的記憶體時,會先呼叫 finalize()函式
接著在下一次的垃圾回收動作發生時才會回收該記憶體空間
所以一般常見 finalize() 的定義是在垃圾回收時先執行某些重要的清理工作

有個可能比較容易引起誤會的地方是 finalize() 並不是C++的 destructor
差異點在於C++物件一定會被摧毀,所以destrucor絕對會被喚起
但是Java物件卻不一定會被GC回收
GC的執行條件是記憶體接近用完
假如直到程式執行結束都沒有啟動GC,記憶體空間會在程式終止時一次歸還
GC的執行會給程式帶來額外的負擔,能不執行的話自然是最好
因此 finalize() 並不一定會被呼叫
如果有一定要執行的清除動作,必須自行撰寫函式處理
通常影像處理類的任務比較有這類需求
finalize()真正適合使用的時機就只有要清除 non-Java 的物件

2010年9月26日 星期日

JDBC 簡介與基礎使用

JDBC(Java Database Connectivity)是Java規範資料庫存取的API
資料庫廠商依據Java提供的介面進行實作,開發者則依照JDBC的標準進行操作
如此設計的因素是每家廠商提供用來操作的 API 並不相同
開發者透過JDBC的介面操作的好處是,若有更換資料庫的要求,可以避免大量修改程式碼
只需要替換實作廠商的資料庫驅動
也就是寫一個Java程式就能應付所有的資料庫
當然實作時,有時為了使用資料庫的特定功能,仍有修改程式碼的必要

廠商實作JDBC驅動程式的方式有四種類型
1.JDBC-ODBC Bridge Driver
   ODBC(Open DataBase Connectivity)是微軟所主導的資料庫連結標準
   所以也很常在 Microsoft 的系統上使用
   這類的驅動程式將JDBC的呼叫轉換成對ODBC的呼叫
   但因為兩者並非一對一的對應,所以在功能及存取速度上都有所限制

2.Native API Driver
   載入由廠商提供的 C\C++撰寫成的原生函式庫
   直接將JDBC的呼叫轉成資料庫中的相關 API 呼叫
   因為直接呼叫 API 的關係,操作速度比第一種快(但比不上後兩種)
   但因為驅動程式作用的對象只限制在原生資料庫
   沒有做到JDBC的跨資料庫的目標

3.JDBC-Net Driver
   這類型的驅動程式提供了用戶端網路API,JDBC驅動透過Socket呼叫伺服器上的中介元件
   由該元件將JDBC的呼叫轉換成 API 呼叫
   客戶端的驅動程式與中介元件間的協定是固定的,所以不需要載入廠商的函式庫
   也因此在更換資料庫時,只需修改中介元件即可
   此種方式也是相當有彈性的架構

4.Native Protocol Driver
   這類的驅動程式使用Socket,直接在用戶端和資料庫間通訊
   也因為這是最直接的實作方式,通常具有最快的存取速度
   但針對不同的資料庫需使用不同的驅動程式
   為不需要JDBC-Net Driver那般彈性時的選擇,也是最常見的驅動程式類型

接下來的操作範例以第四種為主,並以MySQL為操作範例
MySQL提供的JDBC驅動程式是 mysql-connector-java ,可在其官網中找到
首先來個範例:
SQLController.java=======

package DB;

import java.sql.*;

public class SQLController {
  private Connection con = null;      //Database objects
  private Statement stat = null;        //SQL code to execute
  private ResultSet res = null;         //result
  private PreparedStatement pst = null;

  public SQLController(){

  }
  public SQLController(String SQLType, String SQLDriver, String daatabase, String account, String password){

/*  範例裡傳入的參數
SQLType = "mysql"
SQLDriver = "com.mysql.jdbc.Driver"
其餘的則按資料庫的設定傳入
*/ 
    
      String conInfo = "jdbc:" + SQLType + "://localhost:3306/" + daatabase + "?useUnicode=true&characterEncoding=UTF-8";
     
      try {
          //register driver
          Class.forName(SQLDriver);
          con = DriverManager.getConnection(conInfo, account, password); 
      }
      catch(ClassNotFoundException e){
            System.out.println("DriverClassNotFound :"+e.toString());
      }
       //prevent SQL code error
      catch(SQLException x) {
            System.out.println("Exception :"+x.toString());
      }
  }

  //clear objects in order to close database
  private void Close()
  {
    try{       
      if(res!=null){  res.close();}
      if(stat!=null){ stat.close();}
      if(pst!=null){ pst.close(); }
      if(con!=null){ con.close(); }
    }
    catch(SQLException e){
      System.out.println("Close Exception :" + e.toString());
    }
  }

}

網路上可以找到寫成JavaBean格式的寫法,搭配EL也能正常地使用資料庫
範例將資料庫連線寫成Java物件,寫法依個人喜好而定

連結資料庫前必須先載入JDBC驅動程式
透過Class.forName(),範例程式動態載入 com.mysql.jdbc.Driver 類別至 DriverManager
類別會自動向 DriverManager 做註冊
生成連線時,DriverManager就會使用該驅動建立Connection實例

JDBC URL定義連線資料庫的協定、子協定、資料來源識別
型態是  協定:子協定:識別資料來源
使用MySQL的JDBC URL就會是  jdbc:mysql://localhost:3306/資料庫名稱?參數=值&...
識別資料裡的 localhost:3306 連結 MySQL的連結阜
使用中文時需附加參數 useUnicode=true&characterEncoding=UTF8,指定用UTF8編碼
建立Connection實例時我們必須提供JDBC URL
DriverManager.getConnection() method有兩種引入參數的版本
單一 參數的版本必須將資料庫的帳密資料附加在JDBC URL裡,再引入URL
另一個版本就是本例使用的 DriverManager.getConnection(conInfo, account, password);
將帳號、密碼當作呼叫時的參數傳入

SQLException是資料庫處理過程發生異常時會被丟出的例外
資料庫的使用過程中都必須做這個例外處理的準備

Connection是連接資料庫的代表物件
執行SQL還需要取得 java.sql.Statement 物件
下面的範例示範實際執行SQL碼的撰寫方式,並搭配DCBP與JNDI來進行:
DatabaseBean.java======

package tw.vencs;

import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;

public class DatabaseBean {
    private DataSource dataSource = null;

    public DatabaseBean(){

    }  
    public void setConSource(DataSource dataSource){
        this.dataSource = dataSource;
    }
    public Connection getConnection() throws SQLException{
        return dataSource.getConnection();
    }
}

class SignInBean extends DatabaseBean{
    public boolean isSignIn(String account, String password){
        Connection con = null;
        Statement sta = null;
        ResultSet res = null;
        boolean checkOK = false;
      
        try{
          con = getConnection();
          sta = con.createStatement();
          res = sta.executeQuery("SELECT * FROM `member` WHERE account = '"+ account
                                   +"' AND password = '"+ password +"' ");
        
          //如果有找到與輸入的帳密相同的資料,允許會員登入
          while(res.next()){
              checkOK = true;
          }
        }catch(SQLException ex){
            Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
            throw new RuntimeException();
        }finally{
          try{  
            if(res != null){ res.close();}
            if(sta != null){ sta.close();}
            if(con != null){ con.close();}
          }catch(SQLException ex){
            Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
            throw new RuntimeException();
          }
        }
        return checkOK;
    }
}

SignInConfirmer.java======

package tw.vencs;

import java.io.IOException;
import javax.servlet.http.*;
import javax.sql.DataSource;
import com.colid.DatabaseBean;

public class SignInConfirmer extends HttpServlet{
  public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
         String account = request.getParameter("account");
         String password = request.getParameter("password");
         String cookieMaker = request.getParameter("cookieMaker");
         //get DBCP connection resource from InitialListener
         DataSource dataSource = (DataSource)getServletContext().getAttribute("conData");

         SignInBean confirmer = new SignInBean();
         confirmer.setConSource(dataSource);
        
         if(confirmer.isSignIn(account, password)){
             HttpSession session = request.getSession();
             session.setAttribute("account", account);
            
             if(cookieMaker.equals("yes")){
                int life = 30*24*60*60;  //set Life of Cookie to 30 days
                Cookie cookie = new Cookie("account", account);
                cookie.setMaxAge(life);        
                response.addCookie(cookie);
             }
            
             //encode URL if user's COOKIE function is closed
             response.sendRedirect(response.encodeURL("index.jsp"));
         }
         else{
             getServletContext().setAttribute("warning", true);
             response.sendRedirect(response.encodeURL("login.jsp"));
         }

  }
}

範例是一隻處理登入狀態(會話管理)的 Servlet 程式
DBCP 與 JNDI 的部分可以去找那篇的筆記來看

執行SQL碼的 Statement 物件從連線物件中取得,程式碼如: con.createStatement()
Statement 物件常用的方法有 executeUpdate()、executeQuery()和execute()
executeUpdate()主要用來執行  CREATE、INSERT、 DROP等改變資料庫內容的SQL碼
執行完畢後回傳  int 數值,表示資料變動的筆數
executeQuery() 則如字面所示,用來得到 SELECT等SQL碼查詢到的結果
執行完畢後回傳 java.sql.ResultSet 物件

ResultSet 的next() method 會回傳 boolean值來表示是否有下一筆資料
如果確實有資料的話可以使用 getXXX()的方式取得該筆資料,XXX為資料型別
查詢結果會從1開始作為欄位的索引值
例如: int id = res.getINT(1)
execute() 這個method則用在無法事先得知要執行的SQL碼的場合
假使回傳的結果是 true,則可以用getResultSet() 取得 ResultSet 物件
回傳 false 則可以用 getUpdateCount() 得知更新資料筆數

最後提到上面的範例沒有展示到的 PreparedStatement
假使有動態資料的情況下
像上述範例以 + 運算子結合字串組成SQL碼會比較麻煩
字串每使用一次 + ,其實也是重新製作一個String物件
如此的字串使用也造成效能上的負擔
PreparedStatement 的使用與 Statement 很相似,如改寫範例後為:
PreparedStatement pre = con.prepareStatement("SELECT * FROM `member` WHERE
                                                                                       account =? AND password =?");
pre.setString(1, account);           //setString() 輸入的參數可以避免 Injection Attack
pre.setString(2, password);
pre.executeUpdate();
pre.clearParameters();

將SQL碼中要動態輸入的部分以 ? 運算子代替,然後設置所要輸入的變數
con.prepareStatement() 會建立一個預先編譯的SQL實例
它也同樣可以使用executeUpdate()、executeQuery() method
當查詢完畢之後,clearParameters() 清除所輸入的參數
留下來的實例卻可以填上別的參數繼續使用,而不用重新再建立SQL實例
會被頻繁使用的SQL碼實例適合用這種方式建立
setString() 輸入的參數會被視為純粹的String物件,最後在SQL碼內的形式是包覆在引號內
若是需要填入SQL碼內的是數字,則可以用 setInt() 以免出現SQL碼錯誤的情形
PreparedStatement 會是效能與安全性考量的好選擇

2010年9月3日 星期五

程式物件與記憶體的配置方式

物件在配置時能被儲存在五個地方:
1.暫存器(Registers)  2.堆疊(Stack)  3.堆積(Heap)
4.常數暫存空間(Constant storage)   5.Non-RAM儲存空間

以下對這五種方式做說明


1.暫存器(Registers):
   暫存器內建於CPU內,所以是速度最快的儲存位置
   但因為數量有限,所以這裡的物件配置都由編譯器來決定
   自由度比較高的C或C++也僅能向編譯器建議暫存器的配置

2.堆疊(Stack):
   堆疊的位置位於一般的RAM裡,處理器經由其指標(stack pointer)提供直接支援來運作
   當程式配置一塊記憶體時,stack指標會往後移動;釋放記憶體則會讓指標往前移回
   堆疊很快也很有效率,速度僅次於暫存器

   因為Java的編譯器必須自行建立移動stack pointer的程式碼
   所以必須要完全掌握存在stack裡的資料的實際大小與存活時間
   因為這個限制,儘管物件的reference可以儲存於其上,但一般的Java物件並不能如此

3.堆積(Heap):
   Heap是通用性質的記憶體儲存空間,也位在RAM裡
   與stack不同的是,它不需要事先知道必須配置於其上的物件大小與存活時間
   所以它有了相當的彈性

   Heap也是Java放置一般Java物件的地方
   每當在程式中執行 new 指令時,就會在heap上配置
   當然有彈性的另一面是配置的時間較stack來的長

4.常數暫存空間(Constant storage):
   因為常數值不會改變,所以有時候會被放在ROM(read-only memory)或內嵌式系統中
   內嵌式系統的例子舉Java的string pool,它是特殊的儲存空間

5.Non-RAM儲存空間:
   如果資料可以獨立於程式外,這類物件通常被儲存於磁碟裡
   這一類儲存空間的特色是,可以將物件轉換為可儲存於其他媒介的形式
   必要時也可以還原成儲存於RAM中的一般物件


我們稱呼在撰寫程式過程中常用到的類別為基本型別(primitive types)
要特別的處理它是因為透過 new 來配置這種極小、簡單的變數於heap上,效率不佳
Java採取C/C++的方式來處理,也就是不用 new 來配置其空間
這產生了"automatic"變數(不再使用reference型態)
這類變數直接存放資料值,並配置在stack上以取得較佳的效率

2010年5月10日 星期一

定時任務:Java中Timer和TimerTask的使用

java.util.Timer定時器,實際上是個線程,定時調度所擁有的TimerTasks。
一個TimerTask實際上就是一個擁有run方法的類別,需要定時執行的code放到run method內,TimerTask一般是以匿名類的方式創建。



一個完整的Timer:
Java  code

   java.util.Timer timer = new java.util.Timer(true);  
   // true 說明這個timer以daemon方式運行(優先級低,  
   // 程序結束timer也自動結束),注意,javax.swing  
   // 包中也有一個Timer類,如果import中用到 swing包,
   // 要注意名字的衝突。  
     
   TimerTask task = new TimerTask() {  
   public void run() {  
   ... //每次需要執行的代碼放到這裡面。  
   }  
   };  
    
   //以下是幾種調度task的方法:  
    
   timer.schedule(task, time);  
   // time為Date類型:在指定時間執行一次。  
    
   timer.schedule(task, firstTime, period);  
   // firstTime為Date類型,period為long  
   // 從firstTime時刻開始,每隔period毫秒執行一次。  
    
   timer.schedule(task, delay)  
   // delay 為long類型:從現在起過delay毫秒執行一次  
    
   timer.schedule(task, delay, period)  
   // delay為long,period為long:從現在起過delay毫秒以後,每隔period  
   // 毫秒執行一次。

下面是一個完整的例子,由兩個類組成,一個定製任務,一個調用java.util.Timer

定製任務:
Java code

    import java.util.Timer; 
     
    public class TimerTaskTest extends java.util.TimerTask{ 
     
    @Override 
      public void run() { 
       // TODO Auto-generated method stub 
       System.out.println("start"); 
      } 
    } 

使用java.util.Timer
Java code

    import java.util.Timer; 
     
    public class Test { 
    public static void main(String[] args){ 
       Timer timer = new Timer(); 
       timer.schedule(new TimerTaskTest(), 1000, 2000); 
    } 
    } 

根據上面的介紹,便可以在1秒後,每隔2秒執行一次程序

2010年5月8日 星期六

JDK安裝環境設定

http://www.oracle.com/technetwork/java/javase/downloads/index.html
到Oracle下載最新版的jdk

接著到  [電腦] -> 右鍵[內容] -> [進階系統設定] -> [環境變數]  中做設定




JAVA_HOME         C:\Program Files\Java\jdk1.7.0_09      
PATH                     ;%JAVA_HOME%\bin      (附加在現有內容後)
CLASSPATH         %JAVA_HOME%\lib

不存在的變數以新增建立即可
建立 JAVA_HOME 變數儲存 Java路徑資料
日後昇級版本只要修改 JAVA_HOME 就能一併修改其他套用 JAVA_HOME 的環境變數
PATH 設定能簡化呼叫 JDK 的路徑宣告
CLASSPATH 則是簡化了編譯 Java 檔案時要引用的工具路徑

如此一來就完成最基礎的設定
接著可以到windows的search裡面打上cmd
輸入 java -version可以看到java版本的資訊
再輸入javac,如果有出現許多指令就代表成功囉