为什么在将列表分配给 null 后列表的大小没有改变?
Why size of list don't change after assigning list to null?
当 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());
}
}
对同一 List
对象的两次引用
list
变量里面你的someVoid
方法是一个局部变量,参数的名字。局部 list
是一个独立的不同变量,与 class 成员字段巧合地命名为 list
。因此,当您说 list = null ;
时,您正在清除该局部变量。您没有清除成员字段 list
。名为 test
的 Test
对象仍然带有它自己对列表的引用(指针)。
这里有几点值得学习:
- 注意你的命名。在一段代码中的不同上下文中重复相同的名称可能会导致混淆。在模棱两可的情况下,考虑在参数名称后附加
Arg
之类的内容。例如:listArg
。但最好使用语义名称,在每个上下文中具有最明确的含义。
- 说到命名,
Test
是 Java 中 class 的一个糟糕的名字选择。单元测试很常见,这样的名称充其量会分散注意力,最坏的情况会令人困惑。
- 标记你的参数
final
,这应该是Java最初设计中的默认值。更改传递的参数是不好的做法,完全没有必要。将该方法签名更改为 private void someVoid ( final List < Integer > list )
会提示编译器警告行 list = null;
告诉您您正在更改传递的参数变量。如果标记为 final
. ,则此类更改是非法的
- 在不明确的情况下,使用
this.
语法。例如,this.list.add(0);
。在过去,为了清晰起见,我在所有代码中都使用了 this.
。由于现代 IDEs. 中的着色功能,现在使用 this.
的必要性降低了
- 在 Java 中传递原语(
int
、float
、boolean
等)会传递该变量内容的副本。在 Java 中传递对象实际上是将 reference 的副本传递给对象,而不是对象本身。您的代码有 两个对同一个 List
对象的引用 ,一个引用保存在名为 list
的 class 成员变量中,另一个引用非常相同的 List
对象在您的方法中的局部变量 list
中。
示例代码
让我们修改您的代码以实现您的目标:在一个方法中,将元素添加到存储在 class 成员字段中的列表中,然后完全删除该列表。这项工作没有任何意义,但可以作为演示。
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
处,class成员字段没有引用对象,没有引用,被认为是null
.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "app.numbers" is null
at work.basil.example.Lister.main(Lister.java:26)
浅拷贝
在实际工作中,当传递一个集合时,我们通常希望传递该集合的一个浅表副本。内部的一些对象(实际上,对相同对象的引用在内部)。但是如果用户更改集合,adding/deleting/sorting,他们不会打扰我们原来的集合。
同样,当你收到一个集合时,你可能想要做一个浅拷贝。这是为了防止调用程序员不考虑传递副本而不是原始文件。有些人会争辩说,正确地我会说,调用程序员有责任直接获取他们的数据。被调用方法的程序员不必预测调用程序员的失败点。但你的实用性可能会胜出。所以我展示了调用和调用的程序员制作 defensive 副本。另外,在这个例子中,调用程序员传递了一个不可修改的列表,所以被调用的程序员必须制作一个副本才能添加元素。
提示:List.of
和 List.copyOf
make non-modifiable 列表。
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 );
}
}
当运行:
report = [42, 0, 1]
Size is: 1 | [42]
当 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());
}
}
对同一 List
对象的两次引用
list
变量里面你的someVoid
方法是一个局部变量,参数的名字。局部 list
是一个独立的不同变量,与 class 成员字段巧合地命名为 list
。因此,当您说 list = null ;
时,您正在清除该局部变量。您没有清除成员字段 list
。名为 test
的 Test
对象仍然带有它自己对列表的引用(指针)。
这里有几点值得学习:
- 注意你的命名。在一段代码中的不同上下文中重复相同的名称可能会导致混淆。在模棱两可的情况下,考虑在参数名称后附加
Arg
之类的内容。例如:listArg
。但最好使用语义名称,在每个上下文中具有最明确的含义。- 说到命名,
Test
是 Java 中 class 的一个糟糕的名字选择。单元测试很常见,这样的名称充其量会分散注意力,最坏的情况会令人困惑。
- 说到命名,
- 标记你的参数
final
,这应该是Java最初设计中的默认值。更改传递的参数是不好的做法,完全没有必要。将该方法签名更改为private void someVoid ( final List < Integer > list )
会提示编译器警告行list = null;
告诉您您正在更改传递的参数变量。如果标记为final
. ,则此类更改是非法的
- 在不明确的情况下,使用
this.
语法。例如,this.list.add(0);
。在过去,为了清晰起见,我在所有代码中都使用了this.
。由于现代 IDEs. 中的着色功能,现在使用 - 在 Java 中传递原语(
int
、float
、boolean
等)会传递该变量内容的副本。在 Java 中传递对象实际上是将 reference 的副本传递给对象,而不是对象本身。您的代码有 两个对同一个List
对象的引用 ,一个引用保存在名为list
的 class 成员变量中,另一个引用非常相同的List
对象在您的方法中的局部变量list
中。
this.
的必要性降低了
示例代码
让我们修改您的代码以实现您的目标:在一个方法中,将元素添加到存储在 class 成员字段中的列表中,然后完全删除该列表。这项工作没有任何意义,但可以作为演示。
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
处,class成员字段没有引用对象,没有引用,被认为是null
.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "app.numbers" is null at work.basil.example.Lister.main(Lister.java:26)
浅拷贝
在实际工作中,当传递一个集合时,我们通常希望传递该集合的一个浅表副本。内部的一些对象(实际上,对相同对象的引用在内部)。但是如果用户更改集合,adding/deleting/sorting,他们不会打扰我们原来的集合。
同样,当你收到一个集合时,你可能想要做一个浅拷贝。这是为了防止调用程序员不考虑传递副本而不是原始文件。有些人会争辩说,正确地我会说,调用程序员有责任直接获取他们的数据。被调用方法的程序员不必预测调用程序员的失败点。但你的实用性可能会胜出。所以我展示了调用和调用的程序员制作 defensive 副本。另外,在这个例子中,调用程序员传递了一个不可修改的列表,所以被调用的程序员必须制作一个副本才能添加元素。
提示:List.of
和 List.copyOf
make non-modifiable 列表。
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 );
}
}
当运行:
report = [42, 0, 1]
Size is: 1 | [42]