【筆試面試】第二波

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

1.1、會報錯的刪除方式:

(1)在Iterator遍歷時使用list刪除

Iterator<String> it = list.iterator();  
       while(it.hasNext()){  
           String item = it.next();  
           list.remove(item);    //報錯!!!  
}  

(2)foreach遍歷方式中刪除

for(String s : list){  
          list.remove(s); //報錯!!!  
}  

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

1.2、不會報錯,但是有可能漏刪或不能完全的刪除方式:

(1)漏刪的情況(通過索引下標的方式)

List<Integer> list = new ArrayList<Integer>();  
list.add(1);  
list.add(2);  
list.add(2);  
list.add(3);  
list.add(4);  
System.out.println("----------list大小1:--"+list.size());  
for (int i = 0; i < list.size(); i++) {  
    if (2 == list.get(i)) {  
        list.remove(i);      
    }  
    System.out.println(list.get(i));  
}  
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)不能完全刪除的情況

List<Integer> list = new ArrayList<Integer>();  
list.add(1);  
list.add(2);  
list.add(2);  
list.add(3);  
list.add(4);  
System.out.println("----------list大小1:--"+list.size());  
for (int i = 0; i < list.size(); i++) {  
    list.remove(i);  
}  
System.out.println("最後輸出=" + list.toString());  

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

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

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

還是使用Iterator遍歷,但是不用list來remove。如下代碼:

List<Integer> list = new ArrayList<Integer>();  
list.add(1);  
list.add(2);  
list.add(2);  
list.add(3);  
list.add(4);  
System.out.println("----------list大小1:--"+list.size());  
Iterator<Integer> it = list.iterator();  
while(it.hasNext()){  
    Integer item = it.next();  
    if (2 == item) {  
        it.remove();  
    }  
    System.out.println(item);  
}  
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 類型。
如:

enum EnumTest {  
    LEFT,  
    RIGHT  
}  
EnumTest e = EnumTest.LEFT;  
switch (e) {  
case LEFT:  
    System.out.println("----left-----");  
    break;  
default:  
    break;  
}  

在jdk1.7 及1.7以後,switch也支持了String類型,如下:

String str = "abc";  
switch (str) {  
case "abc":  
    System.out.println("-----abc-----");  
    break;  
case "aaa":  
    System.out.println("-----aaa-----");  
    break;  
}  

4、equals與==的區別

(1)==是一個運算符,它比較的是值
  對於基本數據類型,直接比較其數據值是否相等。如果是不同的基本數據類型之間進行比較,則遵循基本數據類型間運算的轉換原則(見上面總結的第二條)。如下:

if(12 == 12.0){  
    System.out.println("-----12 == 12.0-------");  
}  

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

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

(2)equals
equals方法是屬於Object類的一個方法,其實現源碼如下:

public boolean equals(Object obj) {  
    return (this == obj);  
}  

可以看到,其實equals方法裏面用的還是==運算符,所以對於那些沒有重寫過Object類的equals方法來說,==和equals方法是等價的!
然而,很多類都自己去重寫了equals方法,比如String類、所有基本數據類型的包裝類等
String類的equals源碼如下:

public boolean equals(Object anObject) {  
    if (this == anObject) {  
        return true;  
    }  
    if (anObject instanceof String) {  
        String anotherString = (String) anObject;  
        int n = value.length;  
        if (n == anotherString.value.length) {  
            char v1[] = value;  
            char v2[] = anotherString.value;  
            int i = 0;  
            while (n-- != 0) {  
                if (v1[i] != v2[i])  
                        return false;  
                i++;  
            }  
            return true;  
        }  
    }  
    return false;  
}  

首先判斷是否是同一個new出來的對象,即判斷內存地址是否相同;如果不同則判斷對象中的內容是否相同。
Integer類的equals方法如下:

public boolean equals(Object obj) {  
    if (obj instanceof Integer) {  
        return value == ((Integer)obj).intValue();  
    }  
    return false;  
}  

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

5、Object有哪些公用方法

Object類中的所有方法如下:

public boolean equals(Object obj) {//判斷是否同一個對象,具體見上一點總結  
    return (this == obj);  
}  

public String toString(){  
    return getClass().getName() + "@" + Integer.toHexString(hashCode());  
}  

//返回該對象的哈希碼值,重寫了equals方法一般都要重寫hashCode方法  
public native int hashCode();  

/** 
*wait方法就是使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait()方法一直等待,直到獲得鎖或者被中斷。wait(long timeout)設定一個超時間隔,如果在規定時間內沒有獲得鎖就返回。 
*調用該方法後當前線程進入睡眠狀態,直到以下事件發生。 
*(1)其他線程調用了該對象的notify方法。 
*(2)其他線程調用了該對象的notifyAll方法。 
*(3)其他線程調用了interrupt中斷該線程。 
*(4)時間間隔到了。 
*此時該線程就可以被調度了,如果是被中斷的話就拋出一個InterruptedException異常。 

*如:Person p = new Person(); 
*p.wait()//使用Person p對象作爲對象鎖。 
*/  
public final void wait() throws InterruptedException {...}  

public final native void wait(long timeout) throws InterruptedException;  

public final void wait(long timeout, int nanos) throws InterruptedException {...}  

//該方法喚醒在該對象上等待的某個線程。如p.notify();  
public final native void notify();  

//該方法喚醒在該對象上等待的所有線程。  
public final native void notifyAll();  

public final native Class<?> getClass();//獲得運行時類型  

//創建並返回此對象的一個副本。只有實現了Cloneable接口纔可以調用該方法,否則拋出CloneNotSupportedException異常。  
protected native Object clone() throws CloneNotSupportedException;  

//用於釋放資源。當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。也可手動調用,自己實現一些資源的釋放。  
protected void finalize() throws Throwable { }  

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

四種級別由高到低依次爲:強引用 > 軟引用 > 弱引用 > 虛引用
【 參考文章:
http://blog.csdn.net/ocean181/article/details/7232759
http://my.oschina.net/ydsakyclguozi/blog/404389
http://blog.csdn.net/hubenshan/article/details/7735858/#comments

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)

如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。如下使用代碼:    
String str= new String("abc");     //強引用     
  Refenrence sr = new SoftReference(str);    //軟引用      
  //引用時      
  if(sr!=null){      
      str= sr.get();      
  }else{      
      str= new String("abc");      
      sr = new SoftReference(str);      
  }  

可以看到不論是強引用、軟引用、弱引用或者虛引用都是針對某個對象來說的,當我們某個對象需要設置爲軟引用時,只需要給該對象套入到軟引用對象中即可,如上面的代碼

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);    //軟引用
  • 首先將軟引用或弱引用的referent設置爲null(即置str = null;),不再引用堆中的對象;
  • 將堆中的對象new String(“abc”);設置爲可結束的(finalizable)。
  • 當heap中的new String(“abc”)對象的finalize()方法被運行而且該對象佔用的內存被釋放, sr被添加到它的ReferenceQueue中。

    可以用如下代碼來說明過程:

String str = new String("abc");  
       SoftReference<String> soft = new SoftReference<String>(str);    //軟引用  
       str = null;  
       System.out.println("before gc:" + soft.get());  
       System.gc();  
       System.out.println("after gc:" + soft.get()); 

輸出結果:before gc: abc
     after gc: abc

對於弱引用:

String str = new String("abc");  
        WeakReference<String> soft = new WeakReference<String>(str);    //弱引用  
        str = null;  
        System.out.println("before gc:" + soft.get());  
        System.gc();  
        System.out.println("after gc:" + soft.get());

輸出結果:before gc :abc
     after gc: null
  因此可以看出,軟引用和弱引用被gc回收的過程是一致的,但是最後到底會不會回收掉該對象,要分情況。對於軟引用來說,如果內存不足的情況下才會回收掉;對於弱引用來說,只要gc準備回收該弱引用對象,就會被立即釋放掉。

6.4 虛引用(PhantomReference)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章