Java中的指针
Java中是否有指針? 簡短的答案是“不,沒有”,這對于許多開發人員來說似乎是顯而易見的。 但是,為什么對其他人卻不那么明顯呢?
- http://stackoverflow.com/questions/1750106/how-can-i-use-pointers-in-java
- http://stackoverflow.com/questions/2629357/does-java-have-pointers
- https://www.google.hu/search?q=pointers+in+java
這是因為Java用于訪問對象的引用與指針非常相似。 如果您有Java之前的C編程經驗,則可能更容易考慮將變量中存儲的值作為指向保存對象的某些內存位置的指針的想法。 或多或少都可以。 多于少,但這就是我們現在要看的。
引用和指針之間的區別
正如Brian Agnew在stackoverflow上總結的那樣 ,有兩個主要區別。
缺少指針算法
當您在C中擁有一個struct數組時,為該數組分配的內存將一個接一個地包含結構的內容。 如果你有類似的東西
struct circle {double radius;double x,y; } struct circle circles[6];它會在連續區域中占用6*3*sizeof(double)字節的內存(在64位體系結構中通常為144字節)。 如果您在Java中有類似的東西,則需要一個類( 直到我們使用Java 10或更高版本 ):
class Circle {double radius;double x,y; }和數組
Circle circles[6];將需要6個引用(大約48個字節)和6個對象(除非其中一些為null),每個24bytes數據(大約)和對象標頭 (16bytes)。 在64位體系結構上,總計288字節,并且存儲區域不是連續的。
訪問元素時,說出C語言數組的circles[n] ,代碼將使用指針算術。 它使用存儲在指針circles的地址加上n倍sizeof(struct circle) (字節),也就是數據所在的位置。
Java方法有些不同。 它查看對象circles ,它是一個數組,計算第n個元素(與C相似),并獲取存儲在其中的參考數據。 在參考數據可用之后,它使用它從參考數據所處的某個不同存儲位置訪問對象。
請注意,在這種情況下,Java的內存開銷為100%,并且讀取內存的次數為2而不是1,以訪問實際數據。
引用不指向內存
Java引用不是指針。 它們包含某種指針數據或某些指針數據,因為它來自當今計算機體系結構的本質,但這完全取決于JVM實現,它存儲在參考值中以及如何訪問其引用的對象。 盡管不是很有效的實現,但是擁有一個巨大的指針數組(每個指針指向JVM的一個對象),并且引用是該數組的索引,這絕對是可以的。
實際上,JVM將引用實現為一種指針混合,其中某些位是標志,而某些位“指向”相對于某個區域的某個內存位置。
為什么JVM這樣做而不是指針?
原因是垃圾回收。 為了實現有效的垃圾收集并避免內存碎片,JVM定期在內存中移動對象。 當由不再被引用的對象占用的內存被釋放并且我們恰好在一個巨大的可用內存塊中間有一個仍在使用和引用的小對象時,我們不希望該內存塊被拆分。 取而代之的是,JVM將對象移動到另一個內存區域,并更新對該對象的所有引用以跟蹤新位置。 一些GC實現會在發生這些更新時停止其他Java線程,以便沒有Java代碼使用未更新的引用而是移動了對象。 其他GC實現與底層OS虛擬內存管理集成在一起,從而在發生此類訪問時導致頁面錯誤,從而避免了應用程序線程的停止。
但是,問題是引用不是指針,而是JVM實現如何管理所有這些情況的責任。
與該領域密切相關的下一個主題是參數傳遞。
參數是通過值傳遞還是通過Java引用傳遞?
我在大學學習的第一門編程語言是Niklaus Wirth發明的PASCAL。 用這種語言,過程和函數參數可以通過值或引用來傳遞。 當參數通過引用傳遞時,則在過程或函數頭中的參數聲明之前帶有關鍵字VAR 。 在使用函數的地方,不允許程序員將表達式寫為實際參數。 您必須使用變量,并且函數(過程)中對參數的任何更改都將影響作為參數傳遞的變量。
當您使用語言C編程時,總是傳遞一個值。 但這實際上是一個謊言,因為您可能傳遞了指向該函數可以修改的變量的指針的值。 那就是當您將諸如char *s類的東西作為參數編寫時,如果函數使用指針算術,則該函數可以更改s指向的字符或整個字符串。
在PASCAL中,值傳遞或引用傳遞的聲明位于函數(或過程)的聲明中。 在C語言中,您必須明確地編寫一個類似于&s的表達式&s以將指針傳遞給變量s以便調用者可以對其進行修改。 當然,還必須聲明該函數以使用指向s具有的任何類型s指針。
當您閱讀PASCAL代碼時,您無法在實際函數調用的位置知道參數是否按值傳遞,因此可能會被函數修改。 如果使用C,則必須在兩個地方都進行編碼,并且每當看到傳遞了參數值&s ,都可以確保該函數能夠修改s的值。
Java到底是什么? 您可能對Java進行了多年編程,可能沒有遇到這個問題或對此沒有任何想法。 Java自動解決問題? 還是只是提供了一種非常簡單的解決方案,以致不存在雙重按值傳遞/引用方法?
可悲的事實是,Java實際上是隱藏了問題,并沒有解決問題。 只要我們只處理Java通過引用傳遞的對象。 當結果是一個對象時,無論您寫入實際函數調用中的任何表達式,對該對象的引用都將傳遞給該方法。 如果表達式是一個變量,則傳遞該變量包含的引用(這是變量的值,所以這是一種按值傳遞)。
當您傳遞基元( int , boolean等)時,參數將按值傳遞。 如果所求值的表達式結果為原語,則按值傳遞它。 如果表達式是變量,則傳遞該變量包含的原始值。 這樣一來,我們可以說看看三種示例語言
- PASCAL聲明如何傳遞參數
- C計算傳遞給它的實際值
- Java根據參數的類型決定
我認為Java有點混亂。 但是我沒有意識到這一點,因為這種混亂局面是有限的,并且由于原語的盒裝版本是不可變的,因此被很好地隱藏了。 如果無論如何都無法修改值,為什么還要關心參數傳遞的基本機制。 如果按值傳遞:可以。 如果它通過引用傳遞,則仍然可以,因為對象是不可變的。
如果裝箱的原始值是可變的,是否會引起問題? 我們將查看是否以及何時將在Java中使用值類型 。
翻譯自: https://www.javacodegeeks.com/2016/01/pointers-in-java.html
總結
- 上一篇: 微服务和Java EE
- 下一篇: 如何编写Java代理