JAVA對(duì)象中使用?static?和?String?基礎(chǔ)探究
前言
跟同學(xué)在討論 JAVA 期末試題時(shí),對(duì)于一些 static 和 String 在對(duì)象中的使用方法,若有所思,特此記錄一下,也祝沒有對(duì)象的友友可以自己 new
一個(gè)出來(lái)!
那我們先來(lái)看一看試卷里的原題;
原題
主要就是兩個(gè)類 MyClass.java
和 TestMyClass.java
,填代碼的部分就直接跳過(guò)了,然后就是輸出結(jié)果,看看你是否也能全部正確,
兩個(gè)類的具體代碼如下:
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()); } }
運(yùn)行結(jié)果:
Nice:Nice:Nice
true
false
true
2
如果你全部答對(duì)了,那么恭喜你,基礎(chǔ)很不錯(cuò)喲;答錯(cuò)的小伙伴也不要喪氣,接下來(lái)聽我娓娓道來(lái),扎實(shí)基礎(chǔ);
static
工欲善其事必先利其器,在開始解析之前,我們先回顧一下一些關(guān)于 static 的知識(shí);
簡(jiǎn)介
static 表示 “全局” 或者 “靜態(tài)” 的意思,用來(lái)修飾成員變量和成員方法,也可以形成靜態(tài) static 代碼塊,但是 Java 語(yǔ)言中沒有全局變量的概念;
被 static 修飾的成員變量和成員方法獨(dú)立于該類的任何對(duì)象,也就是說(shuō),它不依賴類特定的實(shí)例,被類的所有實(shí)例共享;
只要這個(gè)類被加載,Java 虛擬機(jī)就能根據(jù)類名在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi)定找到他們,因此,static 對(duì)象可以在它的任何對(duì)象創(chuàng)建之前訪問,無(wú)需引用任何對(duì)象;
用 public 修飾的 static 成員變量和成員方法本質(zhì)是全局變量和全局方法,當(dāng)聲明它類的對(duì)象時(shí),不生成 static 變量的副本,而是類的所有實(shí)例共享同一個(gè) static 變量;
static 變量前可以有 private 修飾,表示這個(gè)變量可以在類的靜態(tài)代碼塊中,或者類的其他靜態(tài)成員方法中使用,但是不能在其他類中通過(guò)類名來(lái)直接引用,這一點(diǎn)很重要;
實(shí)際上你需要搞明白,private 是訪問權(quán)限限定,static 表示不要實(shí)例化就可以使用,這樣就容易理解多了,static 前面加上其它訪問權(quán)限關(guān)鍵字的效果也以此類推。
static 修飾的成員變量和成員方法習(xí)慣上稱為靜態(tài)變量和靜態(tài)方法,可以直接通過(guò)類名來(lái)訪問,訪問語(yǔ)法為:
類名.靜態(tài)方法名(參數(shù)列表…)
使用
回顧了 static 相關(guān)知識(shí)之后,我們來(lái)看一下題目中的使用吧;
// 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
,然后通過(guò)下斷點(diǎn)調(diào)試可以獲知,兩個(gè)對(duì)象 mcl1
和 mcl2
被分配到了兩個(gè)不同的地址;
在往下調(diào)試時(shí),發(fā)現(xiàn) mc1.message
,mc2.message
,MyClass.message
三個(gè)成員變量的值是一樣的,且都從 Great → Excellent → Nice
,這就是剛才所回顧的,被 static 修飾的成員變量成了共享變量,被類的所有實(shí)例共享;
接下來(lái)我們?cè)僮鰝€(gè)試驗(yàn)驗(yàn)證一下:
//修改前 private int count; //修改后 private static int count;
可以發(fā)現(xiàn),我們只是對(duì) mcl1
對(duì)象進(jìn)行了操作,但是 mcl2
的成員變量 count
也跟著改變了,這就是因?yàn)樵?nbsp;MyClass
類中,成員變量 count
被 static 修飾,已經(jīng)成了該類的共享變量了,但凡是該類的對(duì)象,都訪問的是同一個(gè) count
變量;
當(dāng)然也可以通過(guò)身份碼進(jìn)行驗(yàn)證:
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
;
接下來(lái)講一些關(guān)于 String 的小知識(shí);
String
關(guān)于 String 的話,這里用到啥聊啥,就不全面的進(jìn)行了;
== 與 equals()
先來(lái)講講關(guān)于 String 的比較,一般常見的比較有兩種,即 ==
和 equals()
;
其中,==
比較的是兩個(gè)字符串的地址是否為相等(同一個(gè)地址),equals()
方法比較的是兩個(gè)字符串對(duì)象的內(nèi)容是否相同(當(dāng)然,若兩個(gè)字符串引用同一個(gè)地址,使用 equals()
比較也返回 true
);
這里就不得不提第二個(gè)知識(shí)點(diǎn)了,String 常量與非常量的區(qū)別;
常量與非常量
那什么是常量,什么是非常量呢,簡(jiǎn)單了解就是,String name = "sid10t."
這個(gè)是常量,屬于是對(duì) name
進(jìn)行賦值,直接存儲(chǔ)在常量池中,而 String name = new String("sid10t.")
這個(gè)就是非常量,因?yàn)橹匦聞?chuàng)建了一個(gè)對(duì)象,這會(huì)將字符串 sid10t.
存儲(chǔ)在常量池中,然后在 Heap 中創(chuàng)建對(duì)象指向 name
;
那這里為什么要提這個(gè)呢?當(dāng)然是因?yàn)樗麄冇休^大的區(qū)別;
在 Java 語(yǔ)言規(guī)范(JavaSE 1.8版本)章節(jié)3.10.5 中有做規(guī)范,所有的 Java 語(yǔ)言編譯、運(yùn)行時(shí)環(huán)境實(shí)現(xiàn)都必須依據(jù)此規(guī)范來(lái)實(shí)現(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.
大致意思就是凡是內(nèi)容一樣的字符串常數(shù),都要引用同一個(gè)字符串對(duì)象,換句話說(shuō)就是內(nèi)存地址相同;
因?yàn)槠渲禐槌A康淖址?,都?huì)通過(guò) String.intern()
函數(shù)被限定為共享同一個(gè)對(duì)象;
稍后會(huì)解析 intern()
函數(shù),也可以自行參考說(shuō)明 String (Java Platform SE 8 );
回到正題,看一下語(yǔ)言規(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"; }
以及另一個(gè)包中的類:
package other; public class Other { public static String hello = "Hello"; }
運(yùn)行結(jié)果:
true true true true false true
結(jié)論:
- 同一個(gè)包中同一個(gè)類中的字符串表示對(duì)同一個(gè) String 對(duì)象的引用;
- 同一個(gè)包中不同類中的字符串表示對(duì)同一個(gè) String 對(duì)象的引用;
- 不同包中不同類中的字符串同樣表示對(duì)同一 String 對(duì)象的引用;
- 由常量表達(dá)式計(jì)算的字符串在編譯時(shí)計(jì)算,然后將其視為文字;
- 在運(yùn)行時(shí)通過(guò)連接計(jì)算的字符串是新創(chuàng)建的,因此是不同的;
- 顯式地嵌入一個(gè)計(jì)算出來(lái)的字符串的結(jié)果與任何現(xiàn)有的具有相同內(nèi)容的字面值字符串的結(jié)果相同;
如果對(duì)結(jié)論的理解不是很深刻的話,那就看看接下來(lái)的解釋:
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” ,因此第一個(gè)輸出中 hello
實(shí)際就是 “Hello”,所以 "Hello" == "Hello"
為 true
,前三個(gè)輸出都是同理的;
System.out.print((hello == ("Hel"+"lo")) + " ");
"Hel" 和 "lo" 都是字符串常量,當(dāng)一個(gè)字符串由多個(gè)字符串常量連接而成時(shí),它自己肯定也是字符串常量,在編譯器會(huì)被編譯器優(yōu)化成 "Hello",因?yàn)?"Hello" 在常量池中了,因此輸出為 true
;
System.out.print((hello == ("Hel"+lo)) + " "); System.out.println(hello == ("Hel"+lo).intern());
JVM 對(duì)于字符串引用,由于在字符串的 +
連接中,有字符串引用存在,而引用的值在程序編譯期是無(wú)法確定的,即 "Hel"+lo
,所以在不執(zhí)行 intern()
方法的前提下,"Hel"+lo
不會(huì)存到常量池中,但 "Hel" 會(huì)被存到常量池中去,所以輸出一個(gè)為 true
,一個(gè)為 false
;
intern()
String.intern()
是一個(gè) Native 方法,它的作用是如果字符串常量池已經(jīng)包含一個(gè)等于此 String 對(duì)象的字符串,則返回字符串常量池中這個(gè)字符串的引用, 否則將當(dāng)前 String 對(duì)象的引用地址(堆中)添加到字符串常量池中并返回。
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; }
它的大體實(shí)現(xiàn)結(jié)構(gòu)就是:JAVA 使用 jni 調(diào)用 c++ 實(shí)現(xiàn)的 StringTable 的 intern
方法,StringTable 的 intern
方法跟 Java 中的 HashMap 的實(shí)現(xiàn)是差不多的,只是不能自動(dòng)擴(kuò)容,默認(rèn)大小是1009。
要注意的是,String 的 String Pool 是一個(gè)固定大小的 Hashtable,默認(rèn)值大小長(zhǎng)度是1009,如果放進(jìn) String Pool 的 String 非常多,就會(huì)造成 Hash 沖突嚴(yán)重,從而導(dǎo)致鏈表會(huì)很長(zhǎng),而鏈表長(zhǎng)了后直接會(huì)造成的影響就是當(dāng)調(diào)用 String.intern
時(shí)性能會(huì)大幅下降(因?yàn)橐粋€(gè)一個(gè)找)。
在 JDK6 中 StringTable
是固定的,就是1009的長(zhǎng)度,所以如果常量池中的字符串過(guò)多就會(huì)導(dǎo)致效率下降很快。
在 JDK7 中,StringTable 的長(zhǎng)度可以通過(guò)一個(gè)參數(shù)指定:-XX:StringTableSize=99991
;
使用
在 JDK1.7 之前的版本,調(diào)用這個(gè)方法的時(shí)候,會(huì)去常量池中查看是否已經(jīng)存在這個(gè)常量了,如果已經(jīng)存在,那么直接返回這個(gè)常量在常量池中的地址值,如果不存在,則在常量池中創(chuàng)建一個(gè),并返回其地址值。
但是在 JDK1.7 以及之后的版本中,常量池從 perm 區(qū)搬到了 heap 區(qū)。intern()
檢測(cè)到這個(gè)常量在常量池中不存在的時(shí)候,不會(huì)直接在常量池中創(chuàng)建該對(duì)象了,而是將堆中的這個(gè)對(duì)象的引用直接存到常量池中,減少內(nèi)存開銷。
來(lái)看這段代碼:
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 之前兩個(gè)都是 false
,在 JDK7 之后輸出分別是 false
,true
;
接下來(lái)根據(jù) JDK7 進(jìn)行分析,
先來(lái)看 part1 部分:
String s1 = new String("sid10t.");
這行代碼生成了兩個(gè)最終對(duì)象:一個(gè)是常量池中的字符串常量 sid10t.
,另一個(gè)是在堆中的 s1 引用指向的對(duì)象;
然后是第二行 s1.intern();
,返回常量池中的字符串常量 sid10t.
,因?yàn)槌A砍刂幸呀?jīng)存在了該常量,所以這里就直接返回即可,因此,在 part1 的此情此景中,這句話可寫可不寫,對(duì)輸出結(jié)果沒有任何影響;
所以最后輸出的肯定是 false
,一個(gè)地址在堆中,一個(gè)在常量池里;
接下來(lái)看看 part2 部分:
String s3 = new String("Hello ") + new String("World!");
這行代碼生成了三個(gè)最終對(duì)象:兩個(gè)分別是常量池中的對(duì)象 Hello
,World!
,還有一個(gè)在堆中的 s3 引用指向的對(duì)象;
然后是第二行 s3.intern();
,由于現(xiàn)在的常量池中不存在字符串常量 Hello World!
,因此它會(huì)直接存儲(chǔ) s3 在堆中的引用地址,而不是拷貝一份;
這時(shí)候,String s4 = "Hello World!";
,常量池中已經(jīng)有了 Hello World!
常量,也就是 s3 的引用地址,因此 s4 的值就是 s3 的引用地址,所以輸出的是 true
;
根據(jù)上述分析,我們將 part2 的代碼略作調(diào)整,如下:
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
;
想必你應(yīng)該理解了!
總結(jié)
到此這篇關(guān)于JAVA對(duì)象中使用 static 和 String 基礎(chǔ)探究的文章就介紹到這了,更多相關(guān)JAVA static和String內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 實(shí)例分析Java中public static void main(String args[])是什么意思
- Java中的static--靜態(tài)變量你了解嗎
- Java關(guān)鍵字詳解之final static this super的用法
- Java修飾符abstract與static及final的精華總結(jié)
- Java中的static關(guān)鍵字修飾屬性和方法(推薦)
- Java中String的split切割字符串方法實(shí)例及擴(kuò)展
- java如何把逗號(hào)分隔的String字符串轉(zhuǎn)int集合
- Java中實(shí)現(xiàn)String字符串用逗號(hào)隔開
- java中String字符串刪除空格的七種方式
相關(guān)文章
MyBatis通用Mapper實(shí)現(xiàn)原理及相關(guān)內(nèi)容
今天小編就為大家分享一篇關(guān)于MyBatis通用Mapper實(shí)現(xiàn)原理及相關(guān)內(nèi)容,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12使用@Service注解出現(xiàn)No bean named 'xxxx'&
這篇文章主要介紹了使用@Service注解出現(xiàn)No bean named 'xxxx' available]錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08SpringBoot實(shí)現(xiàn)優(yōu)雅停機(jī)的流程步驟
優(yōu)雅停機(jī)(Graceful Shutdown) 是指在服務(wù)器需要關(guān)閉或重啟時(shí),能夠先處理完當(dāng)前正在進(jìn)行的請(qǐng)求,然后再停止服務(wù)的操作,本文給大家介紹了SpringBoot實(shí)現(xiàn)優(yōu)雅停機(jī)的流程步驟,需要的朋友可以參考下2024-03-03SpringBoot應(yīng)用快速部署到K8S的詳細(xì)教程
這篇文章主要介紹了SpringBoot應(yīng)用快速部署到K8S的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12解決idea services窗口不見的一種特殊情況(小白采坑系列)
這篇文章主要介紹了解決idea services窗口不見的一種特殊情況(小白采坑系列),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09解決mybatis執(zhí)行SQL語(yǔ)句部分參數(shù)返回NULL問題
這篇文章主要介紹了mybatis執(zhí)行SQL語(yǔ)句部分參數(shù)返回NULL問題,需要的的朋友參考下吧2017-06-06聊聊spring boot的WebFluxTagsProvider的使用
這篇文章主要介紹了聊聊spring boot的WebFluxTagsProvider的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07淺談maven的jar包和war包區(qū)別 以及打包方法
下面小編就為大家分享一篇淺談maven的jar包和war包區(qū)別 以及打包方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11