3

假设我们想要制作一个你必须收集宝石的游戏。所以我们需要一个 Gem 类,一个 GemSpawner 类,当然还有 MainActivity 类。

public class MainActivity {
    public static void main(String[] args) {

        List<Gem> gems = new ArrayList<Gem>();

        GemSpawner gs = new GemSpawner(gems);

        //...
    }
}

在这种情况下,我们用 gems 创建了一个 List,并通过它的构造函数将它传递给 GemSpawner,因此 gs 可以使用以下命令将 gems 添加到列表中:

gems.add(new Gem(10, 50, "red")); //should represent Xpos, Ypos, and color.

但这不是更好吗:

public class MainActivity {

    public static List<Gem> gems = new ArrayList<Gem>();

    public static void main(String[] args) {

        GemSpawner gs = new GemSpawner();

        //...
    }
}

现在,GemSpawner (gs) 可以使用以下方法添加宝石:

MainActivity.gems.add(new Gem(10, 50, "red"));

一个朋友只给我展示和解释了上面的方法,但是下面的方法不是更有效吗?

4

5 回答 5

7

效率论点站不住脚。它是一个单一的引用,因此 32 位(如果您在 64 位机器上,则为 64 位)在开始时被解析一次,因此它没有任何(可测量的)性能影响。

但是,如果您使用大量静态变量,您的代码设计可能会变得混乱。例如,通过将其设为静态,就没有简单的方法来跟踪向您的列表添加的内容(可能是深夜来测试您随机添加宝石然后忘记的东西。几个月后,您无法锻炼为什么这颗宝石不断出现!)

这似乎很愚蠢,但通过限制对事物的访问确实可以帮助调试。如果您知道唯一可以添加 gem 的是GemSpawner,那么您已将基于 gem 的潜在错误隔离到一个类中。如果它可能来自任何地方,那么调试会变得更加困难,尤其是当您的项目变得越来越复杂时。

于 2013-10-27T12:07:59.340 回答
5

静态数据成员在您的程序中引入了我们所说的全局状态。一般来说,全局状态被认为是坏的有几个很好的理由:

  • 它使读取全局状态的方法的行为不可预测。这有两个效果:
    • 很难推断一个方法的结果取决于它所属的对象和传递给它的参数。
    • 它可以使对该方法的任何正式测试尝试都毫无意义(尽管有一些例外)。
  • 它做出的假设现在可能成立,但以后不会。例如,一个修改全局状态的过程可能会很好,如果它在每个程序运行时严格运行一次。但稍后您可能会多次运行该过程。在这种情况下:
    • 按顺序,后续迭代必须注意“使用”状态。
    • 与此同时,您将以一种难以防范的方式打开数据竞争和线程不安全的大门。

另一方面,如果您将所需状态作为参数传递给每个方法,那么您正在创建一个安全网,自动使上述所有情况发生的可能性大大降低,并威胁您程序的正确性。

我怀疑选择全局状态而不是通过状态是否有显着的性能优势(或任何好处)。而且,如果您确实最终遇到了过多的全局状态验证,或者更糟糕的是,引入了锁和障碍(这两种情况都非常昂贵),那么您将取消您本来可以获得的任何好处。

坚持不引入全局状态的良好做法更值得。

于 2013-10-27T12:11:08.790 回答
4

public static变量本质上是一个全局变量。

在经验丰富的程序员中,全局变量被认为是非常糟糕的编程风格。

这是一篇文章,描述了它们不好的一些原因

面向对象的解决方案是将您的 gems 列表封装为 GemManager 类中的私有集合,该类公开通过方法对 gems 的任何访问。然后,您将创建 GemManager 的一个实例并将该实例传递给需要它的每个类或方法。

于 2013-10-27T12:11:52.577 回答
3

这是一个比您可能意识到的要复杂得多的问题。

许多开始使用 Java 编写代码的人开始我编写所有内容static正是因为您不必传递引用 - 它使您的代码“更简单”。

但是,当您的代码变得更加复杂时,您就会开始遇到问题。出现这些问题的主要途径有 3 条:

  1. 封装
  2. 抽象
  3. 测试

封装

这是一个Object不应该允许直接访问其成员的想法,应该告诉它“做事”,然后它在内部这样做而不暴露这是如何完成的。

这背后的想法是您尝试避免将类彼此过于紧密地耦合。

这将我们引向下一点

抽象

在 Java 中,这通过abstract类和interfaces.

这个想法是你GemSpawner只是产生宝石的东西的定义。它如何在内部做到这一点实际上与任何人无关,而是它自己的事。

在 Java 中,您无法真正将static方法与继承的关键 OO 思想相协调。

static方法是继承的,但它们被隐藏而不是被覆盖,因此您不能(轻松)修改它们的行为。

这导致我们进入

测试

随着您的程序变得越来越复杂,这个话题会越来越多地出现。

如何测试“Hello World”程序?好吧,你运行它,看看它是否打印“Hello Wrld”——在这种情况下,有一个错误。

一旦程序变得更复杂,您就不能简单地这样做。您需要将程序分开并测试“单元”。称为单元测试

在这里,您的static参考确实开始引起问题。您不能将程序分成离散的单元,因为所有内容都通过直接的类引用联系在一起。而且您不能模拟static方法的行为,因为它们不容易被覆盖。

所以,总结一下。是的; 放在static任何地方而不是传递引用可能会更快,更容易。但是,如果您打算编写像游戏这样复杂的东西,您应该真正考虑使用 Java 来充分发挥它的潜力。

于 2013-10-27T12:17:06.303 回答
0

使用静态变量几乎没有任何好处,但也有缺点:

  • 您无法创建同时运行的游戏的多个副本,甚至无法创建历史副本 - 您已将其锁定为每个 JVM 最多一个副本
  • 您在不需要存在的类之间存在依赖关系 - 请参阅耦合
  • 您可能想从另一个班级开始游戏,但现在不能 - 您已将启动锁定到您的主要班级
  • 您已经让实现“渗出”到另一个类 - 请参阅凝聚力
于 2013-10-27T12:35:13.593 回答