【问题标题】:modifying the value of a non static variable which was initialized with a static variable modifies the static variable as well修改用静态变量初始化的非静态变量的值也会修改静态变量
【发布时间】:2019-03-15 21:30:15
【问题描述】:

我需要帮助!我正在尝试从 android 项目中的另一个类中的一个类访问静态变量的值。这是静态类...

public class NamesStore {

  private static NamesStore sNamesStore ;
  private static List<Name> sNames = new ArrayList<>();

  public static NamesStore getInstance() {
    if (sNamesStore == null) {
      return new NamesStore();
    } else {
      return sNamesStore;
    }
  }

  public List<Name> getNames(){
   return sNames;
  }

}

现在在其他类中,我尝试让静态变量使用该值,但在使用之前对其进行修改...

public class Utils{

  public static removeTheseNamesFromTheGeneralNames(List<Name> namesToBeRemoved){
      List<Name> names = NamesStore.getInstance().getNames();
      names.removeAll(namesToBeRemoved);
      return names;
  }

}

当我调用removeTheseNamesFromTheGeneralNames方法时,NamesStore中的静态变量也被修改了,我不知道为什么。?这是静态场的正常行为吗?如果是这样,请问如何在不更改静态字段中存储的值的情况下复制静态字段的值并进行修改。?我可以将其设为静态和最终解决此问题,但我还需要不时使用 setter 方法设置该静态变量。我在这里先向您的帮助表示感谢。

【问题讨论】:

  • 变量没有被修改。你在做梦。变量引用object被修改了,但那是因为你修改了它。你还期待什么?

标签: java android static


【解决方案1】:

'static' 并不是这里真正的核心问题(尽管它可能有所贡献)。问题在于方法getNames() 以及调用者如何使用结果。

在您的 NamesStore 类中,有一个名称列表(ArrayList),并且有一个对该名称列表的引用(sNames)。

当您执行return sNames 并且调用者将返回值分配给他的变量names 时,您仍然只有一个名称列表(一个ArrayList),现在有两个对同一个列表的引用。这不是“静态”的结果,而是 Java 如何使用引用类型的结果;简而言之,赋值(并返回一个值)会复制引用,它不会复制被引用事物的另一个实例。

当然,当names 引用的列表被修改时,它确实会修改唯一存在的名称列表。


如果您希望允许调用者修改您的getNames() 方法返回的列表而不修改您的列表,那么您需要返回您的列表的副本,而不是您的列表本身。

return new ArrayList&lt;&gt;(sNames) 可以做到这一点;但请注意,只有名称列表被复制,列表中的任何内容(可能是“名称”)都不是。换句话说,如果您的调用者修改了一个实际名称,它将修改该名称的唯一实例。您需要确定您的要求是什么。

另一种方法是禁止您的用户更改返回的列表;可以实现为return Collections.unmodifiableList(sNames)。如果尝试更改该列表,则执行该更改的代码将失败。任何想要修改列表(如您的示例)的人都必须先复制一份。

这两者之间的决定可能是基于考虑有多少getNames()的用户希望修改结果。如果这不是通常的情况,那么让这些用户进行复制通常是更好的方法。

【讨论】:

  • 感谢您的回复。你说的对。这也是我所知道的。我仍在思考异常行为的根本原因。我提供的代码只是项目的一小部分,也许某处发生了一些事情。
  • @N.Hubert 这里没有“异常行为”。 Java 正在按设计工作。问题是你。您假设对象是按值传递的。不是。
  • 正如@user207421 所说,这是正常的。我在我的回答中添加了一些关于如何编写代码以避免您遇到的问题的建议。
  • @another-dave。非常感谢你!我终于明白你在解释什么了。
  • @user207421 谢谢!哈哈,我猜我现在已经停止做梦了。我现在明白了。
【解决方案2】:

我认为最好先阅读有关班级成员的信息,以了解其工作原理。 https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html

我的建议是以这种方式重构代码:

public class NamesStore {

    private static NamesStore sNamesStore = new NamesStore();
    private List<Name> sNames = new ArrayList<>();

    private NamesStore(){}

    public static NamesStore getInstance() {
        return sNamesStore;
    }

    public List<Name> getNames() {
        return Collections.unmodifiableList(sNames);
    }

    public List<Name> getNamesWithout(List<Name> namesToBeRemoved) {
        return sNames.stream().filter(name -> !namesToBeRemoved.contains(name)).collect(Collectors.toList());
    }

}

关于过滤方法的说明

getNamesWithout(List namesToBeRemoved)方法也可以这样写:

public List<Name> getNamesWithout(List<Name> namesToBeRemoved) {
    return sNames.stream().filter(new Predicate<Name>() {
        @Override
        public boolean test(Name name) {
            return !namesToBeRemoved.contains(name);
        }
    }).collect(Collectors.toList());
}

接口 Predicate 有一个抽象方法(一个你必须在子类中实现的方法)。在 Java 中,具有单个抽象方法的接口称为功能接口。当你必须实现一个功能接口时,你可以使用lambda expression。这不是什么意思:您可以省略样板代码并仅保留重要的内容。在下图中,重要的代码用绿色标记,样板代码用红色标记。

我希望我的解释能够让您从 lambda 表达式中阐明该 name 的含义。

【讨论】:

  • 感谢您的回答。请问先生,getNamesWithout 方法中的“名称”是什么?
  • @N.Hubert 关于“名字”,请看我上面的解释。
  • 非常感谢我从您的回复中学到了一些新东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-13
  • 2020-12-09
  • 2021-10-28
  • 2011-08-22
  • 2010-12-22
  • 1970-01-01
相关资源
最近更新 更多