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類型;
如:
-
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());
-
}
-
-
-
public native int hashCode();
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 {...}
-
-
-
public final native void notify();
-
-
-
public final native void notifyAll();
-
-
public final native Class<?> getClass();
-
-
-
protected native Object clone() throws CloneNotSupportedException;
-
-
-
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的算法。
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。如下使用代碼:
-
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); //軟引用
1 首先將軟引用或弱引用的referent設置爲null(即置str = null;),不再引用堆中的對象;
2 將堆中的對象new String("abc");設置爲可結束的(finalizable)。
3 當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。
四種引用類型的聲明週期如下: