เป็นจำนวนเต็มไม่เปลี่ยนรูป


103

ฉันรู้ว่านี่อาจจะโง่มาก แต่หลายแห่งอ้างว่าคลาส Integer ใน Java ไม่เปลี่ยนรูป แต่รหัสต่อไปนี้:

Integer a=3;
Integer b=3;
a+=b;
System.out.println(a);

ดำเนินการโดยไม่มีปัญหาใด ๆ ให้ผลลัพธ์ (ที่คาดหวัง) 6. ดังนั้นค่าของ a จึงเปลี่ยนไปอย่างมีประสิทธิภาพ ไม่ได้หมายความว่าจำนวนเต็มไม่แน่นอน? คำถามรองและนอกหัวข้อเล็กน้อย: "คลาสที่ไม่เปลี่ยนรูปไม่จำเป็นต้องมีตัวสร้างการคัดลอก" ใครสนใจที่จะอธิบายว่าทำไม?


13
คลาสไม่เปลี่ยนรูป แต่การทำ autoboxing ทำให้สิ่งที่น่าสนใจเกิดขึ้น: stackoverflow.com/questions/3085332/…
wkl

ขอบคุณมวยคือคีย์เวิร์ดที่ Google ต้องการ :)
K.Steff

7
คุณกำลังสับสนกับค่าสุดท้ายหรือค่าคงที่
Code กระตือรือร้น

คำตอบ:


95

ไม่เปลี่ยนรูปไม่ได้หมายความว่าaจะไม่มีวันเท่ากับค่าอื่น ตัวอย่างเช่นStringไม่เปลี่ยนรูปด้วย แต่ฉันยังสามารถทำได้:

String str = "hello";
// str equals "hello"
str = str + "world";
// now str equals "helloworld"

strไม่ได้มีการเปลี่ยนแปลงค่อนข้างstrขณะนี้คือวัตถุที่สมบูรณ์ instantiated ใหม่เช่นเดียวกับที่คุณIntegerมี ดังนั้นค่าของaไม่ได้กลายพันธุ์ new Integer(6)แต่มันถูกแทนที่ด้วยวัตถุใหม่ที่สมบูรณ์แบบคือ


14
"นี่เป็นเพราะตอนนี้ str เป็นวัตถุที่สร้างอินสแตนซ์ใหม่ทั้งหมด" หรือ str (a varibale) ชี้ไปที่วัตถุใหม่ อ็อบเจ็กต์นั้นไม่สามารถเปลี่ยนแปลงได้ แต่เนื่องจากตัวแปรยังไม่สิ้นสุดจึงสามารถชี้ไปที่อ็อบเจ็กต์อื่นได้
Sandman

ใช่มันชี้ไปที่วัตถุอื่นที่สร้างอินสแตนซ์อันเป็นผลมาจากการ+=ดำเนินการ
Travis Webb

11
พูดอย่างเคร่งครัดไม่จำเป็นต้องเป็นวัตถุใหม่ การใช้ Boxing Integer.valueOf(int)และวิธีการนั้นจะรักษาแคชของIntegerวัตถุ ดังนั้นผลมาจาก+=บนIntegerตัวแปรอาจจะเป็นวัตถุที่มีอยู่ก่อนหน้านี้ (หรือมันก็อาจจะเป็นวัตถุเดียวกัน ... ในกรณีของa += 0)
Stephen C

1
เหตุใด JavaDoc สำหรับ String จึงพูดอย่างชัดเจนว่าไม่เปลี่ยนรูป แต่ JavaDoc สำหรับ Integer ไม่ ความแตกต่างนั่นคือสาเหตุที่ฉันอ่านคำถามนี้ ...
cellepo

52

aเป็น "การอ้างอิง" สำหรับจำนวนเต็ม (3) ซึ่งการชวเลขของคุณa+=bหมายถึงการทำสิ่งนี้:

a = new Integer(3 + 3)

ไม่ดังนั้นจำนวนเต็มจึงไม่แปรผัน แต่ตัวแปรที่ชี้ไปที่พวกเขาคือ *

* เป็นไปได้ที่จะมีตัวแปรที่ไม่เปลี่ยนรูปซึ่งแสดงโดยคำหลักfinalซึ่งหมายความว่าข้อมูลอ้างอิงอาจไม่เปลี่ยนแปลง

final Integer a = 3;
final Integer b = 3;
a += b; // compile error, the variable `a` is immutable, too.

20

คุณสามารถระบุได้ว่าวัตถุมีการเปลี่ยนแปลงโดยใช้System.identityHashCode()(วิธีที่ดีกว่าคือการใช้แบบธรรมดา==แต่ไม่ชัดเจนเท่าที่การอ้างอิงแทนที่จะเปลี่ยนค่า)

Integer a = 3;
System.out.println("before a +=3; a="+a+" id="+Integer.toHexString(System.identityHashCode(a)));
a += 3;
System.out.println("after a +=3; a="+a+" id="+Integer.toHexString(System.identityHashCode(a)));

พิมพ์

before a +=3; a=3 id=70f9f9d8
after a +=3; a=6 id=2b820dda

คุณสามารถเห็น "id" ที่อยู่ภายใต้การอ้างอิงของวัตถุที่aอ้างถึงมีการเปลี่ยนแปลง


1
System.identityHashCode () เป็นเคล็ดลับที่ดีมาก ขอบคุณสำหรับสิ่งนี้.
Ad Infinitum

11

สำหรับคำถามเริ่มต้นที่ถามว่า

Integer a=3;
Integer b=3;
a+=b;
System.out.println(a);

จำนวนเต็มไม่เปลี่ยนรูปดังนั้นสิ่งที่เกิดขึ้นข้างต้นคือ 'a' ได้เปลี่ยนเป็นการอ้างอิงใหม่ของค่า 6 ค่าเริ่มต้น 3 จะไม่เหลือการอ้างอิงในหน่วยความจำ (ไม่มีการเปลี่ยนแปลง) ดังนั้นจึงสามารถเก็บขยะได้

หากสิ่งนี้เกิดขึ้นกับ String สตริงจะเก็บไว้ในพูล (ในพื้นที่ PermGen) เป็นระยะเวลานานกว่าจำนวนเต็มตามที่คาดว่าจะมีการอ้างอิง


9

ใช่จำนวนเต็มไม่เปลี่ยนรูป

A คือการอ้างอิงที่ชี้ไปที่วัตถุ เมื่อคุณเรียกใช้ a + = 3 จะเป็นการกำหนดให้ A อ้างอิงอ็อบเจ็กต์จำนวนเต็มใหม่ด้วยค่าที่แตกต่างกัน

คุณไม่เคยแก้ไขวัตถุต้นฉบับ แต่คุณชี้การอ้างอิงไปยังวัตถุอื่น

อ่านข้อมูลเกี่ยวกับความแตกต่างระหว่างวัตถุและการอ้างอิงที่นี่


พูดง่ายๆเป็นภาษาของคนธรรมดาพร้อมคำอธิบายที่ซับซ้อนอื่น ๆ ทั้งหมด :)
Roshan Fernando

5

ไม่เปลี่ยนรูปไม่ได้หมายความว่าคุณไม่สามารถเปลี่ยนค่าสำหรับตัวแปรได้ หมายความว่าการกำหนดใหม่จะสร้างวัตถุใหม่ (กำหนดตำแหน่งหน่วยความจำใหม่) จากนั้นจึงกำหนดค่าให้กับมัน

เพื่อทำความเข้าใจสิ่งนี้ด้วยตัวคุณเองให้ดำเนินการกำหนดจำนวนเต็มในลูป (ด้วยจำนวนเต็มที่ประกาศนอกลูป) และดูวัตถุที่มีชีวิตในหน่วยความจำ

สาเหตุที่ไม่จำเป็นต้องใช้ตัวสร้างการคัดลอกสำหรับวัตถุที่ไม่เปลี่ยนรูปเป็นสามัญสำนึกง่ายๆ เนื่องจากงานแต่ละงานจะสร้างออบเจ็กต์ใหม่ในทางเทคนิคภาษาจึงสร้างสำเนาไว้แล้วดังนั้นคุณจึงไม่ต้องสร้างสำเนาอีก


2

"คลาสที่ไม่เปลี่ยนรูปไม่จำเป็นต้องมีตัวสร้างสำเนา" ใครสนใจที่จะอธิบายว่าทำไม?

เหตุผลก็คือแทบไม่จำเป็นต้องคัดลอก (หรือจุดใด ๆ ในการคัดลอก) อินสแตนซ์ของคลาสที่ไม่เปลี่ยนรูป สำเนาของวัตถุควร "เหมือนกับ" ต้นฉบับและถ้าเหมือนกันก็ไม่จำเป็นต้องสร้างขึ้นมา

มีสมมติฐานพื้นฐานบางประการแม้ว่า:

  • ถือว่าแอปพลิเคชันของคุณไม่ได้ให้ความหมายใด ๆ กับเอกลักษณ์ของวัตถุของอินสแตนซ์ของคลาส

  • สันนิษฐานว่าคลาสมีงานหนักเกินไปequalsและhashCodeสำเนาของอินสแตนซ์จะ "เหมือนกับ" ต้นฉบับ ... ตามวิธีการเหล่านี้

สมมติฐานเหล่านี้หรือทั้งสองข้ออาจเป็นเท็จและอาจรับประกันการเพิ่มตัวสร้างสำเนา


1

นี่คือวิธีที่ฉันเข้าใจว่าไม่เปลี่ยนรูป

int a=3;    
int b=a;
b=b+5;
System.out.println(a); //this returns 3
System.out.println(b); //this returns 8

ถ้า int กลายพันธุ์ได้ "a" จะพิมพ์ 8 แต่ไม่ใช่เพราะไม่เปลี่ยนรูปนั่นจึงเป็น 3 ตัวอย่างของคุณเป็นเพียงการกำหนดใหม่


0

ฉันสามารถระบุได้อย่างชัดเจนว่าจำนวนเต็ม (และลัทธิอื่น ๆ เช่น Float, Short ฯลฯ ) ไม่เปลี่ยนรูปด้วยโค้ดตัวอย่างง่ายๆ:

ตัวอย่างรหัส

public class Test{
    public static void main(String... args){
        Integer i = 100;
        StringBuilder sb = new StringBuilder("Hi");
        Test c = new Test();
        c.doInteger(i);
        c.doStringBuilder(sb);
        System.out.println(sb.append(i)); //Expected result if Integer is mutable is Hi there 1000
    }

    private void doInteger(Integer i){
        i=1000;
    }

    private void doStringBuilder(StringBuilder sb){
        sb.append(" there");
    }

}

ผลลัพธ์ที่แท้จริง

ผลลัพธ์มาถึงเขาHi There 100แทนที่จะเป็นผลลัพธ์ที่คาดหวัง (ในกรณีที่ทั้ง sb และ i เป็นวัตถุที่เปลี่ยนแปลงได้) Hi There 1000

สิ่งนี้แสดงให้เห็นว่าออบเจ็กต์ที่สร้างโดย i ใน main ไม่ได้ถูกแก้ไขในขณะที่ sb ถูกแก้ไข

ดังนั้น StringBuilder จึงแสดงพฤติกรรมที่เปลี่ยนแปลงได้ แต่ไม่ใช่ Integer

จำนวนเต็มจึงไม่เปลี่ยนรูป ดังนั้นพิสูจน์แล้ว

รหัสอื่นที่ไม่มีเพียงจำนวนเต็ม:

public class Test{
    public static void main(String... args){
        Integer i = 100;
        Test c = new Test();
        c.doInteger(i);
        System.out.println(i); //Expected result is 1000 in case Integer is mutable
    }

    private void doInteger(Integer i){
        i=1000;
    }


}

คุณกำลังทำสองสิ่งที่แตกต่างกัน - พยายามกำหนดจำนวนเต็มใหม่และเรียกใช้เมธอดบน stringbuilder ถ้าคุณทำprivate void doStringBuilder(StringBuilder sb){ sb = new StringBuilder(); }แล้วsbจะไม่เปลี่ยนแปลง
MT0

ฉันเพิ่ม StringBuilder (ซึ่งไม่สามารถเปลี่ยนแปลงได้) เพียงแค่ตีค่า Integer กับวัตถุอื่นที่ไม่แน่นอน หากคุณต้องการคุณสามารถลบโค้ดที่เกี่ยวข้องกับ StringBuilder ทั้งหมดและพิมพ์ออกมาเพื่อดู 100
Ashutosh Nigam

สิ่งนี้ไม่ได้พิสูจน์ความไม่เปลี่ยนรูป - สิ่งที่คุณทำคือการแฮชใหม่ตัวอย่างนี้แสดงให้เห็นว่า Java ใช้pass-by-value (และค่าที่ส่งผ่านสำหรับอ็อบเจ็กต์คือพอยน์เตอร์)
MT0

ลองprivate void doInteger(Integer i){ System.out.println( i == 100 ); i=1000; System.out.println( i == 100 ); }
MT0

@ MT0 เมื่อคุณส่งผ่านค่า StringBuilder ยังคงชี้ไปที่อ็อบเจ็กต์เดียวกัน แต่ Integer กำลังส่งสำเนาใหม่โดยไม่อ้างอิงถึงอ็อบเจ็กต์เดียวกัน หากคุณพิมพ์ใน doInteger คุณกำลังแสดงสำเนาที่อยู่ในฟังก์ชันไม่ใช่ฟังก์ชันหลัก เราต้องการดูว่าวัตถุที่ชี้โดย i ใน main เหมือนกันหรือไม่ หวังว่ามันจะเคลียร์แนวความคิด :) นอกจากนี้เวอร์ชันที่ไม่เปลี่ยนรูปของ StringBuilder คือ String โปรดแจ้งให้เราทราบหากคุณต้องการให้ฉันแชร์ตัวอย่าง
Ashutosh Nigam

0
public static void main(String[] args) {
    // TODO Auto-generated method stub

    String s1="Hi";
    String s2=s1;

    s1="Bye";

    System.out.println(s2); //Hi  (if String was mutable output would be: Bye)
    System.out.println(s1); //Bye

    Integer i=1000;
    Integer i2=i;

    i=5000;

    System.out.println(i2); // 1000
    System.out.println(i); // 5000

    int j=1000;
    int j2=j;

    j=5000;

    System.out.println(j2); // 1000
    System.out.println(j); //  5000


    char c='a';
    char b=c;

    c='d';

    System.out.println(c); // d
    System.out.println(b); // a
}

ผลลัพธ์คือ:

สวัสดี Bye 1000 5000 1000 5000 d ก

ดังนั้น char จึงไม่แน่นอน String Integer และ int ไม่เปลี่ยนรูป


1
คำตอบนี้ไม่ได้ให้ข้อมูลใด ๆ จากผู้อื่น
Giulio Caccin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.