星期一, 10月 01, 2012

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



沒有留言: