11. Final關鍵字
1、final修飾類:當用final修飾一個類時,表明這個類不能被繼承。 也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變量可以根據需要設爲final,但是要注意final類中的所有成員方法都會被隱式地指定爲final方法。
在使用final修飾類的時候,要注意謹慎選擇,除非這個類真的在以後不會用來繼承或者出於安全的考慮,儘量不要將類設計爲final類。
2、final修飾方法:“使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉爲內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。”
因此,如果只有在想明確禁止該方法在子類中被覆蓋的情況下才將方法設置爲final的。注:類的private方法會隱式地被指定爲final方法。
3、final修飾變量:對於一個final變量,如果是基本數據類型的變量,則其數值一旦在初始化之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓其指向另一個對象。
當final變量是基本數據類型以及String類型時,如果在編譯期間能知道它的確切值,則編譯器會把它當做編譯期常量使用。也就是說在用到該final變量的地方,相當於直接訪問的這個常量,不需要在運行時確定。
關鍵字final的好處小結
1、final關鍵字提高了性能。JVM和Java應用都會緩存final變量。
2、final變量可以安全的在多線程環境下進行共享,而不需要額外的同步開銷。
3、使用final關鍵字,JVM會對方法、變量及類進行優化。
4、對於不可變類,它的對象是隻讀的,可以在多線程環境下安全的共享,不用額外的同步開銷。
12. Static關鍵字
static關鍵字的用途
《Java編程思想》:“static方法就是沒有this的方法。在static方法內部不能調用非靜態方法,反過來是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。這實際上正是static方法的主要用途。”
這段話雖然只是說明了static方法的特殊之處,但是可以看出static關鍵字的基本作用,簡而言之,一句話來描述就是:
方便在沒有創建對象的情況下來進行調用(方法/變量)。
很顯然,被static關鍵字修飾的方法或者變量不需要依賴於對象來進行訪問,只要類被加載了,就可以通過類名去進行訪問。
static可以用來修飾類的成員方法、類的成員變量,另外可以編寫static代碼塊來優化程序性能。
1)、static方法
static方法一般稱作靜態方法,由於靜態方法不依賴於任何對象就可以進行訪問,因此對於靜態方法來說,是沒有this的,因爲它不依附於任何對象,既然都沒有對象,就談不上this了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,因爲非靜態成員方法/變量都是必須依賴具體的對象才能夠被調用。
但是要注意的是,雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變量,但是在非靜態成員方法中是可以訪問靜態成員方法/變量的。
因此,如果說想在不創建對象的情況下調用某個方法,就可以將這個方法設置爲static。我們最常見的static方法就是main方法,至於爲什麼main方法必須是static的,現在就很清楚了。因爲程序在執行main方法的時候沒有創建任何對象,因此只有通過類名來訪問。
2)、static變量
static變量也稱作靜態變量,靜態變量和非靜態變量的區別是:靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
static成員變量的初始化順序按照定義的順序進行初始化。
3)、static代碼塊
static關鍵字還有一個比較關鍵的作用就是用來形成靜態代碼塊以優化程序性能。static塊可以置於類中的任何地方,類中可以有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每個static塊,並且只會執行一次。
爲什麼說static塊可以用來優化程序性能,是因爲它的特性:只會在類加載的時候執行一次。因此,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行。
static關鍵字的誤區
1)、static關鍵字會改變類中成員的訪問權限嗎?
Java中的static關鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。
2)、能通過this訪問靜態成員變量嗎?
雖然對於靜態方法來說沒有this,那麼在非靜態方法中能夠通過this訪問靜態成員變量嗎?主要考察隊this和static的理解。在這裏永遠要記住一點:靜態成員變量雖然獨立於對象,但是不代表不可以通過對象去訪問,所有的靜態方法和靜態變量都可以通過對象訪問(只要訪問權限足夠)。
3)、static能作用於局部變量麼?
在Java中切記:static是不允許用來修飾局部變量,這是Java語法的規定。
4)、java中是否可以覆蓋(override)一個private方法或者static方法?
都不能
覆蓋,也就是我們常說的重寫,是子類繼承父類,且子類中的方法和父類中的方法,方法名相同,參數個數和類型相同,返回值相同。
private修飾的方法,不能被繼承,所以也不存在重寫(覆蓋)
static修飾的方法,是靜態方法,在編譯時就和類名就行了綁定。而重寫發生在運行時,動態綁定的。 何況static方法,跟類的實例都不相關,所以概念上也適用。
5)、靜態導包
Static還有一種不太常用的用法,即靜態導包用法,將類的方法直接導入到當前類中,從而直接使用“方法名”即可調用類方法,更加方便。
常見的筆試面試題
1、下面這段代碼的輸出結果是什麼?
public class TestMain {
static {
System.out.println("static block1");
}
public static void main(String[] args) {
// 在執行main方法之前會首先加載這個類,所以即使main方法中沒有語句,靜態代碼塊還是會執行
}
static {
System.out.println("static block2");
}
}
2、下面這段代碼的輸出結果是什麼?
public class Test1 extends Base {
static {
System.out.println("test static");
}
public Test1() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test1();
}
}
class Base {
static {
System.out.println("base static");
}
public Base() {
System.out.println("base constructor");
}
}
先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main方法,因爲main方法是程序的入口,但是在執行main方法之前,必須先加載Test1類,而在加載Test1類的時候發現Test1類繼承自Base類,因此會轉去先加載Base類,在加載Base類的時候,發現有static塊,便執行了static塊。在Base類加載完成之後,便繼續加載Test1類,然後發現Test1類中也有static塊,便執行static塊。在加載完所需的類之後,便開始執行main方法。在main方法中執行new Test1()的時候會先調用父類的構造器,然後再調用自身的構造器。因此,便出現了上面的輸出結果。
3、下面這段代碼的輸出結果是什麼?
public class Test2 {
Person person = new Person("Test");
static {
System.out.println("test static");
}
public Test2() {
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 Test2 {
Person person = new Person("MyClass");
static {
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
首先加載Test類,因此會執行Test類中的static塊。接着執行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被加載了,所以只需要加載MyClass類,那麼就會執行MyClass類的中的static塊。在加載完之後,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類並執行Person類中的static塊,接着執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接着執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。
4、下面這段代碼的輸出結果是什麼?
public class Test3 {
// 由於類只加載一次,所以看效果時只能執行一句
public static void main(String[] args) {
/**
* 只輸出classB
* 但是當str沒有final修飾時,會輸出
* A
* B
* classB
*/
// System.out.println(B.str);
/**
* 輸出
* A
* C
* classC
*/
// System.out.println(C.str);
/**
* 均輸出
* A
* D
* 200
*/
// System.out.println(D.bb);
// System.out.println(D.aa);
/**
* 只輸出200
*/
System.out.println(D.cc);
}
}
class A {
static {
System.out.println("A");
}
}
class B extends A {
static {
System.out.println("B");
}
public static final String str = "calssB";
}
class C extends A {
static {
System.out.println("C");
}
public static final String str = new String("classC");
}
class D extends A {
static {
System.out.println("D");
}
public static final int cc = 200;
public static final Integer aa = 100;
public static final Integer bb = new Integer(200);
}
總結:調用類的靜態成員或方法,會引起類的初始化,調用類中常量成員則不會引起類的初始化。
13. this和super
1、從本質上講,this是一個指向本對象的指針,然而super是一個Java關鍵字,用來對父類進行調用。
2、this:它代表當前對象的引用(在程序中易產生二義性之處,應使用this來指明當前對象;如果函數的形參與類中的成員數據同名,這時需用this來指明成員變量名)。
3、可以使用super關鍵字來引用父類(最近父類)的成員變量,方法與構造器。(用來訪問直接父類中被隱藏的成員數據或函數,基類與派生類中有相同成員定義時,如:super.變量名、super.成員函數名(實參))。
4、super(參數):調用基類中的某一個構造函數(應該爲構造函數中的第一條語句)。
調用super()必須寫在子類構造方法的第一行,否則編譯不通過。每個子類構造方法的第一條語句,都是隱含地調用super(),如果父類沒有這種形式的構造函數,那麼在編譯的時候就會報錯。
5、this(參數):調用本類中另一種形成的構造函數(應該爲構造函數中的第一條語句)。儘管可以用this調用一個構造器,但卻不能調用兩個。
super()和this()類似,均需放在構造方法內第一行。區別是,super()從子類中調用父類的構造方法,this()在同一類內調用其它構造方法。
this和super不能同時出現在一個構造函數裏面,因爲this必然會調用其它的構造函數,其它的構造函數必然也會有super語句的存在,所以在同一個構造函數裏面有相同的語句,就失去了語句的意義,編譯器也不會通過。(而且從this和super都要求放在構造函數的第一行來看,它們也無法同時存在一個構造方法裏面)。
6、this()和super()都指的是對象,所以,均不可以在static環境中使用。包括:static變量,static方法,static語句塊。