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