內部類、final與垃圾回收,面試時你一說,面試官就知道


    內部類並不常用,而且使用起來有一定的定式,比如在下面的InnterDemoByTrhead.java里,我們通過內部類的形式創建線程。    

1	public class InnerDemoByThread {
2		public static void main(String[] args) {
3			// 實現runnable接口,創建10個線程並啟動
4			for(int threadCnt = 0;threadCnt<10;threadCnt++)
5			new Thread(new Runnable() {
6				public void run() {
7					for (int i = 0; i < 5; i++) {
8						//在每個線程里,輸出0到4 System.out.println(Thread.currentThread().getName()+":"+ i);
9	         		}
10				}
11			}).start();//這里的括號是和第5行對應,注意需要帶分號
12		}
13	}

    在上述的第4行里,我們通過for循環創建了10個線程,在第5行里,我們通過new Runnable定義了線程內部的動作,具體而言,在第6到第10行的代碼里,定義了打印0到4的動作。這里第5行通過new Thread定義的類,是在第1行定義的InnerDemoByThread類的內部,所以叫內部類,這也是內部類典型的用法。

    雖然內部類出現的機會不多,但其中有個非常重要的知識點:當方法的參數需要被內部類使用時,那么這個參數必須是final,否則會報語法錯誤。我們在講線程的時候,通過內部類比較了線程安全和不安全集合的表現。這里我們通過改寫這個案例,着重看下“內部類“和“final“的要點,請大家看下如下的InnerFinalDemo.java代碼。    

1	import java.util.ArrayList;
2	import java.util.List;
3	public class InnerFinalDemo {
4		public static int addByThreads(final List list) {
5			// 創建一個線程組
6			ThreadGroup group = new ThreadGroup("Group");
7			// 通過內部類的方法來創建多線程
8			Runnable listAddTool = new Runnable() {
9				public void run() {// 在其中定義線程的主體代碼	
10					list.add("0"); // 在集合里添加元素				
11				}
12			};
13			// 啟動10個線程,同時向集合里添加元素
14			for (int i = 0; i < 10; i++) {
15				new Thread(group, listAddTool).start();
16			}
17			while (group.activeCount() > 0) {
18				try { Thread.sleep(10);	} 
19	             catch (InterruptedException e) 
20	             { e.printStackTrace(); }
21			}
22			return list.size(); // 返回插入后的集合長度
23		}
24		public static void main(String[] args) {
25			List list = new ArrayList();	
26			//很大可能返回10
27			System.out.println(addByThreads(list));
28		}
29	}

    這段代碼的邏輯是,在main函數的第25行里,我們創建了一個線程不安全的ArrayList類型的對象,並在第27行調用了addByThreads方法返回list的長度。在addByThreads方法里,我們在第14行里,通過for循環啟動了10個線程,在這10個線程的主體邏輯(第9行的run方法)里,我們在第10行通過list.add方法給集合對象添加元素。

    從功能上講,第27行的打印語句能輸出10,因為雖然ArrayList是線程不安全對象,但僅僅是10個線程同時操作,不足以發生“線程搶占”的情況。

    但本代碼的重點是內部類和final,在代碼第3行定義的addByThreads方法里,我們注意到參數list前一定得加final,否則會報語法錯誤。我們可以通過如下的思維步驟來理解這個要點。

    第一,第3行的這個帶final的list對象從屬於外部的InnerFinalDemo類,並且,在第8到12行的內部類里,也會用到這個對象,也就是說,在外部類和內部類里,都會用到這個對象。

    第二,外部類和內部類是平行的,內部類並不從屬於外部類,這句話隱藏的含義是,外部類有可能在內部類之前被回收。

    那么如果我們不加final,一旦外部類在內部類之前被回收,那么外部類里所包含的list對象也會被回收,但這時,內部類尚未使用這個list。在這種情況下,一旦內部類使用了list,就會報空指針錯(因為這個對象已經隨着外部類被回收了)。

    為了避免這種錯誤,在指定語法時就加上了“當方法的參數需要被內部類使用時,那么這個參數必須是final”這個規定。一旦在此類參數前加final,那么這個參數就是常量了,存儲的位置就不是“堆區”了,而是“常量池”,這樣即使外部類被先回收,那么由於這類參數(比如list)不存在於外部類所從屬的堆空間(而是常量池),所以會繼續存在,這樣內部類就能繼續使用。

    一些資深的面試官不會面試內部類的細節語法(因為不常用,而且使用起來有定式),而會考察上述的“參數和final”的知識點,所以大家在被問及”對內部類的掌握程度“這類問題時,可以按如下的思路來敘述。

    第一,無需敘述內部類中各種語法,事實上,內部類涉及到“如何定義”以及“內部類中對象的可見性”等問題,語法相對而言比較復雜,說起來不容易,而且即使說清楚了,也無法很好體現大家的能力。

    第二,可以直接說,“當方法的參數需要被內部類使用時,那么這個參數必須是final”,同時解釋下原因。當面試官聽到這以后,一般就不再問內部類問題了,因為他會認為,候選人連這么“資深”的知識也知道,那么就沒必要再細問內部類的問題了。

    第三,由於已經引出“垃圾回收”的話題,所以大家可以找機會進一步按本章給出的提示,展示在這方面的能力,這樣就有很大可能得到“Java Core方面比較資深”的評價。

   上述敘述是針對jdk1.7以及之前版本的,如果是針對jdk1.8版本,不需要顯式地加final,但依然會被當常量管理,具體來講,該對象的引用無法指向新的內存空間。

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM