一:==和equals區別
在初學Java時,可能會經常碰到下面的代碼:
1 String str1 = new String("hello"); 2 String str2 = new String("hello"); 3 4 System.out.println(str1==str2); 5 System.out.println(str1.equals(str2));
爲什麼第4行和第5行的輸出結果不一樣?==和equals方法之間的區別是什麼?如果在初學Java的時候這個問題不弄清楚,就會導致自己在以後編寫代碼時出現一些低級的錯誤。今天就來一起了解一下==和equals方法的區別之處。
一.關係操作符“==”到底比較的是什麼?
下面這個句話是摘自《Java編程思想》一書中的原話:
“關係操作符生成的是一個boolean結果,它們計算的是操作數的值之間的關係”。
這句話看似簡單,理解起來還是需要細細體會的。說的簡單點,==就是用來比較值是否相等。下面先看幾個例子:
public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub int n=3; int m=3; System.out.println(n==m); String str = new String("hello"); String str1 = new String("hello"); String str2 = new String("hello"); System.out.println(str1==str2); str1 = str; str2 = str; System.out.println(str1==str2); } }
輸出結果爲 true false true
n==m結果爲true,這個很容易理解,變量n和變量m存儲的值都爲3,肯定是相等的。而爲什麼str1和str2兩次比較的結果不同?要理解這個其實只需要理解基本數據類型變量和非基本數據類型變量的區別。
在Java中游8種基本數據類型:
浮點型:float(4 byte), double(8 byte)
整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
字符型: char(2 byte)
布爾型: boolean(JVM規範沒有明確規定其所佔的空間大小,僅規定其只能夠取字面值"true"和"false")
對於這8種基本數據類型的變量,變量直接存儲的是“值”,因此在用關係操作符==來進行比較時,比較的就是 “值” 本身。要注意浮點型和整型都是有符號類型的,而char是無符號類型的(char類型取值範圍爲0~2^16-1).
也就是說比如:
int n=3;
int m=3;
變量n和變量m都是直接存儲的"3"這個數值,所以用==比較的時候結果是true。
而對於非基本數據類型的變量,在一些書籍中稱作爲 引用類型的變量。比如上面的str1就是引用類型的變量,引用類型的變量存儲的並不是 “值”本身,而是於其關聯的對象在內存中的地址。比如下面這行代碼:
String str1;
這句話聲明瞭一個引用類型的變量,此時它並沒有和任何對象關聯。
而 通過new String("hello")來產生一個對象(也稱作爲類String的一個實例),並將這個對象和str1進行綁定:
str1= new String("hello");
那麼str1指向了一個對象(很多地方也把str1稱作爲對象的引用),此時變量str1中存儲的是它指向的對象在內存中的存儲地址,並不是“值”本身,也就是說並不是直接存儲的字符串"hello"。這裏面的引用和C/C++中的指針很類似。
因此在用==對str1和str2進行第一次比較時,得到的結果是false。因此它們分別指向的是不同的對象,也就是說它們實際存儲的內存地址不同。
而在第二次比較時,都讓str1和str2指向了str指向的對象,那麼得到的結果毫無疑問是true。
二.equals比較的又是什麼?
equals方法是基類Object中的方法,因此對於所有的繼承於Object的類都會有該方法。爲了更直觀地理解equals方法的作用,直接看Object類中equals方法的實現。
該類的源碼路徑爲:C:\Program Files\Java\jdk1.6.0_14的src.zip 的java.lang路徑下的Object.java(視個人jdk安裝路徑而定)。
下面是Object類中equals方法的實現:
很顯然,在Object類中,equals方法是用來比較兩個對象的引用是否相等,即是否指向同一個對象。
但是有些朋友又會有疑問了,爲什麼下面一段代碼的輸出結果是true?
public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub String str1 = new String("hello"); String str2 = new String("hello"); System.out.println(str1.equals(str2)); } }
要知道究竟,可以看一下String類的equals方法的具體實現,同樣在該路徑下,String.java爲String類的實現。
下面是String類中equals方法的具體實現:
可以看出,String類對equals方法進行了重寫,用來比較指向的字符串對象所存儲的字符串是否相等。
其他的一些類諸如Double,Date,Integer等,都對equals方法進行了重寫用來比較指向的對象所存儲的內容是否相等。
總結來說:
1)對於==,如果作用於基本數據類型的變量,則直接比較其存儲的 “值”是否相等;
如果作用於引用類型的變量,則比較的是所指向的對象的地址
2)對於equals方法,注意:equals方法不能作用於基本數據類型的變量
如果沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址;
諸如String、Date等類對equals方法進行了重寫的話,比較的是所指向的對象的內容。
二:try catch finally return 執行順序
1 public static int NoException(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 return --i;
6 }
7 catch(Exception e){
8 --i;
9 System.out.println("i in catch - form try block is:"+i);
10 return --i;
11 }
12 finally{
13 System.out.println("i in finally - from try or catch block is:"+i);
14 return --i;
15 }
16 }
1 public static void main(String[] args) { 2 System.out.println("=============NoException=================="); 3 System.out.println(NoException()); 4 System.out.println("==============================="); 5 }
1 =============NoException================== 2 i in try block is:10 3 i in finally - from try or catch block is:9 4 8 5 ===============================
1 public static int NoException1(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 return --i;
6 }
7 catch(Exception e){
8 --i;
9 System.out.println("i in catch - form try block is:"+i);
10 return --i;
11 }
12 finally{
13 System.out.println("i in finally - from try or catch block is:"+i);
14 --i;
15 System.out.println("i in finally block is:"+i);
16 //return --i;
17 }
18 }
1 =============NoException1================== 2 i in try block is:10 3 i in finally - from try or catch block is:9 4 i in finally block is:8 5 9 6 ===============================
1 public static int WithException(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 i = i/0;
6 return --i;
7 }
8 catch(Exception e){
9 System.out.println("i in catch - form try block is:"+i);
10 --i;
11 System.out.println("i in catch block is:"+i);
12 return --i;
13 }
14 finally{
15 System.out.println("i in finally - from try or catch block is--"+i);
16 --i;
17 System.out.println("i in finally block is--"+i);
18 return --i;
19 }
20 }
1 =============WithException================== 2 i in try block is:10 3 i in catch - form try block is:10 4 i in catch block is:9 5 i in finally - from try or catch block is--8 6 i in finally block is--7 7 6 8 ===============================
1 public static int WithException1(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 i=i/0;
6 return --i;
7 }catch(Exception e){
8 System.out.println("i in catch - form try block is:"+i);
9 return --i;
10 }finally{
11
12 System.out.println("i in finally - from try or catch block is:"+i);
13 --i;
14 System.out.println("i in finally block is:"+i);
15 //return i;
16 }
17 }
1 =============WithException1================== 2 i in try block is:10 3 i in catch - form try block is:10 4 i in finally - from try or catch block is:9 5 i in finally block is:8 6 9 7 ===============================
1 public static int WithException2(){ 2 int i=10; 3 try{ 4 System.out.println("i in try block is:"+i); 5 i=i/0; 6 return --i; 7 } 8 catch(Exception e){ 9 System.out.println("i in catch - form try block is:"+i); 10 int j = i/0; 11 return --i; 12 } 13 finally{ 14 15 System.out.println("i in finally - from try or catch block is:"+i); 16 --i; 17 --i; 18 System.out.println("i in finally block is:"+i); 19 return --i; 20 }
1 =============WithException2================== 2 i in try block is:10 3 i in catch - form try block is:10 4 i in finally - from try or catch block is:10 5 i in finally block is:8 6 7 7 ===============================
1 public static int WithException3(){ 2 int i=10; 3 try{ 4 System.out.println("i in try block is:"+i); 5 i=i/0; 6 //return --i; 7 } 8 catch(Exception e){ 9 System.out.println("i in catch - form try block is:"+i); 10 //int j = i/0; 11 //return --i; 12 } 13 finally{ 14 15 System.out.println("i in finally - from try or catch block is:"+i); 16 --i; 17 --i; 18 System.out.println("i in finally block is:"+i); 19 //return --i; 20 } 21 return --i; 22 }
1 =============WithException3================== 2 i in try block is:10 3 i in catch - form try block is:10 4 i in finally - from try or catch block is:10 5 i in finally block is:8 6 7 7 ===============================
Java基礎知識之字符串
一、理解內存
瞭解字符串之前我們得先理解什麼是內存?
通常我們所說的內存就是暫時存儲程序以及數據的地方,包括隨機存儲器(RAM)(掉電丟失),只讀存儲器(ROM)(掉電不丟失),以及高速緩存(CACHE)(速度最快)。只不過因爲RAM是其中最重要的存儲器。
我們來看RAM組成:
寄存器:速度最快的存儲場所,因爲寄存器位於處理器內部,所以在程序中我們無法控制。
棧(Stack) :存放基本類型的對象和引用,但是對象本身不存放在棧中,而是存放在堆中。
Java中存在8大基本類型,他們的變量值中存放的就是具體的數值,而其他的類型都叫做引用類型(對象也是引用類型,你只要記住除了基本類型,都是引用類型)他們的變量值中存放的是他們在堆中的引用(內存地址)。
堆(Heap):在堆上分配內存的過程稱作 內存動態分配過程。在java中堆用於存放由new創建的對象和數組。堆中分配的內存
靜態存儲區/方法區(Static Field):是指在固定的位置上存放應用程序運行時一直存在的數據,java在內存中專門劃分了一個靜態存儲區域來管理一些特殊的數據變量如靜態的數據變量。
常量池(Constant Pool):顧名思義專門存放常量的。常量池就是該類型所有用到地常量的一個有序集合包括直接常量(基本類型,String)和對其他類型、字段和方法的符號引用。,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後存放到常量池中。
Java語言並不要求常量一定只能在編譯期產生,運行期間也可能產生新的常量,這些常量被放在運行時常量池中。這裏所說的常量包括:基本類型包裝類(包裝類不管理浮點型(浮點型在堆裏面,因爲Double,Float 是new生成的),整形只會管理-128到127(範圍之外也是new出來的))和String(也可以通過String.intern()方法可以強制將String放入常量池)。
總結:
定義一個局部變量的時候,java虛擬機就會在棧中爲其分配內存空間,局部變量的基本數據類型和引用存儲於棧中,引用的對象實體存儲於堆中。因爲它們屬於方法中的變量,生命週期隨方法而結束。
成員變量全部存儲與堆中(包括基本數據類型,引用和引用的對象實體),因爲它們屬於類,類對象終究是要被new出來使用的。當堆中對象的作用域結束的時候,這部分內存也不會立刻被回收,而是等待系統GC進行回收。
二、字符串常量池
我們知道字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字符串我們使用的非常多。JVM爲了提高性能和減少內存的開銷,在實例化字符串的時候進行了一些優化:使用字符串常量池。每當我們創建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經存在常量池中,那麼就直接返回常量池中的實例引用。如果字符串不存在常量池中,就會實例化該字符串並且將其放到常量池中。
優缺點
字符串常量池的好處就是減少相同內容字符串的創建,節省內存空間。
如果硬要說弊端的話,就是犧牲了CPU計算時間來換空間。CPU計算時間主要用於在字符串常量池中查找是否有內容相同對象的引用。不過其內部實現爲HashTable,所以計算成本較低。
創建字符串的兩種方式:
字面量形式:如:String s = “hello”;
JVM檢測這個字面量,如果JVM通過字符串常量池查找不到內容爲hello的字符串對象存在,那麼會創建這個字符串對象,然後將剛創建的對象的引用放入到字符串常量池中,並且將引用返回給變量s。如果發現內容爲“hello”的字符串存在字符串常量池中,那麼直接將已經存在的字符串引用返回給變量s。
使用new創建:如:String s = new String(“hello”);
new創建字符串時首先查看池中是否有相同值的字符串,如果有,則拷貝一份到堆中,然後返回堆中的地址;如果池中沒有,則在堆中創建一份,然後返回堆中的地址(注意,此時不需要從堆中複製到池中,否則導致浪費池的空間)
四、字符串實戰
實例1:
String str = "ABC"; String str1 = new String("ABC"); System.out.println(str == str1); //false System.out.println(str.equals(str1)); //true
分析:第一句:創建一個常量,放於字符串常量池中。
第二句:創建一個對象,將字符串常量池中的”ABC”賦值到堆中。
第三句:兩個對象不是同一個對象,所以輸出false。
第四句:因String的equals方法重寫過,兩個對象的內容相等,所以true。
實例2:
String str1 = "123"; System.out.println("123" == str1.substring(0)); //true System.out.println("23" == str1.substring(1)); //false
補充:substring源碼:
public String substring(int start) { if (start == 0) { return this; } if (start >= 0 && start <= count) { return fastSubstring(start, count - start); } throw indexAndLength(start); }
分析:第二句:由substring源碼可知,如果start==0,就返回當前對象,所以爲true
第三句:由substring源碼可知,如果start不等於0,則創建了新的對象,所以爲false
實例3:
String str3 = new String("ijk"); String str4 = str3.substring(0); System.out.println(str3 == str4); //true System.out.println((new String("ijk") == str4)); //false
分析:第三句:兩個相同的對象,輸出true
第四句:新創建了一個對象,兩個不同的對象,輸出false
實例4
String str5 = "NPM"; String str6 = "npm".toUpperCase(); System.out.println(str5 == str6); //false System.out.println(str5.equals(str6)); //true
補充:toUpperCase源碼:
public String toUpperCase() { return CaseMapper.toUpperCase(Locale.getDefault(), this, count); } public static String toUpperCase(Locale locale, String s, int count) { String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { return ICU.toUpperCase(s, locale); } if (languageCode.equals("el")) { return EL_UPPER.get().transliterate(s); } ....... ....... ....... ....... if (output == null) { if (newString != null) { return newString; } else { return s; } } return output.length == i || output.length - i < 8 ? new String(0, i, output) : new String(output, 0, i);
注意1:toUpperCase()和toLowerCase()只對英文字母有效,對除了A~Z和a~z的其餘字符無任何效果
注意2:toUpperCase()和toLowerCase()都創建了新的對象。
分析:第三句:兩個不同的對象,輸出false
第四句:雖然對象不同但是內容相同,輸出true
實例5:
String str9 = "a1"; String str10 = "a" + 1; System.out.println(str9 == str10); //true
分析:當兩個字符串常量連接時(相加)得到的新字符串依然是字符串常量且保存在常量池中。
實例6:
String str11 = "ab"; String str12 = "b"; String str13 = "a" + str12; System.out.println(str11 == str13); //false
分析:當字符串常量與 String 類型變量連接時得到的新字符串不再保存在常量池中,而是在堆中新建一個 String 對象來存放,很明顯常量池中要求的存放的是常量,有String類型變量當然不能存在常量池中了。str11 是字符串常量池中的對象,str13 是指向堆中的對象,不是同一個對象,所以輸出false。
實例7:
String str14 = "ab"; final String str15 = "b"; String str16 = "a" + str15; System.out.println(str14 == str16); //true
分析:字符串常量與 String 類型常量連接,得到的新字符串依然保存在常量池中。
實例8:
private static String getBB() { return "b"; } String str17 = "ab"; final String str18 = getBB(); String str19 = "a" + str18; System.out.println(str17 == str19); //false
分析:final String str18 = getBB()其實與final String str18 = new String(“b”)是一樣的,也就是說 return “b” 會在堆中創建一個 String 對象保存 ”b”,雖然 str18 被定義成了 final,所以可見看見,並非定義爲 final 的就保存在常量池中,很明顯此處 str18 常量引用的 String 對象保存在堆中,因爲 getBB() 得到的 String 已經保存在堆中了,final 的 String 引用並不會改變 String 已經保存在堆中這個事實。
實例9:
String str20 = "ab"; String str21 = "a"; String str22 = "b"; String str23 = str21 + str22; System.out.println(str23 == str20); //false System.out.println(str23.intern() == str20); //true System.out.println(str23 == str20.intern()); //false System.out.println(str23.intern() == str20.intern()); //true
分析:而對於調用 intern 方法如果字符串常量池中已經包含一個等於此 String 對象的字符串(用 equals(Object) 方法確定)則返回字符串常量池中的字符串,否則將此 String 對象添加到字符串常量池中,並返回此 String 對象的引用,所以str23.intern() == str20實質是常量比較返回 true,str23 == str20.intern()中 str23 就是上面說的堆中新對象,相當於一個新對象和一個常量比較,所以返回 false,str23.intern() == str20.intern() 就沒啥說的了,指定相等。
實例10:
String s1 = "abc"; StringBuffer s2 = new StringBuffer(s1); System.out.println(s1.equals(s2)); //false
補充:
equals源碼:
@Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof String) { String s = (String)other; int count = this.count; if (s.count != count) { return false; } // TODO: we want to avoid many boundchecks in the loop below // for long Strings until we have array equality intrinsic. // Bad benchmarks just push .equals without first getting a // hashCode hit (unlike real world use in a Hashtable). Filter // out these long strings here. When we get the array equality // intrinsic then remove this use of hashCode. if (hashCode() != s.hashCode()) { return false; } for (int i = 0; i < count; ++i) { if (charAt(i) != s.charAt(i)) { return false; } } return true; } else { return false; } }
分析:instanceof運算符用法:左面的操作元是一個對象實例,右面是一個類.當 左面的對象是右面的類創建的對象時,該運算符運算的結果是true,否則是false(一個類的實例包括本身的實例,以及所有直接或間接子類的實例 )
我們再看StringBuffer 並不繼承String類,所以到第五行判斷不成立,直接返回了false。也就是equal比較的是相同類型的引用變量纔行。
總結:要玩明白 Java String 對象的核心其實就是玩明白字符串的堆棧和常量池,虛擬機爲每個被裝載的類型維護一個常量池,常量池就是該類型所用常量的一個有序集合,包括直接常量(String、Integer 和 Floating Point常量)和對其他類型、字段和方法的符號引用,池中的數據項就像數組一樣是通過索引訪問的;由於常量池存儲了相應類型所用到的所有類型、字段和方法的符號引用,所以它在 Java 程序的動態鏈接中起着核心的作用。