代码之家  ›  专栏  ›  技术社区  ›  Tom Tresansky

默认情况下Java方法应该是静态的吗?

  •  64
  • Tom Tresansky  · 技术社区  · 16 年前

    假设你的写作方法 foo() 在A级。foo从来没有进入过A的任何一个州。你对福的行为一无所知。它可以做任何事。

    foo是否应该始终是静态的,而不考虑任何其他因素?为什么不呢?

    似乎我的类总是在积累许多私有助手方法,因为我分解任务并应用“只写一次”原则。其中大多数都不依赖于对象的状态,但在类自己的方法之外永远不会有用。默认情况下它们应该是静态的吗?最终使用大量内部静态方法是错误的吗?

    23 回复  |  直到 16 年前
        1
  •  50
  •   Community Mohan Dere    6 年前

    为了回答标题上的问题,一般来说,Java方法应该

    不过,你说的有点不同。你特别提到助手方法。

    如果是 辅助方法 只需将值作为参数并返回值,而不访问状态,它们

    不访问状态的助手方法应该是静态的。


    完全明确的 在代码中 该方法不需要知道任何实例状态 .

    如果确保该方法不依赖于外部或全局状态,则它是一个 纯函数 ,即 数学意义

    三。优化优势

    如果方法是静态的并且是纯函数,那么在某些情况下它可能是静态的 memoized

    4字节码级别差异

    为了使本节更容易理解,让我们使用一个示例:

    public class App {
        public static void main(String[] args) {
            WithoutStaticMethods without = new WithoutStaticMethods();
            without.setValue(1);
            without.calculate();
    
            WithStaticMethods with = new WithStaticMethods();
            with.setValue(1);
            with.calculate();
        }
    }
    
    class WithoutStaticMethods {
    
        private int value;
    
        private int helper(int a, int b) {
            return a * b + 1;
        }
    
        public int getValue() {
            return value;
        }
    
        public void setValue(int value) {
            this.value = value;
        }
    
        public int calculate() {
            return helper(value, 2 * value);
        }
    }
    
    class WithStaticMethods {
    
        private int value;
    
        private static int helper(int a, int b) {
            return a * b + 1;
        }
    
        public int getValue() {
            return value;
        }
    
        public void setValue(int value) {
            this.value = value;
        }
    
        public int calculate() {
            return helper(value, 2 * value);
        }
    }
    

    helper(...) 在课堂上 WithoutStaticMethods WithStaticMethods

    没有静态方法

    在第一种情况下,如果没有静态方法,当您调用helper方法时,JVM需要将引用推送到实例以将其传递给 invokespecial calculate()

     0 aload_0
     1 aload_0
     2 getfield #2 <app/WithoutStaticMethods.value>
     5 iconst_2
     6 aload_0
     7 getfield #2 <app/WithoutStaticMethods.value>
    10 imul
    11 invokespecial #3 <app/WithoutStaticMethods.helper>
    14 ireturn
    

    0(或1)处的指令, aload_0 ,将加载对堆栈上实例的引用,稍后将由 特别的 帮助程序(…) 函数,并且它从未被使用,正如我们在这里看到的:

    0 iload_1
    1 iload_2
    2 imul
    3 iconst_1
    4 iadd
    5 ireturn
    

    iload_0 ? 它被不必要地加载了。

    现在,如果声明helper方法static,那么 计算()

     0 aload_0
     1 getfield #2 <app/WithStaticMethods.value>
     4 iconst_2
     5 aload_0
     6 getfield #2 <app/WithStaticMethods.value>
     9 imul
    10 invokestatic #3 <app/WithStaticMethods.helper>
    13 ireturn
    

    区别在于:

    • 少了一个 你好吗 指令
    • invokestatic

    helper函数的代码也有点不同:没有 this 作为第一个参数,所以参数实际上在位置0和1,我们可以在这里看到:

    0 iload_0
    1 iload_1
    2 imul
    3 iconst_1
    4 iadd
    5 ireturn
    

    从代码设计的角度来看,将helper方法声明为static更有意义:代码本身就说明了问题,它包含了更多有用的信息。它声明它不需要实例状态来工作。

    在字节码级别,发生了什么更清楚,没有无用的代码(尽管我相信JIT没有办法对其进行优化,但不会产生显著的性能代价)。

        2
  •  16
  •   Jay    16 年前

    如果一个方法不使用实例数据,那么它应该是静态的。如果函数是公共的,这将大大提高效率,您不需要创建多余的对象实例来调用函数。可能更重要的是自文档的优点:通过声明函数static,您可以电报告诉读者该函数不使用实例数据。

    我不理解这里许多海报的观点,即在Java程序中使用静态函数有问题。如果函数在逻辑上是静态的,则使其成为静态的。Java库有许多静态函数。数学课几乎充满了静态函数。

    如果我需要一个计算平方根的函数,合理的方法是:

    public class MathUtils
    {
      public static float squareRoot(float x)
      {
        ... calculate square root of parameter x ...
        return root;
      }
    }
    

    当然,你可以做一个“更糟糕”的版本,看起来像这样:

    public class MathUtils
    {
      private float x;
      public MathUtils(float x)
      {
        this.x=x;
      }
      public float squareRoot()
      {
        ... calculate square root of this.x ...
        return root;
      }
    }
    

    但是除了尽可能满足使用OOP的抽象目标之外,这还有什么更好的呢?它需要更多的代码行,灵活性较差。

    (是的,我现在在标准的数学课上有一个平方根函数。我只是用这个作为一个方便的例子。)

    如果一个静态函数在逻辑上与一个类相关联,但是可以合理地从外部调用,那么就将其设为公共静态函数。比如,Java的parseInt函数是在Integer类中,因为它必须处理整数,所以这是一个合理的位置。

    另一方面,在编写一个类时,您经常会意识到需要一些静态函数,但函数并没有真正绑定到这个类。这只是您第一次碰巧意识到您需要它,但是它可能会被其他与您现在所做的事情无关的类合理地使用。比如,回到平方根的例子,如果你有一个包含经纬度的“Place”类,你想要一个函数来计算两个地方之间的距离,你需要一个平方根作为计算的一部分(并且假装标准库中没有平方根函数),创建一个单独的平方根函数,而不是将其嵌入到更大的逻辑中,这是非常有意义的。但它不属于你的班级。这将是为“数学实用程序”或类似的东西创建一个单独类的时候。

    我能想到的使它不是静态的唯一原因是如果子类想要重写它。

    我想不出其他原因,但我不排除这种可能性。我不愿意说“在任何情况下都不可能”,因为通常有人会提出一些特殊情况。

        3
  •  9
  •   BlairHippo    16 年前

    有趣的问题。实际上,我不认为上课有什么意义 A 的私有助手方法静态(除非它们与中的可公开访问的静态方法相关) A 任其支配。因为它们是幕后的助手方法,所以没有什么可以说您(或其他同事)最终不会决定这些无状态助手中的一个可能会从了解状态中受益,这可能会导致一些重构麻烦。

    我不认为这是 最终会有大量的内部静态方法,但我也不知道从中得到什么好处。我说默认为非静态,除非你有充分的理由不这样做。

        4
  •  9
  •   user395760 user395760    16 年前

    不,从来没有。静态方法应该是一个例外。OO就是让对象具有围绕对象状态的行为。理想情况下,不应该有任何(或很少)静态方法,因为所有与对象状态无关的东西都可以(并且为了避免引入对象的概念,应该)放在模块级的普通旧函数中。工厂可能例外,因为Complex.fromcortesian(以维基百科为例)读起来非常好。

        5
  •  6
  •   Sean Patrick Floyd    16 年前

    我通常

    根据需要依次执行以下步骤:

    a) 我在一个成员方法中编写了一些代码,发现我可能可以重用其中的一些代码

    提取到非静态方法

    b) 现在我来看看这个方法是否需要访问state,或者我是否可以将它的需要放入一个或两个参数和一个return语句中。如果是后者:

    c) 如果我发现我可以在同一个包的其他类中使用这段代码,我将

    使方法公开并将方法移动到具有默认可见性的包帮助器类

    com.mycompany.foo.bar.phleeem 我会创建一个类 PhleeemHelper PhleeemUtils 具有默认可见性。

    d) 如果我意识到我的应用程序需要这个功能,我

    将helper类移到专用的实用程序包中

    com.mycompany.foo.utils.PhleeemUtils

    一般来说,我喜欢最小可能能见度的概念。那些不需要我的方法的人不应该看到它。这就是为什么我从私有访问开始,转到包访问,并且只在它们位于专用包中时才将其公开。

        6
  •  6
  •   AidenMontgomery    16 年前

    static 类上的方法强制方法本身不能改变对象,因为它缺少对对象的访问 this . 在这方面 静止的 修饰符向程序员提供有关方法意图的信息,即无副作用的信息。

    反静态的纯粹主义者可能希望将它们移除到一个实用类中,而反实用的纯粹主义者肯定反对这个类。但实际上,人为地将这些方法从它们唯一的调用位置移开,除了与新的实用程序类紧密耦合之外,还能实现什么呢。

        7
  •  5
  •   Bill K    16 年前

    我一般不会让它们静止,但可能应该。告诉下一个编码者这个方法不能修改你的对象的状态是很有价值的,当你修改这个方法来访问一个成员你正在改变这个方法的性质时给你一个警告也是很有价值的。

    编码就是与下一个编码者通信——不用担心让代码运行,这很简单。所以,为了最大限度地进行交流,我想说,如果你真的需要这样一个助手,让它静态是一个好主意。保密也很重要,除非你在做数学题。就像上课一样。

        8
  •  5
  •   Recurse    16 年前

    Java将module、namespace、adt和class的概念混为一谈,声称某些面向类的OO纯度会阻止您将Java类用作模块、namespace或adt是荒谬的。

    是的,方法应该是静态的。纯粹的内部支持方法应该是私有的;助手方法受保护;效用函数应该是公共的。此外,静态字段、静态常量和公共静态方法之间存在着巨大的差异。第一个只是“全局变量”的另一个词;而且几乎总是要避免的,即使访问器方法的中介也几乎不能限制损害。第二种方法是将java类视为符号常量的命名空间,完全可以接受。第三种方法是将java类视为函数的一个模块,作为一般规则,应该避免副作用,或者在必要时将副作用限制为传递给函数的任何参数。使用static将有助于确保您不会无意中通过访问对象的成员而破坏它。

    另一种情况是,在用java编写函数代码时,静态方法非常有用。在这一点上,大多数由OO支持者开发的经验法则都已经过时了。您将发现自己的类中充满了静态方法,公共静态函数常量绑定到匿名内部函子。

        9
  •  5
  •   Blessed Geek    16 年前

    我发现很难接受那些避免静态方法的理论。它们是为了促进一个完全卫生的面向对象模型,以防从对象关系中清除任何偏差。我看不出在实践对象导向中有任何反腐败的必要性。

    无论如何,所有java.util.Arrays类都是静态的。数值类Integer、Boolean、String有静态方法。很多静态方法。这些类中的所有静态方法要么转换为它们各自的类实例,要么转换为它们各自的类实例。

    由于GoodOldGosling等人被证明是拥有静态方法的非常有用的榜样,因此没有必要回避它们。我意识到有些人对我的回答感到困惑,他们投票否决了我的回答。许多程序员喜欢将尽可能多的成员转换为静态的,这是有原因和习惯的。

    为什么方法是静态的,应该有一个一致的理由。当方法是静态的时,遵循标准的Java库模式是没有坏处的。

    最重要的是编程效率和质量。在一个适应性和敏捷的开发环境中,不仅要调整项目的粒度以有效地响应需求的变化,还要调整编程环境,比如提供一个一致的编码模型以充分利用您拥有的编程技能集。在一天结束时(一个项目几乎永远不会结束),您希望团队成员高效高效,而不是不管他们是否避免静态方法。

    因此,设计一个编程模型,不管你想要MVP、注入、方面驱动、静态避免/亲和力级别等等,并且知道你为什么想要它们——不是因为一些理论上的疯子告诉你你的编程实践会违反oo原则。请记住,如果你在一个行业工作,它总是质量和盈利能力,而不是理论纯度。

    出于同样的原因,OS/2未能与微软竞争,因为IBM的正交性概念纯粹是基于机器和数据的,IBM非常自豪地宣称他们真正的面向对象性,而微软则是迎合人类视角的错误的面向对象性。他们忘记了我们人类有各自不同的信息正交视角,它们不符合数据和基于机器的正交性,甚至不符合彼此。

    如果您熟悉树的拓扑结构,您会意识到您可以选择任何叶节点并使其成为根节点。或者任何节点,如果你不介意有一个多主干树的话。每个人都认为他/她的节点是根,而事实上任何节点都可能是根。如果你认为你的面向对象的观点是正典,再想想。更重要的是尽量减少被接受为候选根的节点数。

    效率和效率之间需要折衷。拥有一个很难被其他程序员有效使用的高效数据或对象模型是没有意义的。

        10
  •  4
  •   Mchl    16 年前

    如果它不处理这个类的对象,但实际上属于这个类(我会考虑将它移到其他地方),那么它应该是静态的。

        11
  •  3
  •   extraneon    16 年前

    如果可以避免,就不要使用静电。它与继承冲突(重写)。

    另外,不要问,但稍微相关,不要公开实用方法。

    至于其余的,我同意马特b。如果你有一堆潜在的静态方法,这些方法不使用state,就把它们放在一个私有类中,或者可能是protected或package protected类中。

        12
  •  3
  •   stacker    16 年前

    这取决于java.lang.Math没有非静态的方法。 (您可以执行静态导入来编写cos(),而不是Math.cos()) 这不应该被过度使用,但是作为一些打算作为实用程序调用的代码,它是可以接受的。I.g Thread.currentThread()

        13
  •  3
  •   user372743 user372743    16 年前

    静态方法用于标识与从该类创建的对象无关但与该类本身有关的方法(或变量)。例如,您需要一个变量来计算创建的对象数。您可以放置类似这样的内容:“private static int instances=0;”然后在该类的构造函数中放入一些递增“instances”的内容,这样您就可以计算它了。

        14
  •  1
  •   Jim Ferrans    16 年前

    在创建静态方法之前一定要仔细考虑,但有时它们是一个好的解决方案。

    joshuabloch在中的“第1项:考虑静态工厂方法而不是构造函数” Effective Java 这是一个非常有说服力的案例,说明静态方法是非常有益的。他以java.util.Collections类的32个静态工厂方法为例。

    在一个例子中,我有一个POJO类的层次结构,这些类的实例可以自动序列化为XML和JSON,然后反序列化为对象。我有使用Java泛型进行反序列化的静态方法: fromXML(String xml) fromJSON(String json) . 它们返回的POJO类型是未知的,但由XML或JSON文本决定(我最初将这些方法打包到一个helper类中,但将这些静态方法移动到根POJO类中在语义上更为清晰。)

    其他几个例子:

    • 这个方法实际上是一个私有类特定的helper方法,不需要访问实例变量(这里引用的例子)。别偷偷摸摸的 this -等价于它的参数列表!

    但不要不假思索地使用静态,否则会有陷入更无序、更程序化编程风格的危险。

        15
  •  1
  •   CurtainDog    16 年前

    foo() 没有输入或输出),但我认为在现实世界的例子中,应该是对象状态的一部分的东西会很快消失。

    obj.method(param) 决心 method(obj, param)

        16
  •  0
  •   Rodrigo Gama    16 年前

    如果它只由A中的方法使用,并且在它之外没有任何用途,那么它应该是静态的(并且,可能,被放置在Helper类中)。除了现在,它没有任何用处,但也不能保证它永远不会有用处。否则,就不应该了。

    如果它与A的状态无关,它可能在其他地方有用。。。

    谈到最后一个问题,默认情况下它们不应该是静态的,因为必须编写“static”会让人们在编写静态方法之前进行思考。当您拥有异构团队(Java最有用的团队)时,这是一个很好的实践。

        17
  •  0
  •   Kru    16 年前

    如果foo是私有的,它可以是任何东西,静态的或非静态的。但是,大多数时候都是这样 因为这些字少了一个字。然后,如果因为更改了代码而需要使用state,可以立即使用。

    当它被保护或公开时,这取决于它做了什么。经验法则是 不是静态的 当它不是实例行为的一部分时,并使其 静止的 当它没有任何对象的时候。

        18
  •  0
  •   nanda    16 年前

    所以是的,一旦你掌握了这个概念,在所有的方法中都使用静态的(作为重构的结果)是有点痒的。我想我们对此无能为力。

    让我猜猜,你有没有读过 Clean Code ?

        19
  •  0
  •   user18943    16 年前

    当你写一个静态方法的时候,你应该记住你将在使用站点使用它和静态导入(使它看起来像类自由的),因此它应该像一个函数,它不做什么,可能返回什么,也可能不返回什么,并且与它所属的类的状态隔离。所以静态方法应该是一种罕见的情况。

    如果您似乎正在创建许多helper方法,那么可以考虑使用包私有实例方法而不是私有实例方法。更少的输入,更少的样板文件,因为您可以将它们作为同一个包中其他类的助手重用。

        20
  •  0
  •   Jesse    16 年前

    我认为“私有静态”(edit:for methods)在Java中有点矛盾。我认为静态方法的要点是提供对对象实例上下文之外的函数的访问。换句话说,它们实际上只有在公开的情况下才有用。如果您只是从单个对象实例的上下文中调用一个方法,并且该方法是私有的,那么将其设置为静态是没有意义的(编辑:但是,这没有实际意义)。

        21
  •  0
  •   xpmatteo    16 年前

    大多数静态方法都是因为

    1. 你希望字符串(或日期,或…)有一些它没有的功能

    第一个本身并不坏,但它往往是你丢失物品的一个迹象。不要使用诸如String或List之类的默认类型,而是尝试创建自己的类,并将静态方法移到这些类中。

    第二个原因是产生了一直流行的StringUtil、DateUtil和FooUtil类。这些方法是有问题的,因为您无法发现它们的存在,所以程序员经常编写这些实用方法的副本。同样,解决方法是避免一直使用字符串和日期。开始创建自己的对象,也许可以通过包装原始对象。静态方法成为新对象的非静态方法。

        22
  •  -1
  •   Josh K    16 年前

    如果 foo() 与对象无关 A 那为什么方法在里面呢?

        23
  •  -1
  •   xagyg    16 年前

    很多有趣的答案。

    如果代码仅由单个类的实例方法使用,则将其作为实例方法—它只是从实例上下文中提取代码—可以重构回(或从)访问实例状态的方法。

    故事结束了。