簡體   English   中英

為什么人們仍然在 Java 中使用原始類型?

[英]Why do people still use primitive types in Java?

從 Java 5 開始,我們對原始類型進行了裝箱/拆箱,以便將int包裝為java.lang.Integer ,等等。

我最近看到很多使用int而不是java.lang.Integer的新 Java 項目(肯定需要至少版本 5 的 JRE,如果不是 6),盡管使用后者更方便,因為它有一些轉換為long值的輔助方法等。

為什么有些人仍然在 Java 中使用原始類型? 有什么切實的好處嗎?

在 Joshua Bloch 的Effective Java 的第 5 項:“避免創建不必要的對象”中,他發布了以下代碼示例:

public static void main(String[] args) {
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

並且運行需要 43 秒。 將 Long 放入原語將其降低到 6.8 秒......如果這能說明我們為什么使用原語的話。

缺少本機值相等也是一個問題( .equals()==相比相當冗長)

對於 biziclop:

class Biziclop {

    public static void main(String[] args) {
        System.out.println(new Integer(5) == new Integer(5));
        System.out.println(new Integer(500) == new Integer(500));

        System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
        System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
    }
}

結果是:

false
false
true
false

編輯為什么 (3) 返回true和 (4) 返回false

因為它們是兩個不同的對象。 最接近零的 256 個整數 [-128; 127] 由 JVM 緩存,因此它們返回相同的對象。 但是,超出該范圍時,它們不會被緩存,因此會創建一個新對象。 更復雜的是,JLS 要求緩存至少256 個享元。 JVM 實現者可以根據需要添加更多內容,這意味着這可以在緩存最近的 1024 並且所有這些都返回 true 的系統上運行...#awkward

自動拆箱可能導致難以發現 NPE

Integer in = null;
...
...
int i = in; // NPE at runtime

在大多數情況下,對in的 null 賦值不如上面那么明顯。

盒裝類型性能較差,需要更多內存。

原始類型:

int x = 1000;
int y = 1000;

現在評價:

x == y

這是true 不足為奇。 現在嘗試盒裝類型:

Integer x = 1000;
Integer y = 1000;

現在評價:

x == y

這是false 大概。 取決於運行時。 這個理由夠嗎?

除了性能和內存問題之外,我還想提出另一個問題:如果沒有intList接口將被破壞。
問題是重載的remove()方法( remove(int)remove(Object) )。 remove(Integer)將始終解析為調用后者,因此您無法按索引刪除元素。

另一方面,嘗試添加和刪除int時存在一個陷阱:

final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!

你真的能想象一個

  for (int i=0; i<10000; i++) {
      do something
  }

用 java.lang.Integer 代替循環? java.lang.Integer 是不可變的,因此循環中的每個增量都會在堆上創建一個新的 java 對象,而不是僅使用單個 JVM 指令在堆棧上增加 int。 表演將是惡魔般的。

我真的不同意使用 java.lang.Integer 比 int 更方便的模式。 相反。 自動裝箱意味着您可以在原本必須使用 Integer 的地方使用 int,而 Java 編譯器會負責插入代碼來為您創建新的 Integer 對象。 自動裝箱就是允許您在需要 Integer 的地方使用 int,編譯器插入相關的對象構造。 它首先不會消除或減少對 int 的需求。 通過自動裝箱,您可以兩全其美。 當您需要基於堆的 java 對象時,您會自動為您創建一個 Integer,而當您只進行算術和本地計算時,您將獲得 int 的速度和效率。

原始類型快得多:

int i;
i++;

整數(所有數字和字符串)是一種不可變類型:一旦創建就無法更改。 如果i是 Integer,那么i++會創建一個新的 Integer 對象——在內存和處理器方面要貴得多。

首先,習慣。 如果您已經用 Java 編碼了八年,就會積累相當多的慣性。 如果沒有令人信服的理由,為什么要改變? 使用盒裝原語並沒有任何額外的優勢。

另一個原因是斷言null不是一個有效的選項。 將兩個數字的總和或循環變量聲明為Integer將是毫無意義和誤導性的。

還有它的性能方面,雖然性能差異在許多情況下並不重要(盡管當它是時,它非常糟糕),沒有人喜歡編寫可以以我們已經更快的方式輕松編寫的代碼曾經。

順便說一下,Smalltalk 只有對象(沒有原語),但他們已經優化了它們的小整數(不使用全部 32 位,僅使用 27 位或類似的)不分配任何堆空間,而只是使用特殊的位模式。 其他常見的對象(真、假、空)在這里也有特殊的位模式。

因此,至少在 64 位 JVM(具有 64 位指針命名空間)上,應該可能根本沒有任何 Integer、Character、Byte、Short、Boolean、Float(和 small Long)對象(除了這些創建的對象)通過顯式new ...() ),只有特殊的位模式,可以非常有效地由普通運算符操作。

我不敢相信沒有人提到我認為最重要的原因:“int”就是這樣,比“Integer”更容易輸入。 我認為人們低估了簡潔語法的重要性。 性能並不是避免它們的真正原因,因為大多數時候使用數字是在循環索引中,並且在任何非平凡循環(無論您使用的是 int 還是 Integer)中遞增和比較這些都沒有任何成本。

另一個給定的原因是您可以獲得 NPE,但是使用裝箱類型非常容易避免(並且只要您始終將它們初始化為非空值,就可以保證避免這種情況)。

另一個原因是 (new Long(1000))==(new Long(1000)) 是假的,但這只是另一種說法,“.equals”對盒裝類型沒有語法支持(不像運算符 <, > 、= 等),所以我們回到“更簡單的語法”的原因。

我認為 Steve Yegge 的非原始循環示例很好地說明了我的觀點: http : //sites.google.com/site/steveyegge2/language-trickery-and-ejb

想一想:與必須使用 Runnable 和 Callable 等接口模擬它們的 Java 相比,在具有良好語法的語言(如任何函數式語言、python、ruby 甚至 C)中使用函數類型的頻率如何?無名類。

不擺脫基元的幾個原因:

  • 向后兼容性。

如果它被消除,任何舊程序都將無法運行。

  • JVM 重寫。

必須重寫整個 JVM 才能支持這個新事物。

  • 更大的內存占用。

您需要存儲值和引用,這會占用更多內存。 如果您有大量字節,則使用byte比使用Byte小得多。

  • 空指針問題。

聲明int i然后用i做東西不會導致任何問題,但聲明Integer i然后做同樣的事情會導致 NPE。

  • 平等問題。

考慮這個代碼:

Integer i1 = 5;
Integer i2 = 5;

i1 == i2; // Currently would be false.

會是假的。 運算符將不得不過載,這將導致內容的重大重寫。

  • 減緩

對象包裝器比它們的原始對應物要慢得多。

對象比原始類型更重要,因此原始類型比包裝類的實例更有效。

原始類型非常簡單:例如一個 int 是 32 位,在內存中正好占用 32 位,並且可以直接操作。 Integer 對象是一個完整的對象,它(像任何對象一樣)必須存儲在堆上,並且只能通過指向它的引用(指針)訪問。 它很可能還占用超過 32 位(4 字節)的內存。

也就是說,Java 區分原始類型和非原始類型這一事實也是 Java 編程語言時代的標志。 較新的編程語言沒有這種區別; 這種語言的編譯器足夠聰明,可以自行判斷您使用的是簡單值還是更復雜的對象。

例如,在 Scala 中沒有原始類型; 有一個用於整數的類 Int,一個 Int 是一個真實的對象(您可以對其進行方法等)。 當編譯器編譯您的代碼時,它會在幕后使用原始 int,因此使用 Int 與在 Java 中使用原始 int 一樣有效。

除了其他人所說的,原始局部變量不是從堆中分配的,而是在堆棧上分配的。 但是對象是從堆中分配的,因此必須進行垃圾回收。

原始類型有很多優點:

  • 編寫更簡單的代碼
  • 性能更好,因為您沒有為變量實例化對象
  • 由於它們不代表對對象的引用,因此無需檢查空值
  • 除非您需要利用裝箱功能,否則請使用原始類型。
int loops = 100000000;

long start = System.currentTimeMillis();
for (Long l = new Long(0); l<loops;l++) {
    //System.out.println("Long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around Long: "+ (System.currentTimeMillis()- start));

start = System.currentTimeMillis();
for (long l = 0; l<loops;l++) {
    //System.out.println("long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around long: "+ (System.currentTimeMillis()- start));

圍繞 Long 循環“100000000”次所用的毫秒數:468

循環 '100000000' 次所用的毫秒數:31

附帶說明一下,我不介意看到這樣的東西發現它進入了 Java。

Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
   ...
}

for 循環自動將 loop1 從 0 增加到 1000 或

Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
   ...
}

其中 for 循環自動將 loop1 1000 遞減為 0。

很難知道在幕后進行了什么樣的優化。

對於本地使用,當編譯器有足夠的信息進行優化排除空值的可能性時,我希望性能相同或相似

然而,基元數組顯然與盒裝基元集合有很大不同 這是有道理的,因為在一個集合中很少有優化是可能的。

此外,與int相比, Integer具有更高的邏輯開銷:現在您必須擔心int a = b + c; 拋出異常。

我會盡可能多地使用原語,並依靠工廠方法和自動裝箱在需要時為我提供語義更強大的裝箱類型。

  1. 您需要進行數學運算的原語
  2. 如上所述,原語占用的內存更少,性能更好

你應該問為什么需要類/對象類型

使用 Object 類型的原因是讓我們在處理 Collections 時生活更輕松。 原語不能直接添加到 List/Map 中,您需要編寫一個包裝類。 Readymade Integer 類的類可以幫助你在這里加上它有許多實用方法,如 Integer.pareseInt(str)

我同意以前的答案,使用原語包裝對象可能很昂貴。 但是,如果您的應用程序的性能並不重要,則可以在使用對象時避免溢出。 例如:

long bigNumber = Integer.MAX_VALUE + 2;

bigNumber的值是 -2147483647,你會期望它是 2147483649。這是代碼中的一個錯誤,可以通過執行以下操作來修復:

long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').

bigNumber將是 2147483649。這類錯誤有時很容易被遺漏,並可能導致未知行為或漏洞(參見CWE-190 )。

如果您使用包裝器對象,則等效代碼將無法編譯。

Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling

因此,通過使用原語包裝對象可以更輕松地解決此類問題。

你的問題已經回答得很清楚了,我的回答只是補充一點以前沒有提到的信息。

因為 JAVA 以原始類型執行所有數學運算。 考慮這個例子:

public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i % 2 == 0)
            sum += i;
        return sum;
}

在這里,提醒和一元加運算不能應用於 Integer(Reference) 類型,編譯器執行拆箱並執行操作。

因此,請確保在 java 程序中發生了多少自動裝箱和拆箱操作。 因為,執行此操作需要時間。

一般來說,最好保留引用類型的參數和原始類型的結果。

原始類型要快得多並且需要的內存少得多 因此,我們可能更喜歡使用它們。

另一方面,當前的 Java 語言規范不允許在參數化類型(泛型)、Java 集合或反射 API 中使用原始類型。

當我們的應用程序需要包含大量元素的集合時,我們應該考慮使用盡可能“經濟”類型的數組。

*有關詳細信息,請參閱來源: https : //www.baeldung.com/java-primitives-vs-objects

簡而言之:原始類型比盒裝類型更快,需要的內存更少

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM