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.message
,MyClass.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ù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MyBatis通用Mapper實現(xiàn)原理及相關內容
今天小編就為大家分享一篇關于MyBatis通用Mapper實現(xiàn)原理及相關內容,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12使用@Service注解出現(xiàn)No bean named 'xxxx'&
這篇文章主要介紹了使用@Service注解出現(xiàn)No bean named 'xxxx' available]錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08SpringBoot實現(xiàn)優(yōu)雅停機的流程步驟
優(yōu)雅停機(Graceful Shutdown) 是指在服務器需要關閉或重啟時,能夠先處理完當前正在進行的請求,然后再停止服務的操作,本文給大家介紹了SpringBoot實現(xiàn)優(yōu)雅停機的流程步驟,需要的朋友可以參考下2024-03-03解決idea services窗口不見的一種特殊情況(小白采坑系列)
這篇文章主要介紹了解決idea services窗口不見的一種特殊情況(小白采坑系列),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09解決mybatis執(zhí)行SQL語句部分參數返回NULL問題
這篇文章主要介紹了mybatis執(zhí)行SQL語句部分參數返回NULL問題,需要的的朋友參考下吧2017-06-06聊聊spring boot的WebFluxTagsProvider的使用
這篇文章主要介紹了聊聊spring boot的WebFluxTagsProvider的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07淺談maven的jar包和war包區(qū)別 以及打包方法
下面小編就為大家分享一篇淺談maven的jar包和war包區(qū)別 以及打包方法,具有很好的參考價值,希望對大家有所幫助2017-11-11