欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JAVA對象中使用?static?和?String?基礎探究

 更新時間:2022年09月27日 14:30:52   作者:????????????  
這篇文章主要介紹了JAVA對象中使用static和String基礎探究,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下

前言

跟同學在討論 JAVA 期末試題時,對于一些 static 和 String 在對象中的使用方法,若有所思,特此記錄一下,也祝沒有對象的友友可以自己 new 一個出來!

那我們先來看一看試卷里的原題;  

原題

主要就是兩個類 MyClass.java 和 TestMyClass.java,填代碼的部分就直接跳過了,然后就是輸出結果,看看你是否也能全部正確,

兩個類的具體代碼如下:

MyClass.java

public class MyClass {
    private int count;
    String info;
    public static String message = "Good";

    public MyClass increase() {
        count++;
        return this;
    }
    private MyClass() {
        this.count=0;
        this.info = "GoodLuck";
    }
    public int getCount() {
        return count;
    }
    public static MyClass getInstance() {
        return new MyClass();
    }
}

TestMyClass.java

public class TestMyClass {
    public static void main(String[] args) {
        MyClass mc1 = MyClass.getInstance();
        MyClass mc2 = MyClass.getInstance();
        mc1.message = "Great";
        mc2.message = "Excellent";
        MyClass.message = "Nice";

        System.out.println(mc1.message+":"+mc2.message+":"+MyClass.message);
        System.out.println(mc1.info == mc2.info);
        mc2.info = new String("GoodLuck");
        System.out.println(mc1.info == mc2.info);
        System.out.println(mc1.info.equals(mc2.info));
        System.out.println(mc1.increase().increase().getCount());
    }
}

運行結果:

Nice:Nice:Nice
true
false
true
2

如果你全部答對了,那么恭喜你,基礎很不錯喲;答錯的小伙伴也不要喪氣,接下來聽我娓娓道來,扎實基礎;  

static

工欲善其事必先利其器,在開始解析之前,我們先回顧一下一些關于 static 的知識;

簡介

static 表示 “全局” 或者 “靜態(tài)” 的意思,用來修飾成員變量和成員方法,也可以形成靜態(tài) static 代碼塊,但是 Java 語言中沒有全局變量的概念;

被 static 修飾的成員變量和成員方法獨立于該類的任何對象,也就是說,它不依賴類特定的實例,被類的所有實例共享;

只要這個類被加載,Java 虛擬機就能根據類名在運行時數據區(qū)的方法區(qū)內定找到他們,因此,static 對象可以在它的任何對象創(chuàng)建之前訪問,無需引用任何對象;

用 public 修飾的 static 成員變量和成員方法本質是全局變量和全局方法,當聲明它類的對象時,不生成 static 變量的副本,而是類的所有實例共享同一個 static 變量;

static 變量前可以有 private 修飾,表示這個變量可以在類的靜態(tài)代碼塊中,或者類的其他靜態(tài)成員方法中使用,但是不能在其他類中通過類名來直接引用,這一點很重要;

實際上你需要搞明白,private 是訪問權限限定,static 表示不要實例化就可以使用,這樣就容易理解多了,static 前面加上其它訪問權限關鍵字的效果也以此類推。

static 修飾的成員變量和成員方法習慣上稱為靜態(tài)變量和靜態(tài)方法,可以直接通過類名來訪問,訪問語法為:

類名.靜態(tài)方法名(參數列表…)

使用

回顧了 static 相關知識之后,我們來看一下題目中的使用吧;

// MyClass.java
public static String message = "Good";

// TestMyClass.java
MyClass mc1 = MyClass.getInstance();
MyClass mc2 = MyClass.getInstance();
mc1.message = "Great";
mc2.message = "Excellent";
MyClass.message = "Nice";

System.out.println(mc1.message+":"+mc2.message+":"+MyClass.message);

先是用 static 修飾了成員變量 message,然后通過下斷點調試可以獲知,兩個對象 mcl1 和 mcl2 被分配到了兩個不同的地址;

在往下調試時,發(fā)現(xiàn) mc1.message,mc2.messageMyClass.message 三個成員變量的值是一樣的,且都從 Great → Excellent → Nice,這就是剛才所回顧的,被 static 修飾的成員變量成了共享變量,被類的所有實例共享;

接下來我們再做個試驗驗證一下:

//修改前
private int count;

//修改后
private static int count;

可以發(fā)現(xiàn),我們只是對 mcl1 對象進行了操作,但是 mcl2 的成員變量 count 也跟著改變了,這就是因為在 MyClass 類中,成員變量 count 被 static 修飾,已經成了該類的共享變量了,但凡是該類的對象,都訪問的是同一個 count 變量;

當然也可以通過身份碼進行驗證:

System.out.println("mcl1: " + System.identityHashCode(mc1));
System.out.println("mcl2: " + System.identityHashCode(mc2));
System.out.println("mcl1_count: " + System.identityHashCode(mc1.getCount()));
System.out.println("mcl2_count: " + System.identityHashCode(mc2.getCount()));
mcl1: 940553268
mcl2: 1720435669
mcl1_count: 1020923989
mcl2_count: 1020923989

因此 System.out.println(mc1.message+":"+mc2.message+":"+MyClass.message); 輸出的是 Nice:Nice:Nice

接下來講一些關于 String 的小知識;  

String

關于 String 的話,這里用到啥聊啥,就不全面的進行了;

== 與 equals()

先來講講關于 String 的比較,一般常見的比較有兩種,即 == 和 equals();

其中,== 比較的是兩個字符串的地址是否為相等(同一個地址),equals() 方法比較的是兩個字符串對象的內容是否相同(當然,若兩個字符串引用同一個地址,使用 equals() 比較也返回 true);

這里就不得不提第二個知識點了,String 常量與非常量的區(qū)別;  

常量與非常量

那什么是常量,什么是非常量呢,簡單了解就是,String name = "sid10t." 這個是常量,屬于是對 name 進行賦值,直接存儲在常量池中,而 String name = new String("sid10t.") 這個就是非常量,因為重新創(chuàng)建了一個對象,這會將字符串 sid10t. 存儲在常量池中,然后在 Heap 中創(chuàng)建對象指向 name;

那這里為什么要提這個呢?當然是因為他們有較大的區(qū)別;

在 Java 語言規(guī)范(JavaSE 1.8版本)章節(jié)3.10.5 中有做規(guī)范,所有的 Java 語言編譯、運行時環(huán)境實現(xiàn)都必須依據此規(guī)范來實現(xiàn),里面有這么一句話:

Moreover, a string literal always refers to the same instance of class String. This is because string literals - or, more generally, strings that are the values of constant expressions (§15.28) - are "interned" so as to share unique instances, using the method String.intern.

大致意思就是凡是內容一樣的字符串常數,都要引用同一個字符串對象,換句話說就是內存地址相同;

因為其值為常量的字符串,都會通過 String.intern() 函數被限定為共享同一個對象;

稍后會解析 intern() 函數,也可以自行參考說明 String (Java Platform SE 8 );

回到正題,看一下語言規(guī)范里的這段代碼:

package testPackage;

class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

以及另一個包中的類:

package other;
public class Other { public static String hello = "Hello"; }

運行結果:

true true true true false true

結論:

  • 同一個包中同一個類中的字符串表示對同一個 String 對象的引用;
  • 同一個包中不同類中的字符串表示對同一個 String 對象的引用;
  • 不同包中不同類中的字符串同樣表示對同一 String 對象的引用;
  • 由常量表達式計算的字符串在編譯時計算,然后將其視為文字;
  • 在運行時通過連接計算的字符串是新創(chuàng)建的,因此是不同的;
  • 顯式地嵌入一個計算出來的字符串的結果與任何現(xiàn)有的具有相同內容的字面值字符串的結果相同;

如果對結論的理解不是很深刻的話,那就看看接下來的解釋:

System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");

“Hello” 和 “lo” 是字符串常量,在編譯期就被確定了,先檢查字符串常量池中是否含有 “Hello” 和 “lo”,如果沒有則添加 “Hello” 和 “lo” 到字符串常量池中,并且直接指向它們,所以 hello 和 lo 分別直接指向字符串常量池的 “Hello” 和 “lo”,也就是 hello 和 lo 指向的地址分別是常量池中的 “Hello” 和 “lo” ,因此第一個輸出中 hello 實際就是 “Hello”,所以 "Hello" == "Hello" 為 true,前三個輸出都是同理的;

System.out.print((hello == ("Hel"+"lo")) + " ");

"Hel" 和 "lo" 都是字符串常量,當一個字符串多個字符串常量連接而成時,它自己肯定也是字符串常量,在編譯器會被編譯器優(yōu)化成 "Hello",因為 "Hello" 在常量池中了,因此輸出為 true;

System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());

JVM 對于字符串引用,由于在字符串的 + 連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即 "Hel"+lo,所以在不執(zhí)行 intern() 方法的前提下,"Hel"+lo 不會存到常量池中,但 "Hel" 會被存到常量池中去,所以輸出一個為 true,一個為 false

intern()

String.intern() 是一個 Native 方法,它的作用是如果字符串常量池已經包含一個等于此 String 對象的字符串,則返回字符串常量池中這個字符串的引用, 否則將當前 String 對象的引用地址(堆中)添加到字符串常量池中并返回。  

JAVA 源碼

/*
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to
this String object as determined by the equals(Object) method, then the string from
the pool is returned. Otherwise, this String object is added to the pool and a
reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and
only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String
literals are defined in section 3.10.5 of the The Java Language Specification.

Returns:
a string that has the same contents as this string, but is guaranteed to be from a
pool of unique strings.
*/
public native String intern();

native 源碼

String.c

Java_java_lang_String_intern(JNIEnv *env, jobject this)  
{  
    return JVM_InternString(env, this);  
}  

jvm.h

/* 
* java.lang.String 
*/  
JNIEXPORT jstring JNICALL  
JVM_InternString(JNIEnv *env, jstring str);   

jvm.cpp

JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))  
  JVMWrapper("JVM_InternString");  
  JvmtiVMObjectAllocEventCollector oam;  
  if (str == NULL) return NULL;  
  oop string = JNIHandles::resolve_non_null(str);  
  oop result = StringTable::intern(string, CHECK_NULL);
  return (jstring) JNIHandles::make_local(env, result);  
JVM_END   

symbolTable.cpp

oop StringTable::intern(Handle string_or_null, jchar* name,  
                        int len, TRAPS) {  
  unsigned int hashValue = java_lang_String::hash_string(name, len);  
  int index = the_table()->hash_to_index(hashValue);  
  oop string = the_table()->lookup(index, name, len, hashValue);  
  // Found  
  if (string != NULL) return string;  
  // Otherwise, add to symbol to table  
  return the_table()->basic_add(index, string_or_null, name, len,  
                                hashValue, CHECK_NULL);  
}   
oop StringTable::lookup(int index, jchar* name,  
                        int len, unsigned int hash) {  
  for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) {  
    if (l->hash() == hash) {  
      if (java_lang_String::equals(l->literal(), name, len)) {  
        return l->literal();  
      }  
    }  
  }  
  return NULL;  
}  

它的大體實現(xiàn)結構就是:JAVA 使用 jni 調用 c++ 實現(xiàn)的 StringTable 的 intern 方法,StringTable 的 intern 方法跟 Java 中的 HashMap 的實現(xiàn)是差不多的,只是不能自動擴容,默認大小是1009。

要注意的是,String 的 String Pool 是一個固定大小的 Hashtable,默認值大小長度是1009,如果放進 String Pool 的 String 非常多,就會造成 Hash 沖突嚴重,從而導致鏈表會很長,而鏈表長了后直接會造成的影響就是當調用 String.intern 時性能會大幅下降(因為要一個一個找)。

在 JDK6 中 StringTable 是固定的,就是1009的長度,所以如果常量池中的字符串過多就會導致效率下降很快。

在 JDK7 中,StringTable 的長度可以通過一個參數指定:-XX:StringTableSize=99991;  

使用

在 JDK1.7 之前的版本,調用這個方法的時候,會去常量池中查看是否已經存在這個常量了,如果已經存在,那么直接返回這個常量在常量池中的地址值,如果不存在,則在常量池中創(chuàng)建一個,并返回其地址值。

但是在 JDK1.7 以及之后的版本中,常量池從 perm 區(qū)搬到了 heap 區(qū)。intern() 檢測到這個常量在常量池中不存在的時候,不會直接在常量池中創(chuàng)建該對象了,而是將堆中的這個對象的引用直接存到常量池中,減少內存開銷。

來看這段代碼:

public static void main(String[] args) {
	// part1
    String s1 = new String("sid10t.");
    s1.intern();
    String s2 = "sid10t.";
    System.out.println(s1 == s2);

	// part2
    String s3 = new String("Hello ") + new String("World!");
    s3.intern();
    String s4 = "Hello World!";
    System.out.println(s3 == s4);
}

在 JDK7 之前兩個都是 false,在 JDK7 之后輸出分別是 false,true;

接下來根據 JDK7 進行分析,

先來看 part1 部分:

String s1 = new String("sid10t."); 這行代碼生成了兩個最終對象:一個是常量池中的字符串常量 sid10t.,另一個是在堆中的 s1 引用指向的對象;

然后是第二行 s1.intern();,返回常量池中的字符串常量 sid10t.,因為常量池中已經存在了該常量,所以這里就直接返回即可,因此,在 part1 的此情此景中,這句話可寫可不寫,對輸出結果沒有任何影響;

所以最后輸出的肯定是 false,一個地址在堆中,一個在常量池里;

接下來看看 part2 部分:

String s3 = new String("Hello ") + new String("World!"); 這行代碼生成了三個最終對象:兩個分別是常量池中的對象 Hello ,World!,還有一個在堆中的 s3 引用指向的對象;

然后是第二行 s3.intern();,由于現(xiàn)在的常量池中不存在字符串常量 Hello World!,因此它會直接存儲 s3 在堆中的引用地址,而不是拷貝一份;

這時候,String s4 = "Hello World!";,常量池中已經有了 Hello World! 常量,也就是 s3 的引用地址,因此 s4 的值就是 s3 的引用地址,所以輸出的是 true;

根據上述分析,我們將 part2 的代碼略作調整,如下:

String s3 = new String("Hello ") + new String("World!");
// s3.intern();
String s4 = "Hello World!";
s3.intern();
System.out.println(s3 == s4);

輸出的就是 false 了,但如果是 s3.intern() == s4,則輸出的就是 true

想必你應該理解了!  

總結

到此這篇關于JAVA對象中使用 static 和 String 基礎探究的文章就介紹到這了,更多相關JAVA static和String內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論