如果把程式當成是魔法,前面幾章都是基本的咒文。
到這章開始需要用到想像力了。
class像是沒有生命的模型,裡面記載了一些關於物件的敘述與特徵。
透過new()方法,可以從模型中複製一或多個一模一樣的物件出來,並且具有生命力,可以執行各種動作。想像一下你的玩具車或鋼彈模型被附加了想像力,真正動起來的樣子吧!
來看看以下範例:
class Employee {
int id;
void setId(int i){
id = i;
}
int getId(){
return id;
}
}
class Sample5_1 {
public static void main(String[] args) {
//實例化社員A
Employee a = new Employee();
//設定A的社員編號
a.setId(100);
//實例化社員B
Employee b = new Employee();
//設定B的社員編號
b.setId(200);
//請電腦告訴我們社員A和社員B的編號
System.out.println("社員A的編號:" + a.getId());
System.out.println("社員B的編號:" + b.getId());
}
}
執行結果
社員A的編號:100
社員B的編號:200
JAVA的物件像是黑盒子,如果要把資料存進去或拿出來,得要透過特定的方法。
通常會命名為set和get方法,利用這兩個方法來從物件裡存取資料。如範例中的setId()和getId()。
set資料的時候要準備引數,用來放進物件裡。
void setId(int i) //指定引數為單個int型數據,void表示不返回數值。
get資料的時候不用引數,但要指定拿出資料時,資料的型態。
int getId() //沒有引數,返回int數值。
在範例5_1裡使用new()實例化了社員A和社員B。這兩個物件複製了class Employee的所有資料及方法。在實例化之後,A和B各自保有自己的資料。
在方法內定義的變數又稱為區域變數,只適用於該方法內(也就是{}的裡面)。看看以下範例:
class Test {
int x;
public static void main(String[] args) {
boolean y = true;
if (y){
String z = "JAVA";
}
System.out.println(z);
}
}
變數x的使用範圍在class Test內
變數y的使用範圍在main method內
變數z的使用範圍在if內,所以System.out.println(z);這行因無法找到變數z,而導致編譯錯誤。
class除了變數及方法外,還可以定義建構式。
建構式是物件實例化的時候,會執行一次的式子(相當於將物件初期化)。
建構式長得跟一般方法有點像,透過以下特徵來區隔兩者:
如果沒有寫建構式的話,JAVA預設會給一個空的建構式,長得像這樣:Class名(){}
這就是為什麼前面的案例沒有給任何引數也能執行的原因。
不過一旦自己寫了建構式,JAVA就不會再幫忙寫空的建構式了。
看看以下範例:
class Employee {
int id;
String name;
//建構式定義
Employee(int i){
id = i;
}
Employee(int i,String s){
id = i;
name = s;
}
}
class Sample5_2 {
public static void main(String[] args) {
//實例化員工
Employee a = new Employee(); //會因為找不到相應的建構式而報錯
Employee b = new Employee(100);
Employee c = new Employee(100,"Tom");
}
}
從此範例可以發現,當要實例化一個員工時
若沒有提供任何引數,則會因為找不到相應的建構式而報錯
至少需提供int數值(id編號),此時Employee(int i)建構式會被呼叫。
若提供了int數值和String資料,則Employee(int i,String s)建構式會被呼叫。
這個特性稱為overload,確保了資料不足的時候,程式仍具有通用性。
overload的特性不只適用於建構式,也適用於一般方法。如下例:
class Test {
void myPirnt(){
//處理1
}
void myPirnt(int i){
//處理2
}
}
static是靜態的意思,static修飾子可以放在變數或方法的前面,表示其屬於class的變數或方法,而非屬於實例化物件(instance)。
JVM在執行時,會先載入靜態資料和class,然後執行main方法,然後進入動態的世界(實例化物件、建立非靜態的變數等..)
注意:
static變數或方法於class建構時被建立。
非static變數或方法於實例化物件時被建立。
這表示非static變數或方法(後被建立者)可以訪問static變數或方法(先被建立者)
相對的,在static方法中要訪問非static變數或方法時,需要先實例化一個物件之後才能使用。
請看下例:
class Sample5_3 {
int instanceVal;
static int staticVal;
int methodA(){return instanceVal;} //OK
int methodB(){return staticVal;} //OK
static int methodC(){return instanceVal;} //NG static方法不能直接訪問instance變數
static int methodD(){return staticVal;} //OK
static int methodE(){ //OK 實例化一個物件後,static方法可以訪問此物件的instance變數
Sample5_3 obj = new Sample5_3();
return obj.instanceVal;
}
}
就如同各個物件擁有自己的數據,class也可以保存自己的數據(即static變數)。
由同一個class實例化出來的各個物件,可以共用和修改這些static變數。
※要使用class變數的時候,別忘記加上static喔!只有靜態變數和方法,會在class建立時一同被建立。非靜態的則是物件實例化時會被建立。
剛剛提到物件被實例化時,會執行建構式。而Static Initializer相當於類的建構式。
當類別初次被使用時,Static Initializer會被執行。
看以下範例:
class Foo {
static {
System.out.println("Foo class: Static Initializer");
}
Foo() {
System.out.println("Foo class: Constructor");
}
}
class Sample5_4 {
static {
System.out.println("Sample5_4 class: Static Initializer");
}
public static void main(String[] args) {
System.out.println("Sample5_4 class: Main Method");
Foo f = new Foo();
}
}
執行結果
Sample5_4 class: Static Initializer
Sample5_4 class: Main Method
Foo class: Static Initializer
Foo class: Constructor
以上的結果翻譯成中文就是:
還記得main方法前面的public嗎?這就是存取修飾子,可以放在類、變數及方法前面,決定這些東西的公開範圍(存取權限)。
例如JAVA的實例化物件裡面,通常會把變數設為private,而get,set等方法設定為public。以確保物件裡面的值不會直接被外部看見或修改,而是只能透過固定的方法來訪問。
class Employee {
private int id;
public Employee(int i) {id = i;}
public int getId() {return id;}
}
class Sample5_5 {
public static void main(String[] args) {
Employee emp = new Employee(100);
System.out.println(emp.id);
System.out.println(emp.getId());
}
}
嘗試編譯以上的檔案會發現,System.out.println(emp.id);這行會因權限不足而報錯。必須使用getId()方法才能訪問id的值。
除了public和private之外,還有protected和預設值(未指定)兩種。
要解釋修飾子,首先得先認識JAVA的package(包)的概念。包就像是資料夾,裡面存放不同的class文件。
文法如下:
package 包名;
class X {…}
如果沒指定包名的類,則會被JAVA歸類到沒有名字的包內。
關於包的詳細介紹待會兒會再說明,先回到修飾子的說明。
public表示完全開放,任何別的包裡的類都可使用此類、變數或方法。
protected只開放給同一個包使用,或是不同包但繼承了此類的子類也可使用。
不指定,即採用預設值,只開放給同一個包使用。
private,僅該類自己可以使用。
在JAVA裡面,如果把一個基本數值的資料,當作引數丟給一個方法進行處理。則丟進去的會是一個複製的新值,而非原本的數值。例如:
int num = 10;
obj.method1(num);
System.out.println(num); //方法外的num保持為10
void method1(int num) {
num += 10;
System.out.println(num); //方法內的num為20
}
寫成以下的樣子可能比較好理解
void method1(int n) {
n += 10;
System.out.println(n); //n為20
}
也就是方法內的num其實跟外部的num沒有任何關係。只是複製了其值而已。而且num作為引數被傳入方法之後,其變數名也可以任意取名。
不過如果引數是物件的話,則物件本身會被傳入方法內。
int[] array = {10};
obj.method2(array);
System.out.println(array[0]); //方法外的值也變為20
void method2(int[] array) {
array[0] += 10;
System.out.println(array[0]); //方法內的值為20
}
請看以下範例:
package chap05.com.se;
class Foo {
void print() {
System.out.println("package sample");
}
}
class Sample5_6 {
public static void main(String[] args) {
Foo f = new Foo();
f.print();
}
}
由於在java文件中指定了包的路徑,因此編譯和執行時也都要符合設定的路徑。
首先在當前路徑下,建立以下資料夾chap05/com/se,然後把Sample5_6.java檔放進去。
進行編譯
javac chap05/com/se/Sample5_6.java
執行
java chap05.com.se.Sample5_6
由於手動新增資料夾很麻煩,所以也可使用-d指令自動建立路徑(d後面的"."表示指定當前路徑為起點)
javac -d . Sample5_6.java
如果需要用到的類,在不同包裡面時,就使用import指令來引用。
建立以下的範例檔案:
Foo.java
package chap05.com.se.ren;
public class Foo { //要給不同包的程式使用,必須加上public修飾子
public void print() { //要給不同包的程式使用,必須加上public修飾子
System.out.println("package sample");
}
}
Sample5_7.java
package chap05.com.se;
import chap05.com.se.ren.Foo;
//import chap05.com.se.ren.*; //也可以使用"*"來引用ren包下面所有類。
class Sample5_7 {
public static void main(String[] args) {
Foo f = new Foo();
f.print();
}
}
先編譯Foo.java,再編譯Sample5_7.java(由於Sample5_7裡面import了Foo,所以如果沒先建立Foo的話會報錯)
javac -d . Foo.java
javac -d . Sample5_7.java
※因中文導致編譯失敗請指定以utf-8編碼進行編譯,如下例:
javac -d . -encoding utf-8 Foo.java
編譯完成後執行
java chap05.com.se.Sample5_7
以上是第五章 class定義及物件的學習心得,接下來第六章會介紹繼承與多型。
參考教材: JAVAプログラマSilver SE8 - 山本 道子