<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          美團面試題:String s = new String("111")會創(chuàng)建幾個對象?

          共 13345字,需瀏覽 27分鐘

           ·

          2021-03-31 13:43

          點擊上方 java項目開發(fā) ,選擇 星標 公眾號

          重磅資訊,干貨,第一時間送達

          作者:維常

          blog.csdn.net/o9109003234/article/details/109523691

          String不可變嗎?

          public class App {
              public static void main(String[] args) {
                  String a = "111";
                  a = "222";
                  System.out.println(a);
              }
          }

          有的人會認為上面這段代碼應該輸出:111

          這樣才和上面的不變性吻合。

          哈哈哈,但是并不是這樣滴。

          222

          這不對呀,不是不變嗎?怎么變了呢?

          其實在JVM的運行中,會單獨給一塊地分給String。

          上面的:

          Stirng a="111"

          我們知道字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字符串我們使用的非常多。JVM為了提高性能和減少內(nèi)存的開銷,在實例化字符串的時候進行了一些優(yōu)化:

          使用字符串常量池。每當我們創(chuàng)建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經(jīng)存在常量池中,那么就直接返回常量池中的實例引用。如果字符串不存在常量池中,就會實例化該字符串并且將其放到常量池中。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串。

          這里先去JVM給常量池里找,找到了就不用創(chuàng)建對象了,直接把對象的引用地址賦給a。找不到會重新創(chuàng)建一個對象,然后把對象的引用地址賦給a。同理a="222";也是先找,找不到就重新創(chuàng)建一個對象,然后把對象的引用地址賦給a。

          搜索公縱號:MarkerHub,關注回復[ vue ]獲取前后端入門教程!

          大家有沒有發(fā)現(xiàn)我上面的描述中“引用地址”。比如說 Object obj = new Object();很多人喜歡成obj為對象,其實obj不是對象,他只是一個變量,然后這個變量里保存一個Object對象的引用地址罷了。

          引用類型聲明的變量是指該變量在內(nèi)存中實際存儲的是一個引用地址,實體在堆中。

          所以網(wǎng)上很多文章老喜歡這么說

          User user = new User()

          創(chuàng)建了一個user對象,老喜歡把user稱之為對象。這里不接受反駁。

          所以上面String a = “111”;表達的是變量a里保存了“111”這個對象的引用地址。變量是可以變的,不能變的是“111”。

          String 為什么是不可變的?

          簡單的來說,String 類中使用 final 關鍵字字符數(shù)組保存字符串。代碼如下:

          public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
              private final char value[];
              public String() {
                  this.value = "".value;
              }
              public String(String original) {
                  this.value = original.value;
                  this.hash = original.hash;
              }
              public String(char value[]) {
                  this.value = Arrays.copyOf(value, value.length);
              }
          }   

          從上面的這段源碼中可以看出三點:

          String 類是final修飾

          String存儲內(nèi)容使用的是char數(shù)組

          char數(shù)組是final修飾

          這里就得復習一下,final有啥用?

          當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變量可以根據(jù)需要設為final,但是要注意final類中的所有成員方法都會被隱式地指定為final方法。

          當final修飾的方法表示此方法已經(jīng)是“最后的、最終的”含義,亦即此方法不能被重寫(可以重載多個final修飾的方法)。此處需要注意的一點是:因為重寫的前提是子類可以從父類中繼承此方法,如果父類中final修飾的方法同時訪問控制權限為private,將會導致子類中不能直接繼承到此方法,因此,此時可以在子類中定義相同的方法名和參數(shù),此時不再產(chǎn)生重寫與final的矛盾,而是在子類中重新定義了新的方法。(注:類的private方法會隱式地被指定為final方法。)

          當final修飾一個基本數(shù)據(jù)類型時,表示該基本數(shù)據(jù)類型的值一旦在初始化后便不能發(fā)生變化。如果final修飾一個引用類型時,則在對其初始化之后便不能再讓其指向其他對象了,但該引用所指向的對象的內(nèi)容是可以發(fā)生變化的。本質(zhì)上是一回事,因為引用的值是一個地址,final要求值,即地址的值不發(fā)生變化。另外final修飾一個成員變量(屬性),必須要顯示初始化。這里有兩種初始化方式,(1.在申明的時候給其賦值,否則必須在其類的所有構造方法中都要為其賦值)

          比如:

          /**
           * Description: final修飾變量
           * @author : 田維常
           * 歡迎關注:java后端技術全棧
           */
          public class FinalDemo {
              private final String name;

              public FinalDemo(String name) {
                  this.name = name;
              }

              public FinalDemo() {
              }
          }

          這是會會報錯

          關于final就簡單說到這里

          下面來看一個使用String的 案例

          /**
           * Description:
           *
           * @author : 田維常
           * @date : 2020/11/3
           * 歡迎關注公眾號:java后端技術全棧
           */
          public class StringDemo {
              public static void main(String[] args) {
                  String name = "老田";
                  name.concat("!");
                  System.out.println(name);
                  System.out.println(name.concat("!"));
              }
          }

          輸出

          順道溜達溜達 String中幾個常用方法源碼

              public String concat(String str) {
                  int otherLen = str.length();
                  if (otherLen == 0) {
                      //啥都沒有,就直接把當前字符串給你
                      return this;
                  }
                  int len = value.length;
                  char buf[] = Arrays.copyOf(value, len + otherLen);
                  str.getChars(buf, len);
                  //看到了嗎?返回的居然是新的String對象
                  return new String(buf, true);
              }
              void getChars(char dst[], int dstBegin) {
                  System.arraycopy(value, 0, dst, dstBegin, value.length);
              }    
              public String replace(char oldChar, char newChar) {
                  //如果兩個是一樣的,那就必要替換了,所以返回this
                  if (oldChar != newChar) {
                      int len = value.length;
                      int i = -1;
                      //把當前的char數(shù)組復制給val,然后下面基于val來操作
                      char[] val = value; 

                      while (++i < len) {
                          if (val[i] == oldChar) {
                              break;
                          }
                      }
                      if (i < len) {
                          //創(chuàng)建一個新的char數(shù)組
                          char buf[] = new char[len];
                          for (int j = 0; j < i; j++) {
                              buf[j] = val[j];
                          }
                          while (i < len) {
                              char c = val[i];
                              buf[i] = (c == oldChar) ? newChar : c;
                              i++;
                          }
                          //創(chuàng)建一個新的String對象
                          return new String(buf, true);
                      }
                  }
                  return this;
              }

              public String substring(int beginIndex, int endIndex) {
                  if (beginIndex < 0) {
                      throw new StringIndexOutOfBoundsException(beginIndex);
                  }
                  if (endIndex > value.length) {
                      throw new StringIndexOutOfBoundsException(endIndex);
                  }
                  int subLen = endIndex - beginIndex;
                  if (subLen < 0) {
                      throw new StringIndexOutOfBoundsException(subLen);
                  }
                  //正常返回的都是新new出來的String對象
                  return ((beginIndex == 0) && (endIndex == value.length)) ? this
                          : new String(value, beginIndex, subLen);
              }

              public String trim() {
                  int len = value.length;
                  int st = 0;
                  char[] val = value;    /* avoid getfield opcode */

                  while ((st < len) && (val[st] <= ' ')) {
                      st++;
                  }
                  while ((st < len) && (val[len - 1] <= ' ')) {
                      len--;
                  }
                  //如果是該字符串中包含了空格,調(diào)用substring方法,否則就是啥都沒干原本返回
                  //就是如果字符串里有空格,那么還是新生一個String對象返回
                  return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
              }

          無論是concat、replace、substring還是trim方法的操作都不是在原有的字符串上進行的,而是重新生成了一個新的字符串對象。也就是說進行這些操作后,最原始的字符串并沒有被改變。

          得出兩個結(jié)論:

          String對象一旦被創(chuàng)建就是固定不變的了,對String對象的任何改變都不影響到原對象,相關的任何變化性的操作都會生成新的對象。

          String對象每次有變化性操作的時候,都會從新new一個String對象(這里指的是有變化的情況)。

          回到前面的例子

          //String a = "111";相當于
          char data [] ={'1','1','1'};
          Stirng a = new String(data);
          //a = "222";
          char data [] ={'2','2','2'};
          a = new String(data);

          這會變量a里保存的是"222"對應String對象的引用。

          繼續(xù)看下面的代碼

          public class App {
              public static void main(String[] args) {
                  String a = "111";
                  String a1 = "111";

                  String b = new String("111");

                  //對象地址是同一個
                  System.out.println(a==a1);
                  //對象內(nèi)容是一樣的
                  System.out.println(a.equals(a1));
                  //對象地址不一樣
                  System.out.println(a==b);
                  //對象內(nèi)容是一樣的
                  System.out.println(a.equals(b));
              }
          }

          輸出

          true
          true
          false
          true

          第一個輸出true,說明a和a1兩個變量保存的引用地址是同一個。

          第二個也輸出true,說明a和a1引用地址中內(nèi)容是一樣的。

          a和a1放在棧上,存放著對象的引用地址。

          new的對象是在堆中。

          常量其實是要看jdk版本的。

          所以String a = "111"; 在JVM申請內(nèi)存存放"111"對應的對象,并將對象保存起來。當String a1="1111";的時候,會先去JVM的那塊地里尋找是否存在"111",剛好前面保存過,所以找到,然后直接把對象的引用地址給了a1。所以此時的a和a1都保存著同一個引用地址。

          接觸java后都知道可以new一個對象。所以 String b = new String("111");就是創(chuàng)建一個對象然后把對象引用地址賦給變量b。但是這里有個特殊點,那就是(“111”),這里會先去JVM里的那塊地里找找,找到了直接存放引用地址。找不到創(chuàng)建一個對象然后把引用地址給String的有參構造方法里。

          所以第三個中輸出false,因為a和b所保存的對象引用是不一樣的。

          最后一個輸出true。那是因為兩個變量所保存的引用地址中的內(nèi)容都是“111”.

          答案:

          如果常量池中存在,則只需創(chuàng)建一個對象,否則需要創(chuàng)建兩個對象。

          --完--
          推薦閱讀:
          怎么接私貨?這個渠道你100%有用!請收藏!

          ,在看 
          瀏覽 22
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  激情乱伦网站 | 免费成年人在线 | 奇米影视成人社区 | 女人毛片| 亚洲美女啪啪 |