[Java] 變數的範圍與類型

一個變數代表了一個可以由程式所獨佔的儲存空間,除了鬆散型態程式語言之外的所有語言,都以資料型態明確的定義了每一個變數的大小及功能。

以上所說的應該對大家來說都早已是老生常談,但是你知道嗎?其實它也有很多不同的類型喔!

 

區域變數 (Local variables):

我要先強調,Java 的定義上沒有一般常見的全域變數(Global Variable),但提供了其他方法來達到原本的功能(例如static),

  • methods, constructors, or blocks區域變數通常宣告在 方法(method)、建構子(constructor)或 區塊(block)之中
  • 區域變數通常在進入 方法(method)、建構子(constructor)或 區塊(block)時建立,並在離開時自動銷毀
  • 你無法在區域變數上使用存取修飾子
  • 區域變數無法在所屬的 方法(method)、建構子(constructor)或 區塊(block)外使用
  • 因為區域變數沒有預設值,所以使用者必須在使用前設定其數值(否則會出現 not have been initialized 的訊息)
  • 一個 Java 方法(method)通常會使用區域變數來暫時儲存資料

以下是一個狹義區域變數的範例,將變數宣告在方法(method)之中

public class Test{ 
   public void pupAge(){
      int age = 0;
      age = age + 7;
      System.out.println("Puppy age is : " + age);
   }
   
   public static void main(String args[]){
      Test test = new Test();
      test.pupAge();
   }
}

 結果:

Puppy age is: 7

接下來我們做一個小實驗,若將以上範例中的

int age=0;

換成這個呢?

int age;

答案是你會得到一個編譯時期錯誤,像這樣:

Test.java:4:variable number might not have been initialized
age = age + 7;
         ^
1 error

因為 沒有初始化 age 的數值,會造成讀不到數值的情況 (NULL),所以 compiler 先提醒使用者。

 

實例變數、非靜態屬性變數(Instance variables、Non-Static Fields):

技術上來說,物件會將其個別狀態儲存在”非靜態區”,也就是沒有宣告 static 的屬性變數的形式。

  • 實例變數會被宣告在類別(class)之中,但在 方法(method)、建構子(constructor)或 區塊(block)外
  • 當物件被使用 new 的方式建立,對應的實例變數也會被建立(匿名物件除外),當物件被清除,變數也一同被清除
  • 實例變數可以使用存取修飾子
  • 實例變數對所有 方法(method)、建構子(constructor)或 區塊(block)而言都是可見的,建議使用存取修飾子來調整權限
  • 有預設值, 如果沒有給訂初值, 編譯器也會給予預設值(數字型態為0、字元型態為NULL、布林型態為false
  • 若在同一類別中,實例變數可以直接呼叫其名稱存取,但若在靜態方法或其他類別之中則須改用 <物件參照>.<變數名稱> 的方式來存取

底下以範例讓大家認識它的特性:

public class Employee
{
   // 實例變數 name 對所有的子類別都是可見的
   public String name;
   
   // 實例變數 salary 只在 Employee 類別是可見的
   // salary  variable is visible in Employee class only.
   private double salary;
   
   // 實例變數 name 的值在這個建構子中被指定
   public Employee (String empName)
   {
      name = empName;
   }

   // 實例變數 salary 的值在這個方法中被指定
   public void setSalary(double empSal)
   {
      salary = empSal;
   }
   
   // 印出資訊
   public void printEmp()
   {
      System.out.println("name  : " + name );
      System.out.println("salary :" + salary);
   }

   public static void main(String args[])
   {
      Employee empOne = new Employee("Ransika");
      empOne.setSalary(1000);
      empOne.printEmp();
   }
}

 結果:

name  : Ransika
salary :1000.0

 

靜態屬性變數 (Class Variables、Static Fields):

static int age;

沒錯,靜態(static),一開始就被載入記憶體,並且放在特別的地方(靜態區),給了它不一樣的特性:

  • 靜態變數在類別檔載入時就已經初始化
  • 靜態變數在任何該類別的物件被建立(常用 new)之前就已經被初始化
  • 靜態變數在任何該類別的靜態方法執行之前就已經被初始化
  • 有預設值, 如果沒有給訂初值, 編譯器也會給予預設值(數字型態為0、字元型態為NULL、布林型態為false)

我們可以用以下的範例來進一步熟悉它的特性:

class VariableDemo
{
   static int count=0;
   public void increment()
   {
       count++;
   }
   public static void main(String args[])
   {
       VariableDemo obj1=new VariableDemo();
       VariableDemo obj2=new VariableDemo();
       obj1.increment();
       obj2.increment();
       System.out.println("Obj1: count is="+obj1.count);
       System.out.println("Obj2: count is="+obj2.count);
   }
}

 輸出:

Obj1: count is=2
Obj2: count is=2

從上面的範例中我們可以發現,雖然是透過不同的物件中的 increment() 操作,但實際上操作的卻是同一區塊,證明 static 變數不是跟其他同 class 的元素一起放在堆積區,而是獨立放在靜態區,因此 obj1 和 obj2 的 count 都是指向記憶體的同一區塊。可以參考下圖(來源:yhhuang1966.blogspot.tw/2014/03/java_25.html)

java-memory

再來,我們再用一個範例示範一下 static 的存取特性:

public class main
{
    public static void main(String[] arg)
    {
        Variable v1 = new Variable(10);
        v1.printA();
        
        Variable v2 = new Variable(20);
        System.out.println("b= " + v2.b);
    }
}

class Variable
{
    int a;
    static int b;
    
    public Variable(int x)
    {
        a=x;
    }
    
    public void printA()
    {
        System.out.println("a= " + a);
    }
}

 結果:

a= 10
b= 0

 

參數(Parameters):

放在函式的標記式,用來說明這個函式,當它被呼叫時必須接收到什麼樣的資料(若跟小獅一樣會跟引數(Arguments)搞混的人,請參考這篇文章

void doStuff(String s, int a) {} // 我們預期兩個參數: String 和 int

來個範例:

 public class MyClass
{
    static int a;
    int b;
    
    public static void myMethod(int c)
    {
        try
        {
            int d;
        }
        catch (Exception e){}
    }

    MyClass(int f)
    {
        int[] g = new int[100];
    }
    
    public static void main(String[] arg){}
    // 略
}
  •  例外處理參數(exception-handler parameter):宣告在 catch 小括號內的變數,例如上面程式碼的 e。
  • 建構子參數(constructor parameter):宣告在 constructor 小括號內的變數,例如上面程式碼的 f。

變數的初始化:

區域變數 (Local variables) 必須手動初始化
實例變數(Instance variables、Non-Static Fields)靜態屬性變數 (Class Variables、Static Fields)通稱 屬性變數(Field Variable) 若未指定 Java 會給予預設值

變數預設值:

byte 0(byte 型態)
short 0(short 型態)
int 0
long 0L
float 0.0F
double 0.0D
char ‘u0000’(NULL)
boolean false

 

同場加映:

因為 variable shadowing 在 Java 中是不存在的,所以以下的範例二會無法編譯!

class X
{  
    public static void main(String args[])
    {
        {
            int a = 2;
        }
        {
            int a = 3;
        }       
    }
}

▲編譯成功

class X
{  
    public static void main(String args[])
    {

        int a = 2;

        {
            int a = 3;
        }       
    }
}

▲編譯失敗

但有趣的是,你可以在如下的範例中找到 variable shadowing 的”影子”

class A
{
    int x = 0;
    void m()
    {
        int x = 10; // Shadows this.x
    }
}

 


參考資料(Reference):

發表迴響