1、String類可以被繼承嗎?
String類在聲明時使用final關鍵字修飾,被final關鍵字修飾的類無法被繼承。
接下來我們可以看一下String類的源代碼片段:
public final class String
implements java.io.Serializable, Comparable<String>,CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
● 為什么Java語言的開發者,把String類定義為final的呢?
因為只有當字符串是不可變的,字符串池才有可能實現。字符串池的實現可以在運行時節約很多heap空間,因為不同的字符串變量都指向池中的同一個字符串。但如果字符串是可變的,那么String interning將不能實現,因為這樣的話,如果變量改變了它的值,那么其它指向這個值的變量的值也會一起改變。如果字符串是可變的,那么會引起很嚴重的安全問題。譬如,數據庫的用戶名、密碼都是以字符串的形式傳入來獲得數據庫的連接,或者在socket編程中,主機名和端口都是以字符串的形式傳入。因為字符串是不可變的,所以它的值是不可改變的,否則黑客們可以鉆到空子,改變字符串指向的對象的值,造成安全漏洞。
因為字符串是不可變的,所以是多線程安全的,同一個字符串實例可以被多個線程共享。這樣便不用因為線程安全問題而使用同步。字符串自己便是線程安全的。
因為字符串是不可變的,所以在它創建的時候HashCode就被緩存了,不需要重新計算。這就使得字符串很適合作為Map中的鍵,字符串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字符串。
● final關鍵字除了修飾類之外,還有哪些用法呢?
final修飾的變量,一旦賦值,不可重新賦值;
final修飾的方法無法被覆蓋;
final修飾的實例變量,必須手動賦值,不能采用系統默認值;
final修飾的實例變量,一般和static聯用,用來聲明常量;
注意:final不能和abstract關鍵字聯合使用。
總之,final表示最終的、不可變的。
● &運算符是:邏輯與;&&運算符是:短路與。
● &和&&在程序中最終的運算結果是完全一致的,只不過&&存在短路現象,當&&運算符左邊的表達式結果為false的時候,右邊的表達式不執行,此時就發生了短路現象。如果是&運算符,那么不管左邊的表達式是true還是false,右邊表達式是一定會執行的。這就是他們倆的本質區別。
● 當然,&運算符還可以使用在二進制位運算上,例如按位與操作。
3、兩個對象值相同equals結果為true,但卻可有不同的 hashCode,這句話對不對?
不對,如果兩個對象x和y滿足x.equals(y) == true,它們的哈希值(hashCode)應當相同。Java 對于equals方法和hashCode方法是這樣規定的:
(1)如果兩個對象相同(equals方法返回true),那么它們的hashCode值一定要相同;
(2)如果兩個對象的 hashCode相同,它們并不一定相同。當然,你未必按照要求去做,但是如果你違背了上述原則就會發現在使用集合時,相同的對象可以出現在Set 集合中,同時增加新元素的效率會大大降低(對于使用哈希存儲的系統,如果哈希碼頻繁的沖突將會造成存取性能急劇下降)。
關于equals和hashCode方法,很多Java程序員都知道,但很多人也就是僅僅了解而已,在Joshua Bloch的大作《Effective Java》(《Effective Java》在很多公司,是Java程序員必看書籍,如果你還沒看過,那就趕緊去買一本吧)中是這樣介紹 equals 方法的:
首先equals方法必須滿足自反性(x.equals(x)必須返回true)、對稱性(x.equals(y)返回true 時,y.equals(x)也必須返回true)、傳遞性(x.equals(y)和y.equals(z)都返回true時,x.equals(z)也必須返回true)和一致性(當x和y引用的對象信息沒有被修改時,多次調用x.equals(y)應該得到同樣的返回值),而且對于任何非null值的引用x,x.equals(null)必須返回false。實現高質量的equals方法的訣竅包括:
使用==操作符檢查"參數是否為這個對象的引用";
使用 instanceof操作符檢查"參數是否為正確的類型";
對于類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;
編寫完equals方法后,問自己它是否滿足對稱性、傳遞性、一致性;
重寫equals時總是要重寫hashCode;
不要將equals方法參數中的Object對象替換為其他的類型,在重寫時不要忘掉@Override注解。
在最外層循環前加一個標記如outfor,然后用break outfor;可以跳出多重循環。例如以下代碼:
public class TestBreak {
public static void main(String[] args) {
outfor: for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
if (j == 5){
break outfor;
}
System.out.println("j = " + j);
}
}
}
}
運行結果如下所示:
j = 0
j = 1
j = 2
j = 3
j = 4
方法的重載和重寫都是實現多態的方式,區別在于前者實現的是編譯時的多態性,而后者實現的是運行時的多態性。
重載發生在一個類中,同名的方法如果有不同的參數列表(類型不同、個數不同、順序不同)則視為重載。
重寫發生在子類與父類之間,重寫要求子類重寫之后的方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求。
● 方法重載的規則:
方法名一致,參數列表中參數的順序,類型,個數不同。
重載與方法的返回值無關,存在于父類和子類,同類中。
可以拋出不同的異常,可以有不同修飾符。
● 方法重寫的規則:
參數列表、方法名、返回值類型必須完全一致;
構造方法不能被重寫;
聲明為 final 的方法不能被重寫;
聲明為 static 的方法不存在重寫(重寫和多態聯合才有意義);
訪問權限不能比父類更低;
重寫之后的方法不能拋出更寬泛的異常;
是值傳遞。Java 語言的方法調用只支持參數的值傳遞。當一個對象實例作為一個參數被傳遞到方法中時,參數的值就是對該對象的內存地址。這個值(內存地址)被傳遞后,同一個內存地址指向堆內存當中的同一個對象,所以通過哪個引用去操作這個對象,對象的屬性都是改變的。
我們來看以下的代碼:
public void testMethod(){
doSome();
}
public void doSome(){
}
public int doSome(){
return 1;
}
在Java語言中,調用一個方法,即使這個方法有返回值,我們也可以不接收這個返回值,例如以上兩個方法doSome(),在testMethod()中調用的時候,Java編譯器無法區分調用的具體是哪個方法。所以對于編譯器來說,doSome()方法不是重載而是重復了,編譯器報錯。所以區分這兩個方法不能依靠方法的返回值類型。
不同點:
● 抽象類中可以定義構造器,接口不能;
● 抽象類可以有抽象方法和具體方法,接口不能有具體方法;
● 接口中的成員全都是 public 的,抽象類中的成員可以使用private、public、protected、默認等修飾;
● 抽象類中可以定義成員變量,接口中只能是常量;
● 有抽象方法的類必須被聲明為抽象類,而抽象類未必要有抽象方法;
● 抽象類中可以包含靜態方法,接口中不能有靜態方法;
● 一個類只能繼承一個抽象類,一個類可以實現多個接口;
相同點:
● 不能夠實例化;
● 可以將抽象類和接口類型作為引用類型;
● 一個類如果繼承了某個抽象類或者實現了某個接口都需要對其中的抽象方法全部進行實現,否則該類仍然需要被聲明為抽象類;
9、char 型變量中能不能存儲一個中文漢字,為什么?
char 類型可以存儲一個中文漢字,因為Java中使用的編碼是Unicode(不選擇任何特定的編碼,直接使用字符在字符集中的編號,這是統一的唯一方法),一個char 類型占2個字節(16 比特),所以放一個中文是沒問題的。
補充:使用Unicode 意味著字符在JVM內部和外部有不同的表現形式,在JVM內部都是 Unicode,當這個字符被從JVM內部轉移到外部時(例如存入文件系統中),需要進行編碼轉換。所以 Java 中有字節流和字符流,以及在字符流和字節流之間進行轉換的轉換流,如 InputStreamReader和OutputStreamReader,這兩個類是字節流和字符流之間的適配器類,承擔了編碼轉換的任務。
都不能。
● 抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛盾的。
● 本地方法是由本地代碼(如 C++ 代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。
● synchronized 和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的。
equals和==最大的區別是一個是方法一個是運算符。
● ==:如果比較的對象是基本數據類型,則比較的是數值是否相等;如果比較的是引用數據類型,則比較的是對象的地址值是否相等。
● equals():用來比較方法兩個對象的內容是否相等。equals方法不能用于基本數據類型的變量,如果沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址。
不管創建多少個對象,靜態變量在內存中有且僅有一個;實例變量必須依存于某一實例,需要先創建對象然后通過對象才能訪問到它。靜態變量可以實現讓多個對象共享內存。
● break和continue 都是用來控制循環的語句。
● break 用于完全結束一個循環,跳出循環體執行循環后面的語句。
continue 用于跳過本次循環,繼續下次循環。
沒有。
因為 String被設計成不可變類,所以它的所有對象都是不可變對象。
在這段代碼中,s原先指向一個 String 對象,內容是 "Hello",然后我們對 s 進行了“+”操作,那么 s 所指向的那個對象是否發生了改變呢?
答案是沒有。這時s不指向原來那個對象了,而指向了另一個 String 對象,內容為"Hello world!",原來那個對象還存在于內存之中,只是 s 這個引用變量不再指向它了。
通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,或者說,不可預見的修改,那么使用 String 來代表字符串的話會引起很大的內存開銷。因為 String 對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個 String 對象來表示。這時,應該考慮使用 StringBuffer/StringBuilder類,它允許修改,而不是每個不同的字符串都要生成一個新的對象。并且,這兩種類的對象轉換十分容易。同時,我們還可以知道,如果要使用內容相同的字符串,不必每次都 new 一個 String。例如我們要在構造器中對一個名叫 s 的 String 引用變量進行初始化,把它設置為初始值,應當這樣做:
s = new String("動力節點,口口相傳的Java黃埔軍校");
而不是這樣做:
s = new String("動力節點,口口相傳的Java黃埔軍校");
后者每次都會調用構造器,生成新對象,性能低下且內存開銷大,并且沒有意義,因為 String 對象不可改變,所以對于內容相同的字符串,只要一個 String 對象來表示就可以了。也就說,多次調用上面的構造器創建多個對象,他們的 String 類型屬性 s 都指向同一個對象。
上面的結論還基于這樣一個事實:對于字符串常量,如果內容相同,Java 認為它們代表同一個 String 對象。而用關鍵字 new 調用構造器,總是會創建一個新的對象,無論內容是否相同。
至于為什么要把 String 類設計成不可變類,是它的用途決定的。其實不只String,很多 Java 標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向對象思想的體現。不可變類有一些優點,比如因為它的對象是只讀的,所以多線程并發訪問也不會有任何問題。當然也有一些缺點,比如每個不同的狀態都要一個對象來代表,可能會造成性能上的問題。所以 Java 標準類庫還提供了一個可變版本,即 StringBuffer。