假设您在A类中编写方法foo()
.foo不会访问任何A的状态。你对foo的作用或行为方式一无所知。它可以做任何事情。
foo应该始终是静态的,不管其他任何考虑因素?为什么不呢?
似乎我的类总是在积累许多私有帮助器方法,因为我将任务分解并应用了only-write-it-once原则。其中大多数不依赖于对象的状态,但在类自己的方法之外永远不会有用。它们默认是静态的吗?结束大量内部静态方法是错误的吗?
答案 0 :(得分:49)
要回答关于标题的问题,一般来说,Java方法默认情况下不应该是静态的。 Java是一种面向对象的语言。
然而,你所谈论的是有点不同。你特别谈谈辅助方法。
对于辅助方法,只需将值作为参数并返回值,而不访问状态, 应为静态 。私人和静态。让我强调一下:
不访问状态的Helper方法应该是静态的。
使这些方法保持静态至少具有一个主要优势:在方法不需要知道任何实例状态的代码中使其完全明确。
代码说明了一切。对于那些将会阅读您的代码的人来说,事情变得更加明显,甚至在将来的某些时候也会对您有所帮助。
如果你确定该方法不依赖于外部或全局状态,那么它是纯函数,即数学意义上的函数:对于相同的输入,您可以确定始终获得相同的输出。
如果方法是静态的并且是纯函数,那么在某些情况下,它可能是 memoized 以获得一些性能提升(更改使用更多内存)。
在字节码级别,如果将辅助方法声明为实例方法或静态方法,则会获得两个完全不同的东西。
为了使本节更容易理解,让我们使用一个例子:
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
的调用。
在第一种情况下,如果没有静态方法,当您调用辅助方法时,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
将加载对堆栈上实例的引用,稍后将由invokespecial
使用。该指令将该值作为helper(...)
函数的第一个参数,并且从未使用过,我们可以在这里看到:
0 iload_1
1 iload_2
2 imul
3 iconst_1
4 iadd
5 ireturn
看到没有iload_0
?它被不必要地加载了。
现在,如果你声明了helper方法static,那么calculate()
方法将如下所示:
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
区别在于:
aload_0
条指令invokestatic
好吧,辅助函数的代码也有点不同:第一个参数没有this
,因此参数实际上位于0和1位置,我们可以在这里看到:
0 iload_0
1 iload_1
2 imul
3 iconst_1
4 iadd
5 ireturn
从代码设计角度来看,将helper方法声明为静态更有意义:代码本身就说明了,它包含更多有用的信息。它声明它不需要实例状态来工作。
在字节码级别,更清楚的是发生了什么,并且没有无用的代码(虽然我认为JIT无法优化它,但不会产生显着的性能成本)。
答案 1 :(得分:16)
如果方法不使用实例数据,那么它应该是静态的。如果该函数是公共的,这将提供重要的效率提升,您不需要创建对象的多余实例来调用该函数。可能更重要的是自我文档的优势:通过声明函数static,您向读者发送电报,告知该函数不使用实例数据。
我不明白这里的许多海报的情绪是否在Java程序中使用静态函数有问题。如果函数在逻辑上是静态的,请将其设置为静态。 Java库有许多静态函数。 Math类几乎充满了静态函数。
如果我需要一个计算平方根的函数,那么理性的方法就是:
public class MathUtils
{
public static float squareRoot(float x)
{
... calculate square root of parameter x ...
return root;
}
}
当然,你可以制作一个看似这样的“更多OOPy”版本:
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的一些抽象目标之外,这会更好吗?它需要更多的代码行,而且灵活性较差。
(是的,我现在在标准Math类中有一个平方根函数。我只是用它作为一个方便的例子。)
如果使用静态函数的唯一位置是每个可能使用的是来自某个类,那么是的,使它成为该类的成员。如果从课外调用它是没有意义的,请将其设为私有。
如果静态函数在逻辑上与类相关联,但可以合理地从外部调用,则将其设置为公共静态。就像,Java的parseInt函数在Integer类中,因为它与整数有关,所以这是一个理性的放置它。
另一方面,经常发生的是你正在编写一个类,并且你意识到你需要一些静态函数,但是这个函数并没有真正与这个类绑定。这是您第一次意识到自己需要它,但它可能非常合理地被其他与您现在正在做的事情无关的类使用。比如,回到平方根示例,如果你有一个包含纬度和经度的“Place”类,并且你想要一个函数来计算两个地方之间的距离,你需要一个平方根作为计算的一部分,(并且假装标准库中没有可用的平方根函数),创建一个单独的平方根函数而不是将其嵌入到更大的逻辑中会很有意义。但它并不属于你的Place类。这是为“数学实用程序”或类似实用程序创建单独类的时候。
你问,“不论其他任何考虑因素,foo总是应该是静态的吗?”我会说“几乎,但不完全。”
我认为使其不是静态的唯一原因是子类想要覆盖它。
我想不出任何其他原因,但我不排除这种可能性。我不愿意说“从来没有在任何情况下”,因为有人通常可以提出一些特殊情况。
答案 2 :(得分:8)
没有。决不。静态方法应该是一个例外。 OO就是让对象具有围绕对象状态的行为。 Imho,理想情况下,不应该有任何(或很少)静态方法,因为与对象状态无关的所有东西都可以(并且为了避免将对象的概念引入荒谬,应该)放在模块中的普通旧函数中水平。工厂可能的例外,因为Complex.fromCartesian(以维基百科为例)读得很好。
当然这个(编辑:模块级函数)在单范式OO语言中是不可能的(编辑:像Java) - 这就是为什么我是多范式语言设计的忠实拥护者,办法。但即使只使用OO语言,大多数方法都会围绕对象的状态,因此是非静态的。也就是说,除非你的设计与OO无关 - 但在这种情况下,你使用了错误的语言。
答案 3 :(得分:8)
有趣的问题。实际上,我没有看到使A
类的私有帮助方法静态的重点(当然,除非它们与A
中可公开访问的静态方法相关)。你没有获得任何东西 - 根据定义,任何可能需要它们的方法都已经拥有A
的实例。而且由于它们是幕后辅助方法,没有什么可以说你(或另一个同事)最终不会决定其中一个无国籍帮助者实际上可以从了解状态中获益,这可能会导致一些重构滋扰。
我不认为错误以最终的大量内部静态方法结束,但我也看不出你从中获得了什么好处。我说默认为非静态,除非你有充分的理由不这样做。
答案 4 :(得分:6)
我通常
根据需要按顺序执行以下步骤:
a)我在成员方法中编写了一些代码,弄清楚我可能会重用一些代码并且
提取到非静态方法
b)现在我将看看这个方法是否需要访问状态,或者我是否可以将其需要放入一个或两个参数和一个return语句中。如果是后者:
制作方法(私有)静态
c)如果我发现我可以在同一个包的其他类中使用此代码,我将
将方法设为公共,并将方法移至具有默认可见性的包帮助程序类
E.g。在com.mycompany.foo.bar.phleeem
包中,我会创建一个具有默认可见性的班级PhleeemHelper
或PhleeemUtils
。
d)如果我意识到我需要在我的应用程序中使用此功能,那么
将帮助程序类移动到专用实用程序包
e.g。 com.mycompany.foo.utils.PhleeemUtils
一般来说,我喜欢最低可见度的概念。那些不需要我方法的人不应该看到它。这就是为什么我从私人访问开始,转移到包访问,只有在专用包中才公开。
答案 5 :(得分:6)
除非传入对象引用,否则类上的static
方法会强制该方法本身不能改变对象,因为它无法访问this
。在这方面,static
修饰符向程序员提供有关方法意图的信息,即无副作用的信息。
反静态纯粹主义者可能希望将它们移除到反实用主义纯粹主义者肯定反对的实用类中。但实际上,除了与新实用程序类的紧密耦合之外,人为地将这些方法从他们唯一的调用站点移除的是什么。
将常用实用程序方法盲目地提取到自己的类中的问题是,这些实用程序应该被视为新的公共API,即使它仅由原始代码使用。很少有开发人员在执行重构时没有考虑到这一点。使用蹩脚的实用程序类快速转发到其他开发人员。稍后有人会对扩展程序进行更改以适应自己。如果你很幸运,一两次考试,但可能不是。
答案 6 :(得分:5)
我发现很难订阅那些避免静态方法的理论。他们在那里推广一种完全卫生的面向对象的模型,以防止任何与对象关系的偏差。在实践面向对象中,我认为没有任何必要的反物质纯粹。
无论如何,所有java.util.Arrays类都是静态的。数字类Integer,Boolean,String具有静态方法。很多静态方法。这些类中的所有静态方法都可以转换为各自的类实例。
由于古老的Gosling等人被证明是具有静态方法的有用的榜样 - 没有必要避免它们。我意识到有些人对我的回答表示不满。有许多程序员喜欢将其成员转换为静态的原因和习惯。
我曾经在一个机构工作,项目负责人希望我们尽可能地使方法静态化并最终确定它们。另一方面,我不是那么极端。与关系数据库模式设计一样,这完全取决于您的数据建模策略。
应该有一致的理由为什么方法是静态的。遵循标准的Java库模式,当方法变为静态时,没有什么坏处。
最重要的是编程生产力和质量。在自适应和敏捷开发环境中,不仅要调整项目的粒度以有效地响应需求变化,还要调整编程氛围,如提供一致的编码模型,以充分利用您拥有的编程技能。在一天结束时(项目几乎永远不会结束),您希望团队成员高效而有效,而不是他们是否避免使用静态方法。
因此,设计一个编程模型,无论你是想要MVP,注入,方面驱动,静态避免/亲和等级,还知道你为什么想要它们 - 不是因为一些理论上的坚果告诉你你的编程习惯会违反原则。请记住,如果你在一个行业工作,它总是质量和盈利能力,而不是理论上的纯度。
最后什么是面向对象的?面向对象和数据规范化是创建正交信息透视的策略。例如,在早些时候,IBM手册被编写为非常正交。也就是说,如果在数千本手册中的某个页面的某处写入一条信息,他们就会避免重复该信息。这很糟糕,因为您将阅读学习如何执行某项任务并经常遇到其他手册中提到的概念,您必须熟悉手册的“数据模型”才能捕获数千个连接的信息。手册。
出于同样的原因,OS / 2未能与微软竞争,因为IBM的正交性概念纯粹是基于机器和数据的,IBM非常自豪地宣称他们真正的面向对象与微软的虚假面向对象相悖以人性化为视角。他们忘记了我们人类拥有各自不同的正交信息视角,这些视角不符合基于数据和机器的正交性,甚至不符合彼此。
如果您熟悉树的拓扑结构,您会发现可以选择任何叶节点并将其作为根节点。或者甚至是任何节点,如果你不介意拥有一个多树干树。每个人都认为他/她的节点是根,而事实上任何人都可能是根。如果你认为你的面向对象的观点是正典,那就再想一想。更重要的是最小化被接受为候选根的节点数。
有效性和效率之间需要妥协。拥有一个高效的数据或对象模型是没有意义的,这些数据或对象模型很难被其他程序员有效地使用。
答案 7 :(得分:5)
Java混淆了模块,命名空间,adt和类的概念,因此声称某些面向类的OO纯度应该阻止你将java类用作模块,命名空间或adt是荒谬的。
是的,方法应该是静态的。纯粹的内部支持方法应该是私人的;辅助方法受到保护;和实用功能应该是公开的。此外,静态字段,静态常量和公共静态方法之间存在差异。第一个是“全局变量”的另一个词;并且几乎总是要避免,甚至通过存取方法的调解几乎不会限制损害。第二种是将java类视为符号常量的命名空间,完全可以接受。第三种是将java类视为函数的模块,作为一般规则,应该避免副作用,或者如果必要,限制为传递给函数的任何参数。静态的使用将有助于确保您不会通过访问对象的成员而无意中破坏它。
另一种情况是静态方法非常有用,就是在java中编写函数代码时。在这一点上,由OO支持者开发的大多数经验法则都会消失。您将发现自己的类充满了静态方法,并且公共静态函数常量绑定到匿名内部函子。
最终,java具有非常弱的作用域构造,在相同的“类”和“接口”语法下混合了许多概念。您不应该将“默认”设置为静态,因为您可以随意使用java提供的工具来提供名称空间,ADT,模块等,以及何时需要它们。
答案 8 :(得分:5)
我通常不会让它们变得静止,但可能应该。作为提示告诉下一个编码器这个方法CANT修改对象的状态是有价值的,当你修改方法来访问你正在改变方法性质的成员时,给你一个警告是很有价值的。
编码就是与下一个编码器进行通信 - 不用担心让代码运行,这是微不足道的。因此,为了最大化沟通,我会说如果你真的需要这样的帮助,那么将它静态化是一个好主意。除非你正在制作数学,否则将其设为私有也很重要。喜欢上课。
答案 9 :(得分:4)
如果它对这个类的对象没有任何作用,但实际上属于这个类(我会考虑将它移到别处),是的,它应该是静态的。
答案 10 :(得分:3)
这取决于i.g. java.lang.Math没有非静态的方法。 (您可以执行静态导入来编写cos()而不是Math.cos()) 这不应该被滥用,但是作为一些有意被称为实用程序的代码,这是可以接受的。 I.g Thread.currentThread()
答案 11 :(得分:3)
静态方法用于标识方法(或该方面的变量),该方法与从该类创建的对象无关,而与类本身无关。例如,您需要一个变量来计算创建的对象数。你可以这样说:'private static int instances = 0;'然后在该类的构造函数中放入一些增加'实例'的东西,这样你就可以继续计算它。
答案 12 :(得分:3)
如果可以避免使用静电,请不要使用静电。它与继承冲突(重写)。
另外,没有被要求但有些相关,不要公开实用方法。
至于其他人,我同意马特b。如果你有一些潜在的静态方法,它们不使用状态,只需将它们放在私有类中,或者可能是受保护的类或包受保护的类。
答案 13 :(得分:1)
在创建静态方法之前要认真思考,但有时它们是一个很好的解决方案。
约书亚布洛赫在Effective Java中的“第1项:考虑静态工厂方法而不是构造函数”中提出了一个非常有说服力的案例,静态方法可能非常有用。他给出了java.util.Collections类的32个静态工厂方法作为示例。在一种情况下,我有一个POJO类的层次结构,其实例可以自动序列化为XML和JSON,然后反序列化为对象。我有使用Java泛型进行反序列化的静态方法:fromXML(String xml)
和fromJSON(String json)
。它们返回的POJO类型不是先验已知的,而是由XML或JSON文本确定的。 (我最初将这些方法打包成一个帮助器类,但是将这些静态方法移动到根POJO类中语义上更清晰。)
其他几个例子:
this
- 等同于其参数列表!但是不要不假思索地使用静态,否则你就会陷入更加混乱和程序化的编程风格。
答案 14 :(得分:1)
不,使用静力学应该非常适合。
在这种情况下,OP很可能在传递给静态方法的参数中“隐藏”状态。提出问题的方式使得这不明显(foo()
没有输入或输出),但我认为在现实世界的例子中,实际上应该成为对象状态一部分的东西会很快消失。
在一天结束时,对obj.method(param)
的每次调用都会解析为method(obj, param)
,但这种情况会比我们应该设计的水平更低。
答案 15 :(得分:0)
当你编写一个静态方法时,你应该记住,你将在use-site上使用static-import(使它看起来没有类),因此它的行为就像一个函数而不是某些东西并且可能会或可能不会返回某些内容,并且与其所属的类状态相隔离。所以静态方法应该是一种罕见的情况。
如果你似乎制作了很多辅助方法,那么考虑使用包私有实例方法而不是私有方法。减少输入,减少样板,因为您可以将它们重新用作同一包中其他类的帮助。
答案 16 :(得分:0)
我认为让Java中的方法保持静态会导致初学者在没有正确理解OO的情况下实现相当混乱的实现。我们一直在那里思考它。如果这些方法是默认的,那么我们理解OO原则有多难?
所以是的,一旦你掌握了这个概念,在整个方法中都有静态(由于重构)。我们没有什么可以做的。
注意:让我猜一下,你有没有机会阅读Clean Code?
答案 17 :(得分:0)
我认为“私有静态”(编辑:方法)在Java中是一种矛盾。我认为静态方法的要点是提供对象实例上下文之外的函数的访问。换句话说,如果它们是公开的,它们实际上只是有用。如果您只是在单个对象实例的上下文中调用方法,并且该方法是私有的,则将其设置为静态是没有意义的。 (编辑:但是,它没有实际区别)。
在这种情况下,我通常会尝试使方法足够抽象,以至于它们在其他上下文中很有用,并且我在实用程序类中将它们公开。把它看作是编写支持库代码,并仔细思考你的api。
答案 18 :(得分:0)
如果foo是私有的,它可能是任何东西,静态或不是。但是,大多数情况下它会不是静态的,因为这些只是键入的一个词。然后,如果您因为更改了代码而需要使用状态,则可以立即执行。
当它受到保护或公开时,它取决于它的作用。一个经验法则是当它不是实例行为的一部分时使不是静态,并且在没有任何对象的情况下调用它时使其成为 static 。 如果您不确定,请问自己是否有必要覆盖子类中的方法。
答案 19 :(得分:0)
如果它只被A中的方法使用过并且在它之外没有任何用处,那么它应该是静态的(并且可能放在Helper类中。它现在在A之外没有任何用处,但是不能保证它永远不会有。否则,它不应该。
如果它与A的状态没有任何关系,它可能在其他地方有用......
无论如何,默认情况下,这并不能使Java方法成为静态。
谈到最后一个问题,默认情况下它们不应该是静态的,因为必须编写“静态”才能让人们在编写静态方法之前思考。当您拥有异构团队(Java最有用的团队)时,这是一个很好的做法。
答案 20 :(得分:0)
大多数静态方法都是因为
而编写的第一个本身并不坏,但它通常表明你缺少对象。不要使用String或List等默认类型,而是尝试创建自己的类并将静态方法移动到这些类。
第二个原因产生了一直流行的StringUtil,DateUtil,FooUtil类。这些都是有问题的,因为你无法发现它们存在,因此程序员经常会编写这些实用程序方法的副本。解决方案同样是避免一直使用String和Date。可以通过包装原始对象开始创建自己的对象。静态方法成为新对象的非静态方法。
答案 21 :(得分:-1)
如果foo()
与对象A
没有任何关系,那么为什么这里的方法呢?
静态方法应该仍然相关。如果没有任何事情发生,那你为什么要编写一个与之无关的方法呢?
答案 22 :(得分:-1)
很多有趣的答案。
如果你拼命寻求规则,那就用这个:
如果代码只被单个类的实例方法使用,那么使它成为一个实例方法 - 它只是从实例上下文中提取代码 - 可以将其重构为(或从)方法中重构访问实例状态。
如果代码由MORE THAN ONE类使用,并且不包含对方法所在类中的实例变量的访问权限,则将其设置为静态。
故事结束。