1. 程式人生 > >Java基礎4——深入理解final關鍵字和static關鍵字以及初始化順序

Java基礎4——深入理解final關鍵字和static關鍵字以及初始化順序

深入理解final關鍵字和static關鍵字以及初始化順序

final關鍵字(基礎1中提到)

final關鍵字可以修飾類、方法和引用。
修飾類,該類不能被繼承。並且這個類的物件在堆中分配記憶體後地址不可變。
修飾方法,方法不能被子類重寫。
修飾引用,引用無法改變,對於基本型別,無法修改值,對於引用,雖然不能修改地址值,但是可以對指向物件的內部進行修改。

final class Fi { 
        int a; 
        final int b = 0; 
        Integer s;

}
class Si{//空白final
    //一般情況下final修飾的變數一定要被初始化。
//只有下面這種情況例外,要求該變數必須在構造方法中被初始化。 //並且不能有空引數的構造方法。 //這樣就可以讓每個例項都有一個不同的變數,並且這個變數在每個例項中只會被初始化一次 //於是這個變數在單個例項裡就是常量了。 final int s ; Si(int s) { this.s = s; } } class Bi { final int a = 1; final void go() { //final修飾方法無法被繼承 } } class Ci extends Bi { final
int a = 1; // void go() { // //final修飾方法無法被繼承 // } } final char[]a = {'a'}; final int[]b = {1}; --------------------- 作者:How 2 Play Life 來源:CSDN 原文:https://blog.csdn.net/a724888/article/details/80048792 版權宣告:本文為博主原創文章,轉載請附上博文連結!

  final修飾方法時,此方法不能被重寫,但是注意:因為重寫前提是子類可以從父類中繼承此方法,如果父類中final修飾的方法同樣訪問控制權限是private,將會導致子類中不能直接繼承此方法,因此,此時可以在子類中定義相同的方法名和引數,此時不再產生重寫與final的矛盾,而在子類中重新定義了新的方法

public class B extends A {

    public static void main(String[] args) {

    }

    public void getName() {
        
    }
}

class A {

    /**
     * 因為private修飾,子類中不能繼承到此方法,因此,子類中的getName方法是重新定義的、
     * 屬於子類本身的方法,編譯正常
     */
    private final void getName() {
        
    }

    /* 因為pblic修飾,子類可以繼承到此方法,導致重寫了父類的final方法,編譯出錯
    */
    public final void getName() {
    
    }
    
}

  修飾變數是final用得最多的地方,final成員變量表示常量,只能被賦值一次,賦值後值不再改變。
  當final修飾一個基本資料型別時,表示該基本資料型別的值一旦在初始化後便不能發生變化;如果final修飾一個引用型別時,則在對其初始化之後便不能再讓其指向其他物件了,但該引用所指向的物件的內容是可以發生變化的。本質上是一回事,因為引用的值是一個地址,final要求值,即地址的值不發生變化。
  final修飾一個成員變數(屬性),必須要顯示初始化。這裡有兩種初始化方式,一種是在變數宣告的時候初始化;第二種方法是在宣告變數的時候不賦初值,但是要在這個變數所在的類的所有的建構函式中對這個變數賦初值
  當函式的引數型別宣告為final時,說明該引數是隻讀型的。即你可以讀取使用該引數,但是無法改變該引數的值。

問題1:類的final變數和普通變數有什麼區別?

  當用 final作用於類的成員變數時,成員變數(注意是類的成員變數,區域性變數只需要保證在使用之前被初始化賦值即可)必須在定義時或者構造器中進行初始化賦值,而且final變數一旦被初始化賦值之後,就不能再被賦值了。

public class Test { 
    public static void main(String[] args)  { 
        String a = "hello2";   
        final String b = "hello"; 
        String d = "hello"; 
        String c = b + 2;   
        String e = d + 2; 
        System.out.println((a == c)); //true 當final變數是基本資料型別以及String型別時,如果在編譯期間能知道它的確切值,則編譯器會把它當做編譯期常量使用。
        System.out.println((a == e)); //false
    } 
}

問題2:final引數的問題

package leetcode.greedy;

public class TestFinal {
	public static void main(String arg[]) {
		TestFinal testFinal = new TestFinal();
        int i = 0;
        testFinal.changeValue(i);
        System.out.println(i);     
    } 
    public void changeValue(final int i){
    	i++; //The final local variable i cannot be assigned. It must be blank and not using a compound assignment
        System.out.println(i);
    	i++;//去掉final
        System.out.println(i);//1,0
    }
}

  上面這段程式碼changeValue方法中的引數i用final修飾之後,就不能在方法中更改變數i的值了。值得注意的一點,方法changeValue和main方法中的變數i根本就不是一個變數,因為java引數傳遞採用的是值傳遞,對於基本型別的變數,相當於直接將變數進行了拷貝。所以即使沒有final修飾的情況下,在方法內部改變了變數i的值也不會影響方法外的i。

package leetcode.greedy;
public class TestFinal {
	public static void main(String arg[]) {
		 TestFinal testFinal = new TestFinal();
	     StringBuffer buffer = new StringBuffer("hello");
	     testFinal.changeValue(buffer);
	     System.out.println("fistbuffer:"+buffer);
	        
	}
	public void changeValue(final StringBuffer buffer){
	     buffer = new StringBuffer("hi");//final修飾引用型別的引數,不能再讓其指向其他物件,但是對其所指向的內容是可以更改的。
	     buffer.append("world");
	     System.out.println("lastbuffer:"+buffer);
	}
}

  執行這段程式碼就會發現輸出結果為 helloworld。很顯然,用final進行修飾雖不能再讓buffer指向其他物件,但對於buffer指向的物件的內容是可以改變的。

public class TestFinal {
    public static void main(String[] args){
        TestFinal testFinal = new TestFinal();
        StringBuffer buffer = new StringBuffer("hello");
        testFinal.changeValue(buffer);
        System.out.println(buffer);      
    }
    public void changeValue(StringBuffer buffer){
        //buffer重新指向另一個物件
        buffer = new StringBuffer("hi");
        buffer.append("world");
        System.out.println(buffer);
    }
}
hiworld
hello

static關鍵字

  關鍵字static表示「靜態的」,主要思想是保證無論該類是否產生物件或無論產生多少物件的情況下,某些特定的資料在記憶體空間中只有一份
  在Java類中,可用static修飾屬性、方法、程式碼塊和內部類,而不能修飾構造器。
  其中,被修飾後的成員具有如下特點:

  • 隨著類的載入而載入,故優先於物件存在;
  • 所修飾的成員,被該類的所有物件所共享;
  • 訪問許可權允許時,可不建立物件,直接被類呼叫。

  關鍵字static大體上有一下五種用法:
1.靜態匯入
2.靜態變數
3.靜態方法
4.靜態程式碼塊
5.靜態內部類

1.靜態匯入:

// 靜態導包,在類中使用Math的靜態方法和屬性時可以省略「Math.」 
import static java.lang.Math.*; 
public class StaticImport {
public static void main(String[] args) { 
	double a = cos(PI / 2); //已省略「Math.」 
	double b = pow(2.4,1.2); 
	double r = max(a,b); 
	System.out.print(r);
	}
}

  當在程式中多次使用某型別的靜態成員(靜態屬性和靜態方法)時,即可使用靜 態匯入,作用是將該型別的靜態成員引入到當前的名稱空間,那麼在程式中呼叫 該型別的靜態成員時可以像呼叫本類內定義的成員一樣,直接呼叫,而無需採用 「類名.靜態成員名」的方式。

2.靜態變數:在Java中切記:static是不允許用來修飾區域性變數
  在Java類中,用static修飾的屬性為靜態變數(類變數或類屬性),而非static 修飾的屬性為例項變數,區別如下:
在這裡插入圖片描述
3.靜態方法:
  在Java類中,用static修飾的方法為靜態方法,也叫類方法,其與非靜態方法 的對比如下:
在這裡插入圖片描述
4.靜態程式碼塊:
  靜態程式碼塊僅在類載入時執行一次,主要用於對Java類的靜態變數(類屬性) 進行初始化操作。
  執行順序:靜態程式碼塊 > 構造程式碼塊(非靜態程式碼塊) > 構造方法。
5.靜態內部類
  內部類的一種,靜態內部類不依賴於外部類,即可以不依賴於外部類例項物件而 被例項化,且不能訪問外部類的非靜態成員(屬性和方法)。

***注意點:***
1.Java中的static關鍵字不會影響到變數或者方法的作用域。
2.static是不允許用來修飾區域性變數。

變數初始化順序

  在有繼承關係的情況下,變數初始化順序如下:

  1. 父類的靜態變數和靜態程式碼塊
  2. 子類的靜態變數和靜態程式碼塊
  3. 父類的例項變數和普通程式碼塊
  4. 父類的建構函式
  5. 子類的例項變數和普通程式碼塊
  6. 子類的建構函式
public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }
     
    public Test() {
        System.out.println("test constructor");
    }
     
    public static void main(String[] args) {
        new MyClass();
    }
}
 
class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}
 
 
class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }
     
    public MyClass() {
        System.out.println("myclass constructor");
    }
}
輸出結果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
class A { 
	static { 
		System.out.print("1"); 
	} 
	public A() { 
		System.out.print("2"); 
	} 
}
class B extends A{ 
	static { 
		System.out.print("a"); 
	} 
	public B() { 
		System.out.print("b"); 
	}
}
public class Hello { 
	public static void main(String[] args) { 
		A ab = new B(); ab = new B(); 
	}
}
輸出結果:
1a2b2b //靜態程式碼塊只初始化一次