重新認識String

Java常用類——String

1、什麼是String

在Java中String類是一個使用非常頻繁的類,它不是java八個基本數據類型的其中之一,而是Java提供的在java.lang包下的用於創建和操作字符串的類。本文章通過蒐集的幾個常見面試題來學習String類的簡單使用。

2、String的特點

  • 使用final修飾:不可被繼承,並且內部一些方法也被final修飾。
public final class String
  • **不可變性:**String的不可變性主要有兩方面決定,首先是String類使用fianl來修飾,決定了String不能被繼承,另外一個原因是因爲String用於存儲字符串值得char行數組value[]也是使用final修飾的。
private final char value[];
  • **常量池優化:**常量池的出現是爲了解決對象快速創建問題。當一個String對象創建之後,會在字符串常量池中進行緩存,等到下次創建相同對象的時候將直接返回已緩存對象的引用,從而優化對象的創建過程。

3、String對象的創建方式

  1. 使用常量賦值的形式實例化對象

    String str = "java";
    
  2. 使用構造方法的形式實例化對象

    String str = new String("java");
    

這兩種實例化方式到底有什麼區別呢?我們先從存放的位置來分析。

兩種實例化方式都會在棧中創建變量str。

第一種方式實例化對象,虛擬機首先會在字符串常量池中通過String的equals方法來查找是否存在“java”常量,如果存在,虛擬機會將該常量的引用地址直接複製給str變量,如果不存在,會將字符串常量“java”存放在字符串常量池中,並將其引用地址複製給str變量。

第二種方式創建的對象會在堆中開闢內存空間,這裏需要注意的是,jvm並不是將字符串變量存放到了堆內存中,它只是在堆中開闢了一個內存塊,用於存放指向內存本身的引用地址。它的字符串常量“java”也會存放到字符串常量池中,所以使用第二種方式創建對象的時候具體流程如下:

虛擬機首先會在字符串常量池中通過String的equals方法來查找是否存在“java”常量,無論常量池中是否存在“java”字符串虛擬機都會在堆空間開闢內存空間存放新對象,如果存在就直接使用,如果不存在,會將字符串常量“java”存放在字符串常量池中,並將其引用地址複製給堆中新開闢的地址。

在這裏插入圖片描述

總結:

  1. 常量賦值的形式實例化,字符常量內容存於常量池,變量存於棧中,直接指向常量池。
  2. 構造方法實例化,會先在堆中創建實例對象,引用對象存於棧中,然後再去常量 區尋找需要的字符常量,如果找到了,直接使用,沒找到則開闢新的空間並存儲內容。

4、“==”與equals的區別

在解釋上述結果之前需要先明確一個概念,那就是“==”符號到底比較的是什麼?這個分爲兩種情況

  • 基本數據類型

對於基本數據類型來講,"=="是判斷左右兩邊的值是否相等

  • 引用類型

對於引用類型的變量,"=="是判斷左右兩邊所指向的地址是否一致

//Object的equals方法
public boolean equals(Object obj) {
    return (this == obj);
}

從Object的equals方法源碼可知,Object的equals方法其實也是直接比較對象的地址,和“==”沒有任何區別。那我們怎麼去比較兩個對象的內容而不是對象地址是否一致呢?現在就需要我們去重寫equals方法,下面以String的equals方法爲例。

//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;
}

首先我們先來看下String中equals方法源碼,從中我們可以看出調用String的equals方法對兩個變量進行比較時,首先是判斷兩個對象的地址是否相等,如果不相等還會對兩個string變量進行逐字符比較,如果相同那麼就返回true,否則返回false。當然對於我們自定義的類我們也可以根據需求自定義重寫他們的equals方法。

5、String常用方法

方法名稱 描述
public String (char[] value,int offset,int count) 將組部分字符數組變爲String類
public String (char[] value) 將組部分字符數組變爲String類
public char cahrAt(int index) 返回指定所索引對應的字符信息
public char[] toCharArrary() 將字符串以字符數組的形式返回
public boolean equals(String anObject) 進行相等判斷,區分大小寫
Public boolean equalsIgnoreCase(String anotherString) 進行相等判斷,不區分大小寫
public int compareTo(String anoterString) 判斷兩個字符串的大小(按照字符編碼比較)
=0:表示要比較的兩個字符內容相等
>0:表示大於的結果
<0:表示小於的結果
public boolean contains(String s) 判斷指定的內容是否內存
public int indexOf(String str) 由前向後查找指定字符串的位置,
如果查找到了則返回(第一個字母)位置的索引,
如果找不到返回-1
public int indexOf(String str,int fromIndex) 從指定位置由前向後查找指定字符串的位置,找不到返回-1
public boolean starsWith(String perfix) 判斷是否以指定的字符串開頭,如果是返回true,否則返回false
public boolean endsWith(String suffix) 判斷是否以指定的字符串結尾
public String repaceAll(String regex(正則),String replacement) 用新的內容替換掉全的舊的
public String substring(int beginIndex) 從指定索引截取到結尾
public String substring(int beginIndex,int endIndex) 截取部分字符串的數據
public String split(String regex) 按照指定的字符串進行全部拆分
public String concat(String str) 追加字符
public String toLowerCase() 轉小寫
public String toUpperCase() 轉大寫
public String trim() 去掉字符串左右兩邊的空格,中間空格保留
public intern() 數據入池
public boolean isEmpty() 判斷是否是空字符串(不是null,而是””長度是0)

6、面試題目解析

  1. 考察String的實例化方式

    String str1 = "java";
    String str2 = "java";
    String str3 = new String("java");
    String str4 = new String("java");
    System.out.println(str1==str2);
    System.out.println(str3==str4);
    System.out.println(str3.equals(str3));
    
    輸出結果:true false true
    

    str1與str2是通過賦值的方式進行的實例化,引用地址相同,所以結果爲true

    str3與str4是通過構造方法來進行的實例化,引用地址不同,所以結果爲false

    第三個結果爲true是因爲String的equals方法並不是直接比較兩者指向的地址,還會判斷內容是否相同。

  2. String不可變性

    String str3 = new String("java");
    String str4 = str3;
    System.out.println(str3==str4);
    str3 = "dart";
    System.out.println(str3==str4);
    
    輸出結果:true false
    

    因爲String對象創建後,它會將字符串值保存在final修飾的一個字符數組裏面,所以String是不可變的

    /** The value is used for character storage. */
    private final char value[];
    

    一旦字符串的值發生改變,那麼該對象就變成了一個新的對象,其地址也會發生改變,所以最終結果應該爲true和false。

  3. String字符串拼接

    String str1 = "java dart";
    String str2 = "java ";
    String str3 = "dart";
    String str4 = str2+str3;
    String str5 = "java "+"dart";
    System.out.println(str1==str4);
    System.out.println(str1==str5);
    
    輸出結果:false true
    

    拼接後得到的str4與str5字符串內容是一樣的,但是輸出結果爲什麼不同呢?

    str2與str3均爲變量,變量與變量進行拼接那麼str4就會在堆中開闢內存地址,而str1則是保存在常量池中,所以str4與str5內存地址一致。

    “java ”與“dart”均爲常量,拼接後的結果將保存在常量池中,所以str5與str1內存地址一致

  4. final修飾符的使用

    String str1 = "java dart";
    final String str2 = "java ";
    final String str3 = new String("java ");
    String str4 = str2+"dart";
    String str5 = str3+"dart";
    System.out.println(str1==str4);
    System.out.println(str1==str5);
    
    輸出結果:true false
    

    這個結果似乎有點意外,爲什麼3中str1==str4爲false,這裏就變成了true?因爲這裏的str2使用final修飾,如果在編譯期都可以知道確切值(定義變量的時候就初始化),那麼在編譯器會將其當做常量使用,所有用到該變量的地方就相當於直接使用該常量,所以常量與常量拼接還將保存在常量池中,而str3在編譯期並不知道確切值,所以輸出結果爲false

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