星期一, 10月 01, 2012

OCPJP Day 5


*建立JAR檔案
  • .class檔案依造宣告的package,使用GZIP 格式壓縮, 並指定副檔名為.jar
  • 設定manifest.mf檔案內容(專案目錄中建立meta-inf的目錄, 並在目錄內設定)
    • Main-Class
      • Main-Class: com.max.main.Hello
      • Main-Class: 後面要空格
    • Class-Path

Lab:

建立一個檔案 abc.txt 當成manifest.mf 內容如下 (Hello後面要換行)
Main-Class: com.max.main.Hello

在類別的最上層目錄下指令(com的上一層), 目前該目錄下有 abc.txt 以及 com 目錄
abc.txt com

jar cvfm test.jar abc.txt .
  • test.jar 為輸出的 jar檔案名稱
  • abc.txt manifest.mf 檔案
  • . 代表目前的目錄, windows 實作上差了一個目錄層級, linux上製作是在現行目錄下

測試建立的 .jar 檔案

java -jar test.jar


*設定classpath
  • 可以在編譯或執行的時候加上 -classpath設定指定JRE以外的類別程式庫
  • 或是加上CLASSPATH環境變數
  • 或是在JAR檔案中的mainifest.mf內容加上Class-Path的設定


Lab: Netbeans

執行 Netbeans
(發現沒有專案, 原來在linux底下不是只裝 netbeans 就好了, 要裝 netbeans-java …. )
以後專案放在SCJP/exercise 下面
Netbeans 下建立一個專案測試

Tips 組合鍵
  • Tab 用來展開 Code Template
    • 例如 sout 然後按 tab 按鍵
  • Crtl + Space 程式碼的輔助功能
  • Shift + F6 Run File

Libraries 上面 可以使用 滑鼠右鍵 Add JAR / Folder 來新增 函式庫

Notes:
  • eclipse 內使用 Alt + / 可以達到 Tab or Ctrl + Space 的效果


*Chapter 6 運算子與運算式

*基本型別的提升
  • = 將右邊的結果指派給左邊的變數
    • 變數名稱=| 另一個變數 | 運算式 | 方法的回傳結果
  • 指派時, 若等號左邊的型別大於右邊的時候, 指派成功稱為提升(Promotion)
    • byte –> short -> char -> int -> long -> float -> double


*基本型別的轉型
  • 指派時, 若等號左邊的型別於右邊的時候, 則需要將右邊的資料轉型(Casting), 否則會出現編譯錯誤
  • possible loss of precision (可能會失去精確度)


*運算子(Operator)

Notes:
  • 位元運算子:先轉成2進位再運算

*算術(Arithmetic)運算子與計算式
  • 提供數字元素的常用計算功能: + - * / %
  • Java 運算的最小的型別是int (元素型別小於int 都會被提升為 int [例如 byte/short/char])
  • Java 算術運算式結果的型別不是依造執行時期實際計算結果來決定, 不是依照等號左邊的型別來決定, 而是根據運算式中所參與的運算元的型別來決定.
    • 例如 byte a=1, b=1, c=a+b;
    • 上述的 a, b, c 都是 byte 型別, 但是 a+b 是運算式, 所以就會被提升為int
    • 也就是 byte c = int a + int b //這樣會有編譯錯誤, 因為喪失精確度
    • 這樣會喪失精確度 possible loss of precision
    • 所以要進行轉型的動作, 才不會喪失精確度
    • 結果要寫成 byte c = (byte) (a+b)

Break –

*遞增/遞減 運算子
  • 放在右邊就是後置
    • 先輸出後遞增 a++
  • 放在左邊就是前置
    • 先遞增後輸出 ++a



*邏輯(Logical)運算子
  • && || Short-cut 運算子, 一般放在兩個運算式之間.
    • 算式1 && 算式2
      • 算式1 為真才會執行算式2
    • 算式1 || 算式2
      • 算式1 為假才會執行算式2

*位移運算子
  • 將整數轉為2進位, 然後將資料向左或是向右位移
  • java 用第一個 bit 來代表 + 或是 -
  • 32 << 2
    • 32 * 22次方 = 32 * 4 = 128
  • 右移分為保留正負號(Right-shift) 以及 不保留正負號(Unsigned Right-shift)(一律都補上0)


*字串運算子
  • 物件跟字串串接, java 會自動呼叫他們的toString()
  • 任何型別的資料與字串相加時, 都會先轉為字串
  • 加號是從左到右取值

例如
System.out.println(111+222+333); //裏面沒有字串所以作數學運算
System.out.println("111"+222+333); //裏面有字串, 所以先轉成字串,後面的 222+333就不會運算
System.out.println(111+"222"+333); //裏面有字串, 所以先轉成字串
System.out.println(111+222+"333"); //由於加號是從左到右, 還沒碰到字串,所以會先進行 111+222 在列出333

輸出結果為

666
111222333
111222333
333333

Lab : java 運算子

int i = 1;
System.out.println("i="+i);
System.out.println("before i++");
System.out.println("Do i++ and i="+i++);
System.out.println("after i++");
System.out.println("i="+i);
System.out.println("Do --i");
System.out.println("i="+--i);
i = 128 ^ 5;
// 128 = 10000000
// 5 = 00000101
//進行互斥運算 10000101
System.out.println(i);
i >>=3;
System.out.println(i);


*Chapter 7 決策判斷與迴圈結構

* if … else
  • if 的語法不一定要搭配 else
  • java 沒有then 關鍵字

*if … else if … else
  • else if 的數量沒有限制

*switch … case
  • switch 可以接受
    • int 的相容型別(byte/char/short/int)
    • enum
    • String (Java SE 7.0 後面才開始支援)
  • case 可以有 0 到多個
  • default case 比對不到就會跑到 default
  • break 用來跳出 switch, 如果沒有 break 就會往下執行

例如

int type = 1;
switch(type) {
case 1:
System.out.println("A");
System.out.println("B");
System.out.println("C");
System.out.println("D");
System.out.println("E");

break; // jump out
case 2:
System.out.println("A");
System.out.println("B");
System.out.println("C");
System.out.println("D");

break;
case 3:
System.out.println("A");
System.out.println("B");
System.out.println("C");
break;
default:
System.out.println("A");
System.out.println("B");
break;
}
以上的作法每一個case 的順序不會影響後面的結果, 但是會有很多重複的部份.

也可以改成

int type = 1;
switch(type) {
case 1:
System.out.println("A");

case 2:
System.out.println("B");

case 3:
System.out.println("C");

default:
System.out.println("D");
System.out.println("E");
}

但是用以上的方式, case 排列的順序就很重要, 沒有break 程序會一直往下執行

上面的 int type = 1; 也可以換成
int type = Integer.parseInt(args[0]); //Just can use it for command line to receive

OCPJP Day 4


Lab: 實作一個建構子

修改 Product.java 加上建構子

public class Product{
private int id;
private String name;
private double unitPrice;
private boolean free;
private int stock;

//建立建構子
//手動建立預設的建構子
public Product(){}

//建立一個建構子可以傳遞所有的類別
public Product(int id, String name, double unitPrice, boolean free, int stock){
this.id = id;
this.name = name;
this.unitPrice = unitPrice;
this.free = free;
this.stock = stock;
}

//建立一個建構子可以傳入 id 以及 name 並利用 overload 的方式
public Product(int id, String name){
//this(id, name); 這樣會造成 recursive constructor invocation, 所以要將後面的參數補上初始值, 才是呼叫別人, 不然只放兩個會在自己的方法產生迴圈
//所以String 的初始值是 null
//double 的初始值是 0.0
//boolean 的初始值是 false
//int 的初始值是 0
this(id, name,0.0,false,0);//所以如果要呼叫上面那個, 這邊就要補到 5 個參數, 以此類推
}

//id
public int getId(){
return this.id; //這邊忘記使用 this.id
}
public void setId(int id){ //注意 set的時候不需要回傳
this.id = id; //所以只有在設定的時候會用到 this
}

//name
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}

//unitPrice
public double getUnitPrice(){
return this.unitPrice;
}
public void setUnitPrice(double unitPrice){
this.unitPrice = unitPrice;
}

//free
public boolean isFree(){
return this.free;
}
public void setFree(boolean free){
this.free = free;
}
//stock

public int getStock(){
return this.stock;
}
public void incStock(int amount){
//stock = stock + amount;
this.stock += amount;
}
public void decStock(int amount){
this.stock -= amount;
}

public String toString(){
//剛剛發生使用單引號來包覆 \t 產生與 id 相加的現象
return id + "\t" + name + "\t" + unitPrice + "\t" + free + "\t" + stock;
}
}

修改 TestProduct.java

public class TestProduct{
public static void main(String[] args){
Product p1 = new Product();
//當改成 private 之後,忘記如何去存取 Product.java 內的方法
p1.setId(1); //使用方法呼叫 前面加上方法名稱,傳遞參數進去
p1.setName("可口可樂");
p1.setUnitPrice(12.0);
p1.setFree(false);
p1.incStock(10000);

Product p2 = new Product();
p2.setId(2);
p2.setName("百事可樂");
p2.setUnitPrice(10.0);
p2.setFree(false);
p2.incStock(10000);

/* p1.id=1;
p1.name="可口可樂";
p1.unitPrice=12.0;
p1.free=false;
p1.stock=10000;

p2.id=2;
p2.name="百事可樂";
p2.unitPrice=10.0;
p2.free=false;
p2.stock=10000; */


//利用建構子來新增
//好處是不用一行一行寫, 可以一次將參數傳進去
Product p3 = new Product(3,"芬達汽水",13.0,false,12000);

//庫存價值
double totalInventory = 0.0;
ProductService ps = new ProductService();
//逐項將各品項相加
totalInventory += ps.calcProductInventory(p1);
totalInventory += ps.calcProductInventory(p2);
totalInventory += ps.calcProductInventory(p3);


System.out.println("庫存報表");
System.out.println("------------------------------------------------");
System.out.println("序號\t品名\t\t單價\t免費\t庫存量\t\t庫存價值");
System.out.println("------------------------------------------------");
System.out.println(p1+"\t"+ps.calcProductInventory(p1));
System.out.println(p2+"\t"+ps.calcProductInventory(p2));
System.out.println(p3+"\t"+ps.calcProductInventory(p3));
System.out.println("------------------------------------------------");
System.out.println("\t\t\t\t 庫存價值= "+totalInventory);
System.out.println("------------------------------------------------");
}
}



*檢視物件建立的過程
  • 當執行到 new ClassName(...)
    • 依造類別的定義在 HEAP Memory 配置物件記憶體
    • 根據資料型別設定屬性的初值
    • 將程式中明確宣告的初值指派給屬性
    • 執行建構子程式, 完成物件的建立.
    • 透過 new ClassName(...) 回傳該物件的參考.

Notes:
  • 在類別表內 <<R/W>> 代表要提供 getter setter 的方法

MyDate
- day: int =1 <<RW>>
- month: int = 1 <<RW>>
- year: int = 2000 <<RW>>
+ MyDate(day: int, month: int, year: int)
+ toString()


如果執行 MyDate d = new MyDate(26, 9, 2012);

Step1:
MyDate d = new MyDate(26, 9, 2012);

會準備一個 32 bits 的空間給 d, 準備存放物件的參考指標.

Step2:
MyDate d = new MyDate(26, 9, 2012);

Heap 裏面規劃記憶體空間, 並完成預設的初始化(根據屬性)
day 初始化0, month 初始化0 year 初始化為 0,

Step3:
MyDate d = new MyDate(26, 9, 2012);

完成明確初始化, day 設定成1, month 設定成1 year 設定為 2000,

Step 4:
MyDate d = new MyDate(26, 9, 2012);

呼叫建構子並完成物建的建構過程
day 設定成26, month 設定成9 year 設定為 2012
完成參考指標並對應到 d

Notes:
  • Java 的變數的型別只要是類別, java 一律配置 32bits 空間


*Pass-by-value
  • Java 使用 Pass-by-value 只有在
    • 指派變數值
    • 呼叫方法傳遞參數值
  • 當指派的變數型別或傳遞的參數型別為 Primitive Type, 則傳遞的參數值為資料
    • int a = 100; //a 100
    • int b = a; // a 複製自己的資料給b
    • b = 200;
  • 當指派的變數型別或傳遞的參數型別為 Reference Type,則傳遞的參數值是HEAP Memory 中的參考
    • MyDate d1 = new MyDate(26,9,2012); //建立指標(例如 0x12345678) 並值傳給 d1
    • MyDate d2 = d1; //d1 將指標複製給 d2, d2 指向 0x12345678
    • d2 = new MyDate(25,12,2012); d2 產生一個新的指標, 並指向新的指標

Notes:
  • Java 只有 Pass-by-value


Lab: 觀察Pass-by-value

MyDate.java

public class MyDate {
private int day=1;
private int month=1;
private int year=2000;
public MyDate(){}
public MyDate(int day,int month,int year){
this.day = day;
this.month = month;
this.year = year;
}
public void setDay(int day) {
this.day = day;
}
public int getDay() {
return this.day;
}
public void setMonth(int month) {
this.month = month;
}
public int getMonth() {
return this.month;
}
public void setYear(int year) {
this.year = year;
}
public int getYear() {
return this.year;
}
public String toString(){
return this.year+"-"+this.month+"-"+this.day;
}
}


Pass.java

public class Pass{
public void changeIntValue(int value){
value = 500;
}
public void changeObjectReference(MyDate ref){
ref = new MyDate(25,12,2010);
}
public void changeObjectValue(MyDate ref){
ref.setDay(6);
ref.setMonth(3);
}
}

PassTest.java

public class PassTest{
public static void main(String[] args){
int a=100;
Pass p = new Pass();
p.changeIntValue(a);
System.out.println("a="+a);
MyDate d = new MyDate(26,9,2012);
p.changeObjectReference(d);
System.out.println("d="+d);
p.changeObjectValue(d);
System.out.println("d="+d);

}
}


Lab: 觀察 java 程式如何運作

利用 Point2D.java TestPoint.java 來觀察

Key Point:
  • Java 在執行任何方法前, 會先將該方法掃描一遍, 然後將該方法所需的區域變數配置到堆疊
  • 任何非 static 的方法, Java 一律會配置一個 "this" 的隱含變數給該方法.
  • static 的方法不會有 this 的隱含變數.
  • this 存著自己的指標指向記憶體的物件
  • 如果堆疊變數沒有直接或是間接的指向 Heap, 最後會被回收

*宣告 import
  • 類別中要使用不同package的其他類別, 在宣告或是建立時必須寫出 Fully Qualified Class Name
  • 下列類別不需要 import
    • 同一個套件的類別
    • java.lang.* 中的類別
  • package 命名建議使用 domain name + 系統結構
  • 類別一旦帶有套件名稱, 名稱就會是package 名稱加上 類別名稱
  • import 是提供 JVM 或是編譯器搜尋的類別管道, 預設的搜尋路徑是 . (目前的目錄)

架構設計

myProject
|----src
|****|----com
|*********|----max
|**************|---- main/ Hello.java
|********************|---- model/ Greeting.java
|----classes
|****|----com
|*********|----max
|**************|---- main/ Hello.class
|********************|---- model/ Greeting.class


Hello.java

package com.max.main;
import com.max.model.*; // 提供JVM 或是 編譯器搜尋的管道, 要在 com目錄的上一層編譯

public class Hello{
public static void main(String[] arg){
System.out.println("Hello world");
Greeting x = new Greeting();
x.sayHello("Max");
}
}

Greeting.java

package com.max.model;
//執行的時候一定要在相對的根目錄才可以使用
public class Greeting {
public void sayHello(String name){
System.out.println("Hello,"+name);
}

}


Notes:
  • javac -d ../classes com/max/main/Hello.java (src 目錄下指令)
    • -d 指定輸出路徑 在上一層的 classes, 會將產生的class 對應到相對路徑
  • java com.max.main.Hello (classes 目錄下指令執行)
  • java -classpath 以及 -cp 都是提供正確的 class path (類別開始起算的路經)


*.jar 檔的製作
  • 使用 zip 來壓縮, 然後變更名稱為 *.jar

Bootstrap Classloader (Java 的類別載入機制)
  • %JAVA_HOME%/jre/lib/rt.jar
    • rt.jar Java 所有的核心類別
  • %JAVA_HOME%/jre/lib/ext/*.jar
    • 讓廠商有機會換掉 jre 預設的核心類別
  • System Loader
    • 設定 CLASSPATH環境變數
      • CLASSPATH=xxxx;xxxx;xxxx;xxxx;
      • Windows 使用 ; 當分隔符號
      • Unix like 使用 :當分隔符號
      • 路徑的部份
        • 可以指向類別起算的相對根目錄
        • 或是指向 jar 檔的完整路徑 + 檔名
    • 執行或是編譯的時候, 下達 -cp 或是 -classpath 指定類別搜尋路徑
      • 例如jar檔放在 C:\myapp\lib, C:\ 下達指令 (windows 來說)
      • java -cp c:\myapp\lib\maxlab.ja;c:\myapp\lib\test.jar com.max.main.Hello