參考android listview 聲明ViewHolder內(nèi)部類時,為什么建議使用static關鍵字
這個問題也是我每次面試別人必問的問題之一。其實這個是考靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類的主要區(qū)別之一蕴坪。非靜態(tài)內(nèi)部類會隱式持有外部類的引用,就像大家經(jīng)常將自定義的adapter在Activity類里,然后在adapter類里面是可以隨意調(diào)用外部activity的方法的。當你將內(nèi)部類定義為static時,你就調(diào)用不了外部類的實例方法了,因為這時候靜態(tài)內(nèi)部類是不持有外部類的引用的。聲明ViewHolder靜態(tài)內(nèi)部類,可以將ViewHolder和外部類解引用童擎。大家會說一般ViewHolder都很簡單芯砸,不定義為static也沒事吧包帚。確實如此谋梭,但是如果你將它定義為static的纤垂,說明你懂這些含義逃糟。萬一有一天你在這個ViewHolder加入一些復雜邏輯琐谤,做了一些耗時工作织阳,那么如果ViewHolder是非靜態(tài)內(nèi)部類的話刽宪,就很容易出現(xiàn)內(nèi)存泄露。如果是靜態(tài)的話,你就不能直接引用外部類纲酗,迫使你關注如何避免相互引用饶囚。 所以將 ViewHolder內(nèi)部類 定義為靜態(tài)的规惰,是一種好習慣.
本文參考
Java中的static關鍵字解析
Java關鍵字之final和static
一、final
Java語言里,final關鍵字有多種用途,其主題都表示“不可變”衅鹿,但背后的具體內(nèi)容并不一樣。當final關鍵字用于修飾類時表示該類不允許被繼承纬霞;當它用于修飾方法時表示該方法在派生類里不允許被覆寫(override)。當final關鍵字用于修飾變量時表示該變量的值不可變澎蛛;靜態(tài)變量气堕、實例成員變量煮寡、形式參數(shù)和局部變量都可以被final修飾炭菌。
1.final類
當一個類聲明為final類黑低,也就證明這個類是不能夠被繼承的勋眯,即禁止繼承塞蹭,因此final類的成員方法是沒有機會被覆蓋的婉烟,這個final類的功能是完整的。在Java中有很多類是final的,如String卵慰、Interger以及其他包裝類。
final類的好處:不可變類有很多的好處佛呻,它們的對象是只讀的裳朋,可以在多線程環(huán)境下安全的共享,不用額外的開銷吓著。
下面是final類的實例:
final class PersonalLoan{
}
class CheapPersonalLoan extends PersonalLoan{ //compilation error: cannot inherit from final class
}
2.final方法
如果一個類不允許其子類覆蓋某個方法鲤嫡,即不能被重寫,則可以把這個方法聲明為final方法绑莺。(類中所有的private方法都隱式的指定為final)暖眼。
使用final方法的原因:
- 方法鎖定,防止任何繼承類修改它的含義纺裁,確保在繼承中使方法行為保持不變且不被覆蓋诫肠;
- 效率,將一個方法指明為final欺缘,就是同意編譯器將針對該方法的所有調(diào)用都轉化為內(nèi)嵌調(diào)用(相當于在編譯的時候已經(jīng)靜態(tài)綁定栋豫,不需要在運行時再動態(tài)綁定)杜跷。
下面是final方法的實例:
public class Test1 {
public static void main(String[] args) {
}
public void f1() {
System.out.println("f1");
}
//final方法
public final void f2() {
System.out.println("f2");
}
}
public class Test2 extends Test1 {
public void f1(){
System.out.println("Test1父類方法f1被覆蓋!");
}
public static void main(String[] args) {
Test2 t=new Test2();
t.f1(); //子類重寫父類的方法
t.f2(); //調(diào)用從父類繼承過來的final方法
}
}
3.final變量
程序中有些數(shù)據(jù)的恒定不變是很有必要的讨阻,比如:
- 一個永不改變的編譯時常量;
- 一個在運行時被初始化的值嘹叫,而在程序的后面不希望它被改變嫩絮。
這種類型的變量只能被賦值一次骡送,一旦被賦值之后,就不能夠再更改了絮记。
有幾點要注意的:
- 一個既是static又是final的域只占據(jù)一段不能改變的存儲空間,一般用大寫來表示虐先;
- final使數(shù)值恒定不變怨愤,而當用于對象時,final使引用恒定不變(一旦引用指向一個對象蛹批,就無法再把它改為指向另一個對象)撰洗;
final變量的好處:
- 提高性能,JVM和Java應用程序都會緩存final變量腐芍;
- final變量可以在安全的在多線程環(huán)境下進行共享差导,而不需要額外的開銷。
public class Test {
public static final int PI = 3.14;//這個變量是只讀的
public final int INIT; //final空白,必須在初始化對象的時候賦初值
public Test(int x) {
INIT = x;
}
public static void main(String[] args) {
Test t = new Test(2);
//t.PI=3.1415;//出錯,final變量的值一旦給定就無法改變
System.out.println(t.INIT);
}
}
4.總結
- 本地變量必須在聲明的時候賦值猪勇;
- 在匿名類中所有變量都必須是final變量设褐;
- final方法不能被重寫;
- final類不能被繼承;
- final成員變量必須在聲明的時候初始化或者在構造器中初始化助析,否則就會報編譯錯誤犀被;
- 接口中聲明的所有變量本身是final的;
- final方法在編譯階段綁定外冀,稱為靜態(tài)綁定(static binding)寡键;
- 對于集合對象聲明為final指的是引用不能被更改,但是你可以向其中增加雪隧,刪除或者改變內(nèi)容西轩;
二、static
1.static方法
Math類中有很多計算方法脑沿,不需要初始化對象藕畔,就能直接調(diào)用,這正是靜態(tài)方法的主要用途捅伤。靜態(tài)方法由于不依賴于任何對象劫流,也就沒有this,當然也不能訪問類的非靜態(tài)變量和方法丛忆,因為它們需要依賴具體的對象才能調(diào)用祠汇。
如果一個方法和他所在類的實例對象無關,那么它就應該是靜態(tài)的熄诡,反之他就應該是非靜態(tài)的可很。如果我們確實應該使用非靜態(tài)的方法,但是在創(chuàng)建類時又確實只需要維護一份實例時凰浮,就需要用單例模式了我抠。
比如說我們在系統(tǒng)運行時候,就需要加載一些配置和屬性袜茧,這些配置和屬性是一定存在了菜拓,又是公共的,同時需要在整個生命周期中都存在笛厦,所以只需要一份就行纳鼎,這個時候如果需要我再需要的時候new一個,再給他分配值裳凸,顯然是浪費內(nèi)存并且再賦值沒什么意義贱鄙,所以這個時候我們就需要單例模式或靜態(tài)方法去維持一份且僅這一份拷貝,但此時這些配置和屬性又是通過面向對象的編碼方式得到的姨谷,我們就應該使用單例模式逗宁,或者不是面向對象的,但他本身的屬性應該是面對對象的梦湘,我們使用靜態(tài)方法雖然能同樣解決問題瞎颗,但是最好的解決方案也應該是使用單例模式件甥。
2.static類
參考知乎-為什么Java內(nèi)部類要設計成靜態(tài)和非靜態(tài)兩種?
從字面上看言缤,一個被稱為靜態(tài)嵌套類嚼蚀,一個被稱為內(nèi)部類。
從字面的角度解釋是這樣的:
什么是嵌套管挟?嵌套就是我跟你沒關系轿曙,自己可以完全獨立存在,但是我就想借你的殼用一下僻孝,來隱藏一下我自己(真TM猥瑣)导帝。
什么是內(nèi)部?內(nèi)部就是我是你的一部分穿铆,我了解你您单,我知道你的全部,沒有你就沒有我荞雏。(所以內(nèi)部類對象是以外部類對象存在為前提的)
注意android listview 聲明ViewHolder內(nèi)部類:
// ViewHolder靜態(tài)類
static class ViewHolder {
public ImageView img;
public TextView title;
public TextView content;
}
參考android listview 聲明ViewHolder內(nèi)部類時虐秦,為什么建議使用static關鍵字
這個問題也是我每次面試別人必問的問題之一。其實這個是考靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類的主要區(qū)別之一凤优。非靜態(tài)內(nèi)部類會隱式持有外部類的引用悦陋,就像大家經(jīng)常將自定義的adapter在Activity類里,然后在adapter類里面是可以隨意調(diào)用外部activity的方法的筑辨。當你將內(nèi)部類定義為static時俺驶,你就調(diào)用不了外部類的實例方法了,因為這時候靜態(tài)內(nèi)部類是不持有外部類的引用的棍辕。聲明ViewHolder靜態(tài)內(nèi)部類暮现,可以將ViewHolder和外部類解引用。大家會說一般ViewHolder都很簡單楚昭,不定義為static也沒事吧栖袋。確實如此,但是如果你將它定義為static的抚太,說明你懂這些含義栋荸。萬一有一天你在這個ViewHolder加入一些復雜邏輯,做了一些耗時工作凭舶,那么如果ViewHolder是非靜態(tài)內(nèi)部類的話,就很容易出現(xiàn)內(nèi)存泄露爱沟。如果是靜態(tài)的話帅霜,你就不能直接引用外部類,迫使你關注如何避免相互引用呼伸。 所以將 ViewHolder內(nèi)部類 定義為靜態(tài)的身冀,是一種好習慣.
另外钝尸,使用使用靜態(tài)內(nèi)部類實現(xiàn)單例模式,這種方法也是《Effective Java》上所推薦的
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
這種寫法仍然使用JVM本身機制保證了線程安全問題搂根;由于 SingletonHolder 是私有的珍促,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的剩愧;同時讀取實例的時候不會進行同步猪叙,沒有性能缺陷;也不依賴 JDK 版本仁卷。
3.static變量
參考Java中靜態(tài)變量的適用場景
靜態(tài)變量被所有的對象所共享穴翩,在內(nèi)存中只有一個副本,它當且僅當在類初次加載時會被初始化锦积。而非靜態(tài)變量是對象所擁有的芒帕,在創(chuàng)建對象的時候被初始化,存在多個副本丰介,各個對象擁有的副本互不影響背蟆。
另外,注意在C/C++中static是可以作用域局部變量的哮幢,但是在Java中切記:static是不允許用來修飾局部變量带膀。不要問為什么,這是Java語法的規(guī)定家浇。
public class WeekB{
static class Data {
private int week;
private String name;
Data(int i, String s) {
week= i;
name = s;
}
}
static Data weeks[] = {
new Data(1, "Monday"),
new Data(2, "Tuesay"),
new Data(3, "Wednesday"),
new Data(4, "Thursday"),
new Data(5, "Friday"),
new Data(6, "Saturday"),
new Data(7, "Sunday")
};
public static void main(String args[]) {
final int N = 10000;
WeekB weekinstance;
for (int i = 1; i <= N; i++){
weekinstance = new WeekB ();
}
}
}
在類WeekB中本砰,在Data weeks[]之前添加了static關鍵字,將該對象變量聲明為靜態(tài)的钢悲,因此當你創(chuàng)建10000個WeekB對象時系統(tǒng)中只保存著該對象的一份拷貝点额,而且該類的所有對象實例共享這份拷貝,這無疑節(jié)約了大量的不必要的內(nèi)存開銷.
那么是不是我們應該盡量地多使用靜態(tài)變量呢莺琳?其實不是這樣的还棱,因為靜態(tài)變量生命周期較長,而且不易被系統(tǒng)回收惭等,因此如果不能合理地使用靜態(tài)變量珍手,就會適得其反,造成大量的內(nèi)存浪費辞做,所謂過猶不及琳要。因此,建議在具備下列全部條件的情況下秤茅,盡量使用靜態(tài)變量:
(1)變量所包含的對象體積較大稚补,占用內(nèi)存較多。
(2)變量所包含的對象生命周期較長框喳。
(3)變量所包含的對象數(shù)據(jù)穩(wěn)定课幕。
(4)該類的對象實例有對該變量所包含的對象的共享需求厦坛。
如果變量不具備上述特點建議你不要輕易地使用靜態(tài)變量,以免弄巧成拙乍惊。
4.static代碼塊
static塊可以置于類中的任何地方杜秸,類中可以有多個static塊。在類初次被加載的時候润绎,會按照static塊的順序來執(zhí)行每個static塊撬碟,并且只會執(zhí)行一次。
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
isBornBoomer是用來這個人是否是1946-1964年出生的凡橱,而每次isBornBoomer被調(diào)用的時候小作,都會生成startDate和birthDate兩個對象,造成了空間浪費稼钩,如果改成這樣效率會更好:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
因此顾稀,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行。
三坝撑、筆試題
1.下面這段代碼的輸出結果是什么静秆?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
base static
test static
base constructor
test constructor
在執(zhí)行開始,先要尋找到main方法巡李,因為main方法是程序的入口抚笔,但是在執(zhí)行main方法之前,必須先加載Test類侨拦,而在加載Test類的時候發(fā)現(xiàn)Test類繼承自Base類殊橙,因此會轉去先加載Base類,在加載Base類的時候狱从,發(fā)現(xiàn)有static塊膨蛮,便執(zhí)行了static塊。在Base類加載完成之后季研,便繼續(xù)加載Test類敞葛,然后發(fā)現(xiàn)Test類中也有static塊,便執(zhí)行static塊与涡。在加載完所需的類之后惹谐,便開始執(zhí)行main方法。在main方法中執(zhí)行new Test()的時候會先調(diào)用父類的構造器驼卖,然后再調(diào)用自身的構造器氨肌。
2.這段代碼的輸出結果是什么?
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
首先加載Test類酌畜,因此會執(zhí)行Test類中的static塊儒飒。接著執(zhí)行new MyClass(),而MyClass類還沒有被加載檩奠,因此需要加載MyClass類桩了。在加載MyClass類的時候,發(fā)現(xiàn)MyClass類繼承自Test類埠戳,但是由于Test類已經(jīng)被加載了井誉,所以只需要加載MyClass類,那么就會執(zhí)行MyClass類的中的static塊整胃。在加載完之后颗圣,就通過構造器來生成對象。而在生成對象的時候屁使,必須先初始化父類的成員變量在岂,因此會執(zhí)行Test中的Person person = new Person(),而Person類還沒有被加載過蛮寂,因此會先加載Person類并執(zhí)行Person類中的static塊蔽午,接著執(zhí)行父類的構造器,完成了父類的初始化酬蹋,然后就來初始化自身了及老,因此會接著執(zhí)行MyClass中的Person person = new Person(),最后執(zhí)行MyClass的構造器范抓。
3.這段代碼的輸出結果是什么骄恶?
public class Test {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}
test static 1
test static 2
雖然在main方法中沒有任何語句,但是還是會輸出匕垫,原因上面已經(jīng)講述過了僧鲁。另外,static塊可以出現(xiàn)類中的任何地方(只要不是方法內(nèi)部象泵,記住寞秃,任何方法內(nèi)部都不行),并且執(zhí)行是按照static塊的順序執(zhí)行的单芜。