JAVA面試2

1、List遍歷時刪除的幾種方式比較

1.1、會報錯的刪除方式:
(1)在Iterator遍歷時使用list刪除
   
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. Iterator<String> it = list.iterator();  
  2.        while(it.hasNext()){  
  3.            String item = it.next();  
  4.            list.remove(item);    //報錯!!!  
  5. }  

(2)foreach遍歷方式中刪除
   
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for(String s : list){  
  2.           list.remove(s); //報錯!!!  
  3. }  

以上都是報java.util.ConcurrentModificationException,某個線程在 Collection 上進行迭代時,通常不允許另一個線性修改該 Collection,因爲在這些情況下,迭代的結果是不確定的。
而對於foreach實際上使用的是iterator進行處理的,而iterator是不允許集合在iterator使用期間通過list刪除的,也就是第一種方式,也就是說上面兩種方式相當於是同一種。

1.2、不會報錯,但是有可能漏刪或不能完全的刪除方式:
(1)漏刪的情況(通過索引下標的方式)
       
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. List<Integer> list = new ArrayList<Integer>();  
  2. list.add(1);  
  3. list.add(2);  
  4. list.add(2);  
  5. list.add(3);  
  6. list.add(4);  
  7. System.out.println("----------list大小1:--"+list.size());  
  8. for (int i = 0; i < list.size(); i++) {  
  9.     if (2 == list.get(i)) {  
  10.         list.remove(i);      
  11.     }  
  12.     System.out.println(list.get(i));  
  13. }  
  14. System.out.println("最後輸出=" + list.toString());     

輸出的結果如下:
----------list大小1:--5
1
2
3
4
最後輸出=[1, 2, 3, 4]
可以看到,只刪除了一個2,還有一個沒有完全刪除,原因是:刪除了第一個2後,集合裏的元素個數減1,後面的元素往前移了1位,此時,第二個2已經移到了索引index=1的位置,而此時i馬上i++了,list.get(i)獲得的是數據3。

(2)不能完全刪除的情況
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. List<Integer> list = new ArrayList<Integer>();  
  2. list.add(1);  
  3. list.add(2);  
  4. list.add(2);  
  5. list.add(3);  
  6. list.add(4);  
  7. System.out.println("----------list大小1:--"+list.size());  
  8. for (int i = 0; i < list.size(); i++) {  
  9.     list.remove(i);  
  10. }  
  11. System.out.println("最後輸出=" + list.toString());  

輸出的結果如下:
----------list大小1:--5
最後輸出=[2, 3]
可以看到,結果並沒有按照我們的想法,把所有數據都刪除乾淨。原因是:在list.remove之後,list的大小發生了變化,也就是list.size()一直在變小,而 卻一直在加大,當 i =3時,list.size()=2,此時循環的判斷條件不滿足,退出了程序。

以上兩種情況通過for循環遍歷刪除,都沒有正確達到目的,都是因爲在remove後list.size()發生了變化(一直在減少),同時後面的元素會往前移動,導致list中的索引index指向的數據有變化。同時我們的for中的i是一直在加大的!

1.3 List遍歷過程中刪除元素的推薦做法

還是使用Iterator遍歷,但是不用list來remove。如下代碼:
       
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. List<Integer> list = new ArrayList<Integer>();  
  2. list.add(1);  
  3. list.add(2);  
  4. list.add(2);  
  5. list.add(3);  
  6. list.add(4);  
  7. System.out.println("----------list大小1:--"+list.size());  
  8. Iterator<Integer> it = list.iterator();  
  9. while(it.hasNext()){  
  10.     Integer item = it.next();  
  11.     if (2 == item) {  
  12.         it.remove();  
  13.     }  
  14.     System.out.println(item);  
  15. }  
  16. System.out.println("最後輸出=" + list.toString());  

輸出結果:
----------list大小1:--5
1
2
2
3
4
最後輸出=[1, 3, 4]
此時,兩個2被全部刪除了。

對於iterator的remove()方法,也有需要我們注意的地方:

1、每調用一次iterator.next()方法,只能調用一次remove()方法。

2、調用remove()方法前,必須調用過一次next()方法。


2、Java基本數據類型及包裝類

byte(字節)              8 位               Byte
shot(短整型)          16               Short
int(整型)                32 位              Integer
long(長整型)         64 位               Long
float(浮點型)         32 位               Float
double(雙精度)     64 位               Double
char(字符型)          16 位               Character
boolean(布爾型)    1 位                Boolean

各數據類型按容量大小(表數範圍大小)由小到大排列爲:

     byte <—— short, char  <——int <——long <——float <——double

基本類型之間的轉換原則:

    1)運算時,容量小的類型自動轉換爲容量大的類型;

    2)容量大的類型轉換爲容量小的類型時,要加強制轉換符,且精度可能丟失

            如:float f = 1.2f;

            int ff = (int) f;
            System.out.println(ff);//輸出爲1,丟掉了小數部分

    3)short,char之間不會互相轉換(需要強制轉換),byte、short、char並且三者在計算時首先轉換爲int類型;

    4)實數常量默認爲double類型, 整數常量默認爲int類型;


3、switch中的參數類型

在jdk1.7 之switch 只能支持 byte、short、char、int或者其對應封裝以及 Enum 類型。關於枚舉的使用請看我這一篇博客(http://blog.csdn.net/shakespeare001/article/details/51151650)。
如:
        
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. enum EnumTest {  
  2.     LEFT,  
  3.     RIGHT  
  4. }  
  5. EnumTest e = EnumTest.LEFT;  
  6. switch (e) {  
  7. case LEFT:  
  8.     System.out.println("----left-----");  
  9.     break;  
  10. default:  
  11.     break;  
  12. }  

在jdk1.7 及1.7以後,switch也支持了String類型,如下:
        
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. String str = "abc";  
  2. switch (str) {  
  3. case "abc":  
  4.     System.out.println("-----abc-----");  
  5.     break;  
  6. case "aaa":  
  7.     System.out.println("-----aaa-----");  
  8.     break;  
  9. }  

4、equals與==的區別

(1)==是一個運算符,它比較的是值
    對於基本數據類型,直接比較其數據值是否相等。如果是不同的基本數據類型之間進行比較,則遵循基本數據類型間運算的轉換原則(見上面總結的第二條)。如下:
        
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. if(12 == 12.0){  
  2.     System.out.println("-----12 == 12.0-------");  
  3. }  

此時打印了-----12 == 12.0-------,因爲低一級的int類型的12自動轉換爲高一級的float類型

    對於引用類型,==比較的還是值,只不過此時比較的是兩個對象變量內存地址。所以,用==來比較對象,實際上是判斷這兩個對象是否是同一個new出來的對象,或者是否是一個對象賦值給另一個對象的情況。如:String s1 = new String("abc");
              String s2 = s1;//將s1對的內存地址賦給了s2,此時s1==s2返回true;

(2)equals
equals方法是屬於Object類的一個方法,其實現源碼如下:
   
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public boolean equals(Object obj) {  
  2.     return (this == obj);  
  3. }  
可以看到,其實equals方法裏面用的還是==運算符,所以對於那些沒有重寫過Object類的equals方法來說,==和equals方法是等價的!
然而,很多類都自己去重寫了equals方法,比如String類、所有基本數據類型的包裝類
String類的equals源碼如下:
    
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public boolean equals(Object anObject) {  
  2.     if (this == anObject) {  
  3.         return true;  
  4.     }  
  5.     if (anObject instanceof String) {  
  6.         String anotherString = (String) anObject;  
  7.         int n = value.length;  
  8.         if (n == anotherString.value.length) {  
  9.             char v1[] = value;  
  10.             char v2[] = anotherString.value;  
  11.             int i = 0;  
  12.             while (n-- != 0) {  
  13.                 if (v1[i] != v2[i])  
  14.                         return false;  
  15.                 i++;  
  16.             }  
  17.             return true;  
  18.         }  
  19.     }  
  20.     return false;  
  21. }  

首先判斷是否是同一個new出來的對象,即判斷內存地址是否相同;如果不同則判斷對象中的內容是否相同。
Integer類的equals方法如下:
   
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public boolean equals(Object obj) {  
  2.     if (obj instanceof Integer) {  
  3.         return value == ((Integer)obj).intValue();  
  4.     }  
  5.     return false;  
  6. }  

直接轉成判斷值是否相等了。
因此,對於String類和所有基本數據類型的包裝類來說,equals方法就是判斷其內容是否相等。對於其他類來說,要具體看其是否重寫了equals方法及具體業務實現。
另:對於基本數據類型來說,使用equals方法,需要用該基本類型對應的包裝類,因爲equals是針對對象來使用的!

5、Object有哪些公用方法

Object類中的所有方法如下:
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public boolean equals(Object obj) {//判斷是否同一個對象,具體見上一點總結  
  2.     return (this == obj);  
  3. }  
  4.   
  5. public String toString(){  
  6.     return getClass().getName() + "@" + Integer.toHexString(hashCode());  
  7. }  
  8.   
  9. //返回該對象的哈希碼值,重寫了equals方法一般都要重寫hashCode方法  
  10. public native int hashCode();  
  11.   
  12. /** 
  13. *wait方法就是使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait()方法一直等待,直到獲得鎖或者被中斷。wait(long timeout)設定一個超時間隔,如果在規定時間內沒有獲得鎖就返回。 
  14. *調用該方法後當前線程進入睡眠狀態,直到以下事件發生。 
  15. *(1)其他線程調用了該對象的notify方法。 
  16. *(2)其他線程調用了該對象的notifyAll方法。 
  17. *(3)其他線程調用了interrupt中斷該線程。 
  18. *(4)時間間隔到了。 
  19. *此時該線程就可以被調度了,如果是被中斷的話就拋出一個InterruptedException異常。 
  20.  
  21. *如:Person p = new Person(); 
  22. *p.wait()//使用Person p對象作爲對象鎖。 
  23. */  
  24. public final void wait() throws InterruptedException {...}  
  25.   
  26. public final native void wait(long timeout) throws InterruptedException;  
  27.   
  28. public final void wait(long timeout, int nanos) throws InterruptedException {...}  
  29.   
  30. //該方法喚醒在該對象上等待的某個線程。如p.notify();  
  31. public final native void notify();  
  32.   
  33. //該方法喚醒在該對象上等待的所有線程。  
  34. public final native void notifyAll();  
  35.   
  36. public final native Class<?> getClass();//獲得運行時類型  
  37.   
  38. //創建並返回此對象的一個副本。只有實現了Cloneable接口纔可以調用該方法,否則拋出CloneNotSupportedException異常。  
  39. protected native Object clone() throws CloneNotSupportedException;  
  40.   
  41. //用於釋放資源。當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。也可手動調用,自己實現一些資源的釋放。  
  42. protected void finalize() throws Throwable { }  

6、Java中的四種引用:強引用、軟引用、弱引用、虛引用

四種級別由高到低依次爲:強引用 > 軟引用 > 弱引用 > 虛引用
 參考文章:
  】
6.1 強引用(StrongReference)
    強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。如下的定義方式:
    String str = new String("abc");    //強引用,在堆中創建了String這個對象,通過棧中的變量str引用這個對象
    String str2 = str;    //強引用,str2也指向了堆中創建的String對象
這兩個引用都是強引用.只要存在對堆中String對象的引用,gc就不會回收該對象,如果通過下面代碼:str = null; str2 = null;顯示的設置引用str和str2爲null,則gc就會認爲堆中的String對象已經不存在其他引用了,此時該對象處於可回收的狀態,但是到底什麼時候回收該對象,取決於gc的算法。
    
6.2 軟引用(SoftReference)
    如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。如下使用代碼:    
    
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. String str= new String("abc");     //強引用     
  2.   Refenrence sr = new SoftReference(str);    //軟引用      
  3.   //引用時      
  4.   if(sr!=null){      
  5.       str= sr.get();      
  6.   }else{      
  7.       str= new String("abc");      
  8.       sr = new SoftReference(str);      
  9.   }  
    
可以看到不論是強引用、軟引用、弱引用或者虛引用都是針對某個對象來說的,當我們某個對象需要設置爲軟引用時,只需要給該對象套入到軟引用對象中即可,如上面的代碼SoftReference sr = new SoftReference(str);  

由於軟引用在內存不足時可以被回收,在內存充足時不會被回收,所以軟引用經常被用來作爲緩存使用。比如在Android中經常把Bitmap作爲軟引用來緩存圖片,如HashMap<String, SoftReference<Drawable>> imageCache;的方式。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。

6.3 弱引用(WeakReference)
    弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象
    弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

    對於軟引用或者弱引用來說,gc回收軟引用或弱引用對象的過程是一樣的,其執行過程如下:
     String str= new String("abc");     //強引用   
     Refenrence sr = new SoftReference(str);    //軟引用
    1 首先將軟引用或弱引用的referent設置爲null(即置str = null;),不再引用堆中的對象;

    2 將堆中的對象new String("abc");設置爲可結束的(finalizable)。

    3 當heap中的new String("abc")對象的finalize()方法被運行而且該對象佔用的內存被釋放, sr被添加到它的ReferenceQueue中。


    可以用如下代碼來說明過程:
       
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. String str = new String("abc");  
  2.        SoftReference<String> soft = new SoftReference<String>(str);    //軟引用  
  3.        str = null;  
  4.        System.out.println("before gc:" + soft.get());  
  5.        System.gc();  
  6.        System.out.println("after gc:" + soft.get());  
    輸出結果:before gc: abc
                     after gc: abc

    對於弱引用:
        
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. String str = new String("abc");  
  2.         WeakReference<String> soft = new WeakReference<String>(str);    //弱引用  
  3.         str = null;  
  4.         System.out.println("before gc:" + soft.get());  
  5.         System.gc();  
  6.         System.out.println("after gc:" + soft.get());  
    輸出結果:before gc :abc
                     after gc: null
    
    因此可以看出,軟引用和弱引用被gc回收的過程是一致的,但是最後到底會不會回收掉該對象,要分情況。對於軟引用來說,如果內存不足的情況下才會回收掉;對於弱引用來說,只要gc準備回收該弱引用對象,就會被立即釋放掉。

6.4 虛引用(PhantomReference
    "虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收虛引用主要用來跟蹤對象被垃圾回收的活動
    虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃 圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是 否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動。 建立虛引用之後通過get方法返回結果始終爲null。
    
    四種引用類型的聲明週期如下:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章