为什么列表的大小在赋值为null后不变?

rbl8hiat  于 2021-06-27  发布在  Java
关注(0)|答案(1)|浏览(389)

当一个电话打给 someVoid() 方法如果我没有错的话就把引用的列表传递给它。代码相加后,2值进入列表,大小相应变为2。这里的一切对我来说都是可以理解的(如果我犯了错误,请纠正我)。之后 someVoid() 方法将列表的引用更改为 null . 问题是:在这些操作之后,为什么列表的大小仍然是2?提前谢谢。

public class Test {
          List<Integer> list;

          public Test() {
            this.list = new ArrayList<>();
            someVoid(list);
          }

          private void someVoid(List<Integer> list) {
            list.add(0);
            list.add(1);
            list = null;
          }

          public static void main(String[] args) {
            Test test = new Test();
            System.out.println("Size is: " + test.list.size());
          }
}
zwghvu4y

zwghvu4y1#

对同一列表对象的两个引用

这个 list 内部变量 someVoid 方法是一个局部变量,即参数的名称。那个地方 list 是与类成员字段同时命名的单独的不同变量 list . 所以当你说 list = null ; ,您正在清除该局部变量。您没有清除成员字段 list . 这个 Test 名为的对象 test 仍然携带自己对列表的引用(指针)。

这里有一些经验教训:
注意你的名字。在一段代码的不同上下文中重复相同的名称可能会导致混淆。在模棱两可的情况下,可以考虑附加 Arg 以你的论点的名义。例如: listArg . 但最好使用语义名称,在每个上下文中都有最清晰的含义。
说到命名, Test 在java中,类的名称选择不当。单元测试是很常见的,这样的名称最好是分散注意力,最坏是令人困惑。
标记你的论点 final ,这应该是java原始设计中的默认值。改变已通过的论点是不好的做法,完全没有必要。将方法签名更改为 private void someVoid ( final List < Integer > list ) 提示编译器在该行发出警报 list = null; 告诉您正在更改传递的参数变量。这样的更改如果被标记则是非法的 final .
在模棱两可的情况下,使用 this. 语法。例如, this.list.add(0); . 在过去,我用 this. 在我所有的代码上。使用 this. 由于现代ide的着色特性,现在不太需要了。
传递基本体( int , float , boolean ,等)传递该变量内容的副本。在java中传递一个对象实际上是向一个对象传递一个引用的副本,而不是对象本身。您的代码有两个引用相同 List 对象,一个引用保存在名为 list 另一个同样的例子 List 对象位于局部变量中 list 在你的方法之内。

示例代码

让我们修改代码以实现您的目标:在方法中,将元素添加到存储在类成员字段上的列表中,然后完全消除该列表。这项工作毫无意义,但作为一个示范工程。

package work.basil.example;

import java.util.ArrayList;
import java.util.List;

public class Lister
{
    List < Integer > numbers;

    public Lister ( )
    {
        this.numbers = new ArrayList <>();
        this.sillyMethodToAddToListAndThenEliminateList();
    }

    private void sillyMethodToAddToListAndThenEliminateList ( )
    {
        this.numbers.add( 0 );
        this.numbers.add( 1 );
        this.numbers = null;   // Clearing the *local* reference to the `List` object in memory. The class member field continues to point to the `List` object. 
    }

    public static void main ( String[] args )
    {
        Lister app = new Lister();
        System.out.println( "Size is: " + app.numbers.size() );
    }
}

运行时,我们得到一个空指针异常。这是有意义的,因为我们删除了示例化的列表。所以在 println ,类成员字段未引用任何对象,未引用任何内容 null .
线程“main”java.lang.nullpointerexception中出现异常:无法调用“java.util.list.size()”,因为“app.numbers”在work.basil.example.lister.main(lister)中为null。java:26)

浅拷贝

在实际工作中,当我们交出一份藏品时,我们常常想要交出一份浅薄的藏品。一些对象在里面(实际上,对相同对象的引用在里面)。但是如果用户更改了集合,添加/删除/排序,它们就不会干扰我们原来的集合。
同样地,当你收到一个集合时,你可能想要做一个浅拷贝。这是在这样的情况下调用程序员没有想到传递一个副本,而不是原来的。我想说的没错,有些人会争辩说,调用程序的程序员有责任把他们的数据弄清楚。被调用方法的程序员不必预测调用程序员的失败点。但你可能会赢。所以我展示了调用和被调用的程序员制作防御副本。另外,在本例中,调用的程序员传递一个不可修改的列表,因此被调用的程序员必须复制一个列表才能添加元素。
提示: List.of 以及 List.copyOf 制作不可修改的列表。

package work.basil.example;

import java.util.ArrayList;
import java.util.List;

public class Lister
{
    List < Integer > numbers;

    public Lister ( )
    {
        this.numbers = new ArrayList <>();
        this.numbers.add( 42 );
        this.sendReport( List.copyOf( this.numbers ) );  // Share a shallow copy of collection.
    }

    private void sendReport ( final List < Integer > fodderForReport )
    {
        List < Integer > data = new ArrayList <>( fodderForReport ); // Make defensive and modifiable copy of passed collection.
        data.add( 0 );
        data.add( 1 );
        String report = data.toString();
        System.out.println( "report = " + report );
        //  … send report
    }

    public static void main ( String[] args )
    {
        Lister app = new Lister();
        System.out.println( "Size is: " + app.numbers.size() + " | " + app.numbers );
    }
}

运行时:
报告=[42,0,1]
尺寸为:1 |[42]

相关问题