java 字符串,表達式,等陷阱

1. 關於字符串的陷阱

java程序中創建對象的常規方式有如下4種:


通過new()調用構造器創建java對象

通過class對象的newInstance()方法調用構造器創建java對象

通過java的反序列化機制從IO流中恢復java對象

通過java對象提供的clone()方法複製一個新的java對象

除此之外,還允許直接量創建java對象,如 String str="apple";,也可以通過簡單的表達式,連接運算來創建java對象,String str2="apple"+"sweet";


對於java的字符直接量,JVM會使用一個字符串池來保存它們,當第一次使用某字符串直接量時,JVM會將它放入字符串池進行緩存。在一般情況下,字符串池中的字符串對象不會被垃圾機制回收,當程序再次需要該字符串時,無需創建一個新的字符串,而是直接讓引用變量直接指向字符串池中已有的字符串。

public class inittest {
public static void main(String[] args)
{
String str1="hello.java";
String str2="hello."+"java";
System.out.println(str1==str2);

}

輸出結果爲true, 因爲上式的表達式中,所有的運算都是字符串直接量,沒有調用方法,或者有變量參加其中,所有在編譯的時候就確定該字符串連接表達式的值,可以讓字符串變量指向字符串池中對應的字符串。但如果使用了變量,或者調用了方法,那就只能等程序運行的時候纔可以確定該字符串連接表達式的值,因此無法使用JVM的字符串池。如下程序會輸出false

public class inittest {
public static void main(String[] args)
{
String str1="hello.java";
String str2="hello.";
String str3="java";
String str4=str2+str3;
System.out.println(str1==str4);

}

但前面講過,可以用final修飾變量,讓它實現宏替換,這樣也可以實現在編譯時就確定值。修改如下即可

public class inittest {
public static void main(String[] args)
{
String str1="hello.java";
final String str2="hello.";
final String str3="java";
String str4=str2+str3;
System.out.println(str1==str4);

}

}

一個簡單的問題,String str="hello"+"java"+"happy"+"to learn";這個語句創建了幾個字符串對象?

其實只創建了一個字符串對象,因爲這些直接量,可以在編譯的時候就確定下來。在編譯的時候就確定str的值爲hellojavahappytolearn,然後將變量str指向字符串池中的字符串對象。

通過這裏可以看出,當程序中需要使用字符串、基本類型包裝類實例時,應該儘量使用字符串直接量、基本類型的直接量,避免通過new string()、new integer()形式來創建字符串。


String類是一個典型的不可變類,當一個String對象創建完成後,該String類裏包含的字符串被固定下來了,以後永遠不能改變。不是說變量str1不可變,而是它指向的字符串對象不可變。

public class inittest {
public static void main(String[] args)
{
String str1="hello";
System.out.println(System.identityHashCode(str1));
str1=str1+"java";
System.out.println(System.identityHashCode(str1));
str1=str1+"happy to learn";
System.out.println(System.identityHashCode(str1));

}
}

輸出結果爲

2065827189
1989444474
272890728

使用了System.identityHashCode(str1)方法來獲取str1的hashcode值,發現返回三個不同的返回值,這是因爲每次訪問str1時,它都指向了不同的string對象。


System類的identityHashCode()與是否重寫hashcode()函數無關。


上述程序執行時的內存分配如下;


圖中的hello,hello java字符串可能以後都不會被用到,但這個字符串不會被垃圾機制回收,因爲它將一直存在於字符串池中,這就是java內存泄露的原因之一


對於String類而言,它代表字符串序列不可改變的字符串,因此如果程序需要一個可變的字符串,那就考慮stringBuider和StringBuffer,兩者之間的區別是後者是線程安全的,但實際上在沒有多線程的環境下,應該考慮使用StringBuilder

public class inittest {
public static void main(String[] args)
{
StringBuilder str1=new StringBuilder("hello");
System.out.println(System.identityHashCode(str1));
str1.append("java");
System.out.println(System.identityHashCode(str1));
str1.append("happy to learn");
System.out.println(System.identityHashCode(str1));

}
}

輸出結果爲:

1573625905
1573625905
1573625905

System.identityHashCode(str1)返回的值都相同,表明str1一直指向同一個對象。String是不可變類,所以它總是線程安全的。


2.表達式類型的陷阱


java語言規定:當一個算術表達式中包含多個基本類型的值時,整個算術表達式的數據類型將發生自動提升。

所有byte型、short型和char型將被提升到int型,下面爲提升的等級,右邊的等級高於左邊類型的等級


short a=5;
a=a-2;

編譯出錯錯誤,因爲a爲short ,而2爲int型,最後賦值給short,會發生錯誤


byte b=40;
char c='a';
int i=80;
double d=.21;
double result=b+c+i+d;

不會出現錯誤,因爲最高級的爲double, 最後那個語句,表達式右邊的類型被提升到double,將計算結果賦值給double型的result變量,沒問題。


public class inittest {
public static void main(String[] args)
{
System.out.println("hello"+'a'+7);
System.out.println('a'+7+"hello");
}
}輸出結果爲:

helloa7
104hello

當基本類型值和String進行連接運算時,系統會將基本類型的值轉換成String類型,這樣纔可以讓連接運算正常進行。


複合賦值運算符的陷阱

short a=5;
a=a-2;如果改爲

short a=5;

a-=2;

則沒有錯誤!這是因爲,複合賦值運算會自動將它的結果值強制類型轉換爲其左側變量的類型。等價於E1=(E1的類型)(E1 OP E2)    如果結果類型與該變量的類型相同,那麼這個轉換不會造成任何影響,但是如果結果值的類型比這個變量的類型大,那麼複合賦值運算符將會執行一次強制類型轉換,這個強制類型轉換將有可能導致高位“截斷”。


由此可見,複合賦值運算簡單,方便但是有一定的危險,它潛在的隱式類型轉換可能在不知不覺中導致計算結果的高位被“截斷”


3.輸入法的陷阱

public class inittest {
public static void main(String[] args)
{
System.out.println("hello"+'a'+7);

}
}

一個簡單的程序,會出現錯誤,這個是因爲你在全角狀態下,輸入空格當成分隔符,所以出現錯誤。


4. 註釋的字符必須合法

大部分情況下,java編譯會直接忽略註釋部分,但又一種例外:java要求註釋部分的所有字符必須是合法的字符。

/***

d:\codes\unit3\...

***/

這段註釋就會出現錯誤,java允許使用\uXXXXX,但要求後4個必須是0-F的字符,這段註釋不符合java對unicode轉義字符的要求。



5.轉義字符的陷進


java提供3種方式來表示字符:

直接使用單引號起來的字符值‘a'

使用轉義字符,如'\n'

使用unicode轉義字符,如’\u6002'

但謹慎用第三種!


6.泛型可能引起的錯誤


原始類型變量的賦值

import java.util.List;
import java.util.ArrayList;


public class inittest {
public static void main(String[] args)
{
List list=new ArrayList();
list.add("hello");
list.add("java");
List<Integer> intList=list;//將集合賦給泛型
for (int i=0;i<intList.size();i++)
{
System.out.println(intList.get(i));
}



}
}

輸出結果爲:

hello
java

可以看出當程序把一個原始類型的變量賦給一個帶泛型信息的變量時,只要它們的類型保持兼容,例如將List賦給List<Integer>, 無論集合中實際包含什麼類型的元素,系統都不會有任何問題。

但是看下面的程序

import java.util.List;
import java.util.ArrayList;


public class inittest {
public static void main(String[] args)
{
List list=new ArrayList();
list.add("hello");
list.add("java");
List<Integer> intList=list;//將集合賦給泛型
for (int i=0;i<intList.size();i++)
{
Integer in=intList.get(i);
System.out.println(in);
}



}
}輸出結果:Exception in thread "main" java.lang.ClassCastException

所以:

當程序把一個原始類型的變量賦給一個帶泛型信息的變量時,總是可以通過編譯,只會顯示一些警告信息

當程序試圖訪問帶泛型聲明的集合的集合元素時,編譯器總是把集合元素當成泛型類型處理

當程序試圖訪問帶泛型聲明的集合的集合元素時,JVM會遍歷每個集合元素自動執行強制轉換,如果集合元素的實際類型與集合所帶的泛型信息不符,運行時將引發classcastexception


java 不支持泛型數組!


7.正則表達式的陷阱


matches( String regex);

String replaceAll(String regex,String replacement);

String replaceFirst(String regex,String replacement);

String[] split( String regex);// regex 正則表達式

replace();

下面是一個例子:注意使用轉義

public class inittest {
public static void main(String[] args)
{
String str1="hello.java.happy to learn";
String path1=str1.replace(".", "\\");
System.out.println(path1);
String path2=str1.replaceAll("\\.", "\\\\");


}
}

輸出結果爲:hello\java\happy to learn






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