關於String面試題

Java中String類由於其特殊性(不變類),幾乎是筆試面試中的必考題,當然有些題目其實沒啥意思,不過關鍵是要通過題目掌握原理性的東西。下面六道題目,如果您全部做對了,且明白其所以然,那麼Java中的關於String的筆試面試題應該難不到你了。也許您覺得polaris說的有點過了,然而徹底明白這些題目,對理解String類還是很有好處的。

寫出下面各題的打印輸出的結果:

1

public static void main(String[] args) {   
        String a = "a1";   
        String b = "a" + 1;   
        System.out.println(a == b);   
    } 

2

public static void main(String[] args) {   
        String a = "ab";   
        String bb = "b";   
        String b = "a" + bb;   
        System.out.println(a == b);   
    }   

3

public static void main(String[] args) {   
        String a = "ab";   
        final String bb = "b";   
        String b = "a" + bb;   
        System.out.println(a == b);   
    }   

4

public static void main(String[] args) {   
        String a = "ab";   
        final String bb = getBB();   
        String b = "a" + bb;   
        System.out.println(a == b);   
    }   

    private static String getBB() {   
        return "b";   
    }  

5

private static String a = "ab";   

public static void main(String[] args) {   
    String s1 = "a";   
    String s2 = "b";   
    String s = s1 + s2;   
    System.out.println(s == a);   
    System.out.println(s.intern() == a);   
}  

6

private static String a = new String("ab");   

    public static void main(String[] args) {   
        String s1 = "a";   
        String s2 = "b";   
        String s = s1 + s2;   
        System.out.println(s == a);   
        System.out.println(s.intern() == a);   
        System.out.println(s.intern() == a.intern());   
    }  

(1)通過java源碼分析String

我們都知道String是不可變的(immutable),不變性的體現是:String類內部通過char數組來保存字符串,而這個char數組被聲明爲:final。那麼爲什麼要將String類聲明爲不可變呢?瞭解設計模式的你應該知道有一種模式叫做“不變模式”(immutable pattern),String類的設計就是使用了不變模式,有興趣的朋友可以看看“不變模式”講的具體是啥東東。

說完String的不可變性,需要說說String的“final性”(其實也還是不可變性決定的)。這也是有些面試官會問到的問題:我能不能寫一個類繼承自String?爲什麼?我們來看看String類的聲明:

public final class String implements java.io.Serializable, Comparable, CharSequence

對於final關鍵字的作用不用多解釋了。其實這也是“強不變模式”的一種要求(類本身聲明爲final或所有方法聲明爲final)。

(2)理解String對象的存儲機制

要深入理解String必須先了解Java內存機制——運行時數據區(Runtime Data Area)。《The JavaTM Virtual Machine Specification》中將運行時數據區分爲六部分(參看第三章): 1)The pc Register;2)Java Virtual Machine Stacks;3)Heap;4)Method Area;5)Runtime Constant Pool;6)Native Method Stacks;     以上數據區的具體描述可參考規範。需要注意的是,以上只是一個規範說明,並沒有規定虛擬機如何實現這些數據區。

在說明String對象存儲機制之前,我們需要先了解數據區的三個部分:Java 虛擬機棧(可以簡稱爲Java棧)、堆和運行時常量池(簡稱常量池)。對於Java棧和堆大家應該比較熟悉,這裏有一個關鍵點是常量池,下面就重點介紹一下與String相關的常量池。

首先大概描述一下什麼是常量池:

虛擬機必須爲每個被裝載的類型維護一個常量池。常量池就是該類型所用常量的一個有序集合,包括直接常量(string,integer和floating point常量)和對其他類型、字段和方法的符號引用。池中的數據項就像數組一樣是通過索引訪問的。因爲常量池存儲了相應類型所用到的所有類型、字段和方法的符號引用,所以它在Java程序的動態鏈接中起着核心的作用。

<1> String相關常量池

在《The JavaTM Virtual Machine Specification》第四章有一節是專門講解各種常量池的,其中有兩個常量池是關於String的。

1)The CONSTANT_String_info Structure

對於常量池的細節此文不做過多介紹,polaris以後可能會寫一序列關於Java虛擬機的文章。現在您可以查閱規範或在網上收集相關資料閱讀。規範上對該常量池結構的介紹是: The CONSTANT_String_info structure is used to represent constant objects of the type String. 在該常量池結構中引用了另一個常量池結構,如2)

2)The CONSTANT_Utf8_info Structure

規範上的描述是:The CONSTANT_Utf8_info structure is used to represent constant string values.

根據上面的介紹可以看出,字符串字面值會存儲在常量池中。下面來分析String對象的存儲機制。

<2> String對象的存儲

請看這樣兩個語句:

String x = “abc”; String y = new String(“abcd”);

現在來分析一下內存的分配情況。如圖:
這裏寫圖片描述

可以看出,x與y存在棧中,它們保存了相應對象的引用。第一條語句沒有在堆中分配內存,而是將“abc”保存在常量池中。對於第二條語句,同樣會在常量池中有一個“abcd”的字符串,當new時,會拷貝一份該字符串存放到堆中,於是y指向了堆中的那個“abcd”字符串。不知道polaris有沒有講明白。如果您明白了,那麼做前面那六道題就沒什麼問題了。

3、六道題答案詳解

1)true

要說明一點:當兩個字符串字面值連接時(相加),得到的新字符串依然是字符串字面值,保存在常量池中。

2)false

當字符串字面值與String類型變量連接時,得到的新字符串不再保存在常量池中,而是在堆中新建一個String對象來存放。很明顯常量池中要求的存放的是常量,有String類型變量當然不能存在常量池中了。

3)true

注意此題與上一題的區別,此處是字符串字面值與String類型常量連接,得到的新字符串依然保存在常量池中。

4)false

此題中第條語句:final String bb = getBB();其實與final String bb = new String(“b”);是一樣的。也就是說return “b”會在堆中創建一個String對象保存”b”,雖然bb被定義成了final。可見並非定義爲final的就保存在常量池中,很明顯此處bb常量引用的String對象保存在堆中,因爲getBB()得到的String已經保存在堆中了,final的String引用並不會改變String已經保存在堆中這個事實。

5)false,true

可能很多人對intern()這個函數不瞭解。JDK API文檔中對intern()方法的描述是:

返回字符串對象的規範化表示形式。
一個初始爲空的字符串池,它由類 String 私有地維護。

當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(用 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並返回此 String 對象的引用。

它遵循以下規則:對於任意兩個字符串 s 和 t,當且僅當 s.equals(t) 爲 true 時,s.intern() == t.intern() 才爲 true。

所有字面值字符串和字符串賦值常量表達式都使用 intern 方法進行操作。

上面字符串池即爲字符串常量池。明白該題結果的原因了吧。

6)false,false,true

第五題看明白後,第六題就沒什麼好講的了。

面試題:

Java代碼

String s = new String("abc");   
String s1 = "abc";   
String s2 = new String("abc");   

System.out.println(s == s1);   
System.out.println(s == s2);   
System.out.println(s1 == s2);  

請問以上程序執行結果是什麼?

第一句執行後內存中有兩個 對象,而不是一個。一個由new String(“abc”)中的”abc”在String Pool裏生成一個值爲”abc”的對象;第二個由new在堆裏產生一個值爲”abc”的對象,該對象完全是String Pool裏的”abc”的一個拷貝。變量s最後指向堆中產生的”abc”對象;

第二句執行時,s1先去String Pool找是否有值爲”abc”的對象,很顯然在上一步中java已經在String Pool裏生成一個”abc”對象了,所以s1直接指向String Pool中的這個”abc”;

第三句中又有一個new,在java中凡遇到new時,都會在堆裏產生一個新的對象。因此,該句執行後堆裏又多了一個”abc”對象,這與執行第一句後生成的”abc”是不同的兩個對象,s2最後指向這個新生成的對象。
因此,執行後面的打印語句的結果是三個false

問題2:
Java代碼

System.out.println(s == s.intern());   
System.out.println(s1 == s1.intern());   
System.out.println(s1.intern() == s2.intern());  

請問以上程序執行結果是什麼?

設 s爲String類型的變量,當執行s.intern()時,java先在String Pool裏找與字符串變量s相等(用equals()方法)的字符串,若有則將其引用返回;若沒有則在String Pool裏創建一個與s的值相等的字符串對象,並將其引用返回。從中我們可以總結出intern()方法無論如何都將返回String Pool裏的字符串對象的引用。
因此,以上程序執行的結果是false,true,true
PS:設s和t爲兩個字符串變量,若有s.equals(t),必有s.intern() == t.intern();
PS:”==”永遠比較的是兩邊對象的地址是否相等。

問題3:
Java代碼

String hello = "hello";   
String hel = "hel";   
String lo = "lo";   
System.out.println(hello == "hel" + "lo");   
System.out.println(hello == "hel" + lo);  

請問以上程序執行結果是什麼?

前三句在String Pool裏分別產生“hello”、“hel”、“lo”三個常量字符串對象
當做第一個加法連接時,+號兩邊都是常量字符串,java就會將兩者拼起來後到String Pool裏找與之相等(用equals)的字符串,若存在則將其地址返回;不存在則在String Pool裏新建一個常量對象,其值等於拼接後的字符串,並將其地址返回。

第二個+號兩邊有一個是變量,此時,java會在堆裏新建一個對象,其值是兩字符串拼接後的值,此時返回的地址是堆中新對象的地址。所以,第一句做+連接後返回String Pool中“hello”的地址,顯然與變量hello的地址相等;第二句返回的是堆中地址,顯然與變量hello的地址不等;

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