整数是不可变的吗

Is Integer Immutable

提问人:K.Steff 提问时间:4/6/2011 最后编辑:James P.K.Steff 更新时间:2/23/2021 访问量:82580

问:

我知道这可能非常愚蠢,但很多地方声称 Java 中的 Integer 类是不可变的,但以下代码:

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

毫无问题地执行,给出(预期的)结果 6.因此,实际上 a 的值已经发生了变化。这不意味着 Integer 是可变的吗? 第二个问题和一点题外话:“不可变类不需要复制构造函数”。有人愿意解释为什么吗?

爪哇岛 不变性 可变

评论

13赞 wkl 4/6/2011
这个类是不可变的,但自动装箱正在让时髦的事情发生:stackoverflow.com/questions/3085332/......
0赞 K.Steff 4/6/2011
谢谢,拳击是我需要谷歌搜索的关键词:)
9赞 Code Enthusiastic 8/14/2013
您将不可变值与最终值或常量值混淆。

答:

59赞 Mark Elliot 4/6/2011 #1

a是对某个 Integer(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.

评论

0赞 j3141592653589793238 12/20/2021
这个答案很有帮助。因此,可以将最后一个关键字视为在 C 中标记指针常量。
5赞 uncaught_exceptions 4/6/2011 #2

不可变并不意味着不能更改变量的值。它只是意味着任何新的赋值都会创建一个新对象(为其分配新的内存位置),然后将值分配给它。

要自己理解这一点,请在循环中执行整数赋值(在循环外声明整数)并查看内存中的活动对象。

不可变对象不需要复制构造函数的原因是简单的常识。由于每个赋值都会创建一个新对象,因此从技术上讲,该语言已经创建了一个副本,因此您不必创建另一个副本。

101赞 Travis Webb 4/6/2011 #3

不可变并不意味着它永远不能等于另一个值。例如,也是不可变的,但我仍然可以这样做:aString

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

str没有被改变,而是现在是一个全新的实例化对象,就像你一样。所以 的值没有改变,而是被一个全新的对象所取代,即 .strIntegeranew Integer(6)

评论

17赞 Sandman 4/6/2011
“这是因为 str 现在是一个全新的实例化对象”。或者,更确切地说,str(变量)指向一个新对象。对象本身是不可变的,但由于变量不是最终变量,因此它可以指向不同的对象。
0赞 Travis Webb 4/6/2011
是的,它指向由于操作而实例化的不同对象。+=
11赞 Stephen C 5/1/2012
严格来说,它不一定是一个新对象。装箱使用,该方法维护对象的缓存。因此,变量的结果可能是以前存在的对象(或者它甚至可以是同一个对象......在 的情况下)。Integer.valueOf(int)Integer+=Integera += 0
3赞 cellepo 5/18/2017
为什么 JavaDoc for String 明确说它是不可变的,而 JavaDoc for Integer 却没有?这种差异就是我阅读这个问题的原因......
9赞 Andy 4/6/2011 #4

是的,整数是不可变的。

A 是指向对象的引用。当您运行 += 3 时,它会重新分配 A 以引用具有不同值的新 Integer 对象。

您从未修改过原始对象,而是将引用指向其他对象。

在此处了解对象和引用之间的区别。

评论

0赞 Roshan Fernando 3/18/2019
用外行的语言简单易行地说,所有其他复杂的解释都在那里:)
2赞 Stephen C 4/6/2011 #5

“不可变类不需要复制构造函数”。有人愿意解释为什么吗?

原因是很少需要复制(甚至复制)不可变类的实例。对象的副本应与原始对象“相同”,如果相同,则无需创建它。

不过,有一些基本假设:

  • 它假定应用程序不会对类实例的对象标识赋予任何意义。

  • 它假定该类已重载,因此实例的副本将与原始副本“相同”。根据这些方法。equalshashCode

这些假设中的任何一个或两个都可能是错误的,这可能需要添加一个复制构造函数。

23赞 Peter Lawrey 4/6/2011 #6

您可以使用以下方法确定对象已更改(更好的方法是使用普通值,但是引用而不是值已更改并不那么明显)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)));

prints

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

You can see the underlying "id" of the object refers to has changed.a

评论

2赞 Ad Infinitum 3/24/2017
System.identityHashCode() 是一个非常好的提示。谢谢你。
11赞 Harsha 9/9/2012 #7

对于最初提出的问题,

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

整数是不可变的,所以上面发生的事情是“a”已更改为值为 6 的新引用。初始值 3 在内存中没有引用(未更改),因此可以对其进行垃圾回收。

如果这种情况发生在 String 上,它将在池中(在 PermGen 空间中)保留的时间比 Integers 长,因为它希望有引用。

0赞 Ashutosh Nigam 4/4/2016 #8

我可以通过简单的示例代码明确指出 Integer(以及它的其他信条,如 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");
    }

}

实际结果

结果是他 嗨,有 100 而不是预期结果(如果 sb 和 i 都是可变对象) 嗨,有 1000

这表明 main 中 i 创建的对象未被修改,而 sb 被修改。

因此,StringBuilder 演示了可变行为,但演示了 Integer。

所以整数是不可变的。因此证明

另一个没有 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;
    }


}

评论

0赞 MT0 4/4/2016
您正在做两件不同的事情 - 尝试重新分配整数和在 stringbuilder 上调用方法。如果你这样做了,那么是不变的。private void doStringBuilder(StringBuilder sb){ sb = new StringBuilder(); }sb
0赞 Ashutosh Nigam 4/4/2016
我添加了 StringBuilder(可变的)以将 Integer 与另一个可变对象并列。如果需要,您可以删除所有与 StringBuilder 相关的代码,然后打印出来 i 即可查看 100。
0赞 MT0 4/4/2016
这并不能证明不可变性 - 您所做的只是重新散列此示例,以证明 Java 使用逐个值传递(并且为对象传递的值是指针)。
0赞 MT0 4/4/2016
尝试private void doInteger(Integer i){ System.out.println( i == 100 ); i=1000; System.out.println( i == 100 ); }
0赞 Ashutosh Nigam 4/4/2016
@MT0 当您传递值时,StringBuilder 仍指向同一对象,但 Integer 传递的是新副本,而不是对同一对象的引用。如果在 doInteger 中打印输出,则显示的是函数拥有的副本,而不是 main 函数。我们想看看 main 中 i 指向的对象是否相同。希望它能清除概念:)此外,StringBuilder 的不可变版本是 String。如果您希望我分享示例,请告诉我。
3赞 Ndheti 10/14/2016 #9

这就是我对不可变的理解

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赞 özgür arslan 10/9/2017 #10
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
}

Output is :

Hi Bye 1000 5000 1000 5000 d a

So char is mutable , String Integer and int are immutable.

评论

1赞 Giulio Caccin 10/9/2017
此答案不提供其他答案的任何信息。
0赞 sree vishnu 11/20/2020 #11

复制并运行此代码,希望这能解答您的所有疑问

private static void wrapperClassDemo() {
    //checking wrapper class immutability using valueOf method
    //We can create wrapper class by using either "new" keyword or using a static method "valueOf()"
    //The below Example clarifies the immutability concept of wrapper class in detail
    //For better understanding just ciopy the below code to the editor and run
        
    Integer num1 =Integer.valueOf(34);  // i'm passing the 34 as the parameter to the valueOf method
    System.out.println("value assigned to num1 is : "+num1);
    System.out.println("Printing the hashcode assigned to store the \" num1 \"value in memory: "+System.identityHashCode(num1));
        
    Integer num2 =Integer.valueOf(34);
    System.out.println("value assigned to num2 is : "+num2);
    System.out.println("Printing the hashcode assigned to store the \" num2 \"value in memory: "+System.identityHashCode(num2));
        
    /*Now u can notice both the hashcode value of num1 and num2 are same. that is because once you created the num1 with the value 34 an object is 
     * created in the heap memory. And now You are passing the value  same as num1 to the num2 .Now JVM Checks the same value is present in the heap Mmeomry
     * If present the reference variable(in this example it is num2) will  be pointed to the same address where the object num1 is stored so u get the same hashcode         */
         
        
    num2++; // You can use num2 = 35 as both are same; 
    System.out.println("\nvalue assigned to num2 is : "+num2);
    System.out.println("Printing the hashcode of  \" num1 \": "+System.identityHashCode(num1) + "\nPrinting the hashcode of  \" num2 \": "+System.identityHashCode(num2));
    System.out.println("As now you can notice the hashcode has changed for num2 ,That is because now a new object is created for num2 and it is referencing the new object");
        
    //Again i'm incrementing num2
    System.out.println("\nBefore incremeting  the hashcode of  \" num2 \" is: "+System.identityHashCode(num2));
    num2++; // You can use num2 = 36 as both are same;
    System.out.println("After incremeting  the hashcode of  \" num2 \" is: "+System.identityHashCode(num2));
    //now the hashcode value of num2 is changed ,again new object is created for the updated value and num2 is referencing new object ,and old object will be garbage collected
        
    System.out.println("\n Thus the CONCLUSION is Wrapper objects are immutable ,They only create new object and refernce the new object ,They won't modify the present object ");
    System.out.println("This is applicable for Strings also");
0赞 Olav Holten 2/23/2021 #12

需要记住的是,现在有一个整数值的缓存。它有时可以使开发人员免于在从 int 更改为 Integer 时使用 == 而不是 .equals() 的错误。但并非总是如此。当您实例化一个新的 Integer 时,将创建一个新实例。因此,整数不仅是不可变的,而且是半静态的。

    Integer a = 3;
    Integer b = 3;
    Integer c = new Integer(3);
    b = b + 1;
    b = b - 1;

    System.out.println("a-id: " + System.identityHashCode(a));
    System.out.println("b-id: " + System.identityHashCode(b));
    System.out.println("c-id: " + System.identityHashCode(c));
    System.out.println("a == b: " + (a == b));
    System.out.println("a == c: " + (a == c));
    System.out.println("a eq c: " + (a.equals(c)));    

给出打印输出:

a-id: 666988784
b-id: 666988784
c-id: 1414644648
a == b: true
a == c: false
a eq c: true