在Java中更改非静态字段之前,它们是静态的吗?

时间:2015-03-28 12:03:03

标签: java memory reference

让我们考虑以下代码:

public class Testing {
    static int i = 47;

    public static void main(String[] args) {
        Testing t1 = new Testing();
        Testing t2 = new Testing();
        System.out.println(t1.i == t2.i);

我正在创建一个属于Testing类的静态字段,该字段也在该类t1t2的两个实例之间共享。然后我测试它们是否在内存中引用相同的值,实际上,它们确实如此,结果是真的。这对我来说很清楚。

但是,如果我从int i的声明中删除static关键字,则会发生意外情况。

public class Testing {
    int i = 47;

    public static void main(String[] args) {
        Testing t1 = new Testing();
        Testing t2 = new Testing();
        System.out.println(t1.i == t2.i);

我希望两个实例t1t2都有47作为其字段的值,但它们的字段位于不同的内存地址中。但令人惊讶的是,在t1.i == t2.i的测试中,我在这种情况下也是如此 - 为什么?字段int i = 47;不再是静态的,所以我希望它对于类的每个实例都在不同的内存地址中,但是相等的结果为真。

4 个答案:

答案 0 :(得分:6)

int是基本类型,而不是引用类型。条件t1.i == t2.i不测试引用相等性 - 这里首先没有引用。它只是比较值,在这种情况下,两者都具有值47

如果您的成员字段不是原始字段,则结果会有所不同,例如:

public class Testing {
    Integer i = new Integer(47);

    public static void main(String[] args) {
        Testing t1 = new Testing();
        Testing t2 = new Testing();
        System.out.println(t1.i == t2.i);   // false
    }
}

在这种情况下,每个实例对使用调用构造函数的Integer关键字创建的new对象具有不同的引用,条件t1.i == t2.i比较这两个引用。

答案 1 :(得分:2)

int的标识与对象的标识不同。

对象身份源自同一个对象, 原始类型标识是从identic值派生的(47 == 47)。

如果您将代码更改为Integer i = 47;,则==不会取消整理整数,但会比较对象引用,结果将为false。

如果如图所示初始化Integer(使用文字47),则自动装箱将从内部缓存中选择Integer对象。在身份比较(==)上,结果为真。如果使用Integer i = new Integer(47)`设置至少一个Integers,则比较将失败,因为您现在已经创建了一个新对象。如果超出缓存范围,使用带符号字节范围之外的文字初始化整数,则同样适用。然后运行时将为文字创建一个新的Integer,而identity compare将返回false。

我认为这是拆箱的一个不好的警告,如果你真的想要比较整数的引用而不是值,那么很难找到问题。 我使用以下代码来说明不同的行为。我现在认为简单地使用int literals实例化Integer并依赖自动装箱是不好的习惯。这将对运行时行为产生微妙的变化。此外,如果您处理对象,请在保存侧并与.equals()进行比较。

public class Test {
    public static void main(String...args) {
        Integer h = new Integer(47); // Object created w/o boxing
        Integer i = new Integer(47);
        Integer j = 47; // Object created with boxing
        Integer k = 47; // due to  caching, this is the same Integer

        Integer j2 = 247; // Object created with boxing
        Integer k2 = 247; // no caching, these are different Integers

        int l = 47;  // primitives 
        int m = 47;

        // compare two explicit Objects 
        System.out.println((h == i) ? "true" : "false"); // false

        // compare one explicit Object with a autoboxed Object
        // compare is reference compare
        System.out.println((h == j) ? "true" : "false"); // false
        System.out.println((j == h) ? "true" : "false"); // false

        // compare two autoboxed Objects, compare is by reference
        // because value was in cache range, the Integers are identical
        System.out.println((k == j) ? "true" : "false"); // true 

        // compare two autoboxed Objects, compare is by reference
        // because value was not in cache range, these are two Objects of type Integer
        System.out.println((k2 == j2) ? "true" : "false"); // false 

        // adding a primitive to the compare will
        // always compare by value
        System.out.println((i == l) ? "true" : "false"); // true
        System.out.println((m == l) ? "true" : "false"); // true
    }
}

更新:我将评论考虑在内并更新了我的示例,以包含intValue()的意外缓存。

答案 2 :(得分:2)

即使您创建了2个实例,对于==测试仅适用于原生int的原因,已有很好的答案。

我只是想指出一些奇怪的东西,编译器可以在场景后面做些可以产生违反直觉的结果。考虑以下测试:

public class Foo {
    final Integer i = 47;
    final Integer j = 1234;
    public static void main(String args[]) {
        Foo p = new Foo();
        Foo q = new Foo();
        System.out.println(p.i.equals(q.i));
        System.out.println(p.i == q.i);
        System.out.println(p.j.equals(q.j));
        System.out.println(p.j == q.j);
    }
}

你期待

  • 要么true, true, true, true,因为编译器很聪明地识别出即使有Foo的2个实例,它们也具有ij的相同值。

  • true, false, true, false,因为ijInteger的不同实例,==比较参考值不应该是一样的。

但令人惊讶的是你真的得到了true, true, true, falseij之间的区别是什么?

好吧,如果你用javap -verbose Foo.class查看生成的代码,你会看到:

  stack=2, locals=1, args_size=1
     0: aload_0
     1: invokespecial #11                 // Method java/lang/Object."<init>":()V
     4: aload_0
     5: bipush        47
     7: invokestatic  #13                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    10: putfield      #19                 // Field i:Ljava/lang/Integer;
    13: aload_0
    14: sipush        1234
    17: invokestatic  #13                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    20: putfield      #21                 // Field j:Ljava/lang/Integer;
    23: return

编译器生成了使用Integer#valueOf(int)的代码。该文件指出:

  

此方法始终缓存值的范围为-128到127(含),可以缓存此范围之外的其他值

这解释了为什么Foo的2个实例实际上共享Integer的{​​{1}}个对象,而不是47。{/ p>

道德是:在比较对象时要小心1234。在大多数情况下,您想要和需要的是==

答案 3 :(得分:1)

static所做的就是告诉解释器对象/变量仅包含在该类中,而无需在使用之前定义该类的实例(Object)。

发生这种情况的原因是因为这两个变量包含相同的值,因为它们都是值int的{​​{1}},因此,比较它们会显示它们完全相同。

如果你想比较两个非原始类的相等,你可以使用它:

47

否则,如果你想比较两个对象/变量的,你可以通过测试它来做到这一点:

if(t1.equals(t2)){
...
}

或者,如果您想检查它是否是另一个类的实例(扩展或继承它),您可以使用:

if(t1.getClass() == t2.getClass()){
...
}