Java:何时创建静态方法和实例方法

33

我有一个 Gene 类来跟踪基因。在 Gene 中有一种计算两个基因之间距离的方法。是否有将其设置为静态方法的原因?

哪种方式更好?

public static int geneDistance(Gene g0, Gene g1)

或者

public int geneDistance(Gene other)

关于将其设置为静态成员的支持/反对理由?我知道成员静态的含义,我只是想了解它对最大清洁度/效率等方面的影响。

我重复使用相同的模式来返回两个基因的修剪版本,查找基因之间的匹配,查找动物(包含基因集合)之间的匹配等。


1
这是一个非常有趣的问题。我有自己的观点,但我认为它们只能算是“口味”,所以我不会回答。 - Jonathan Feinberg
这应该是社区维基。 - IAdapter
8
请考虑使用“distanceTo(Gene that)”这个名称。您有一个隐含的上下文应该在命名中反映出来。 - Thorbjørn Ravn Andersen
1
甚至更好的是“getDistanceTo(Gene g)”。 - Johnny Baloney
16个回答

21

实例化,而不是静态的


对于这种情况,我认为第二种选择显然更好。如果你仔细想想,任何方法都可以被实现为静态的,只要你愿意将对象传递给它,这似乎只是一个特殊情况,因为另一个参数也是一个实例。

因此,我们对对称性和抽象的追求在选择点运算符的两个实例对象之间时稍微有些受到冒犯。但是,如果你把 .method 看作是 . 然后加上 operator,那么这并不是真正的问题。

此外,函数式链接的唯一方法是使用属性,即实例方法。你可能希望 thing.up.down.parent.next.distance(x) 能够工作。


14

当你将一个方法声明为静态方法时,它意味着可以在没有类实例的情况下调用该方法。同时,这也意味着该方法无法访问实例变量,除非它被传递了一个对象的引用。

有时候,将一个方法声明为静态方法是有意义的,因为该方法与类相关,而不是与类的某个特定实例相关。例如,所有的解析(parse)方法,比如Integer.parseInt(String s),将一个String转换成一个int,但与Integer对象的任何特定实例都无关。

然而,如果一个方法必须返回一些特定于对象实例的数据(比如大多数 getter 和 setter 方法),那么它不能是静态的。


2
这也意味着该方法本身只能访问同样是静态的类变量。什么?一个静态方法在给定实例“a”的情况下可以访问所有“a”的字段和方法。 - Jonathan Feinberg
2
他的意思是,由于没有需要调用它的实例,所以他们无法访问“this”。 - mk12
2
对,我应该更像这样表达:静态方法不能访问实例变量,除非它被传递了一个对象引用。 - Charles Salvia
2
我认为@Charles在没有参考OP的public static int geneDistance(Gene g0, Gene g1)的情况下发言。他显然是指类内静态方法作用于类而不是传入实例。 - non sequitor
Charles,您可以编辑您的答案以澄清。 - Jonathan Feinberg

8

我认为并没有绝对的“更好”,但是public int geneDistance(Gene other)在风格上更加类似于Java中的其他方法(例如Object.equals, Comparable.compareTo),所以我会选择这种方式。


8

我更喜欢第二种形式,即实例方法,原因如下:

  1. 静态方法很难被替换,这使得测试变得困难。
  2. 静态方法更倾向于过程化编程(因此不太面向对象)。

在我看来,静态方法对于实用类(如StringUtils)是可以接受的,但我更喜欢不滥用它们。


请注意,静态导入可以使实用程序类非常干净,这对于静态来说是一个罕见的情况。例如:设计按合同进行断言框架之类的东西。 - Michael Easter
没错,例如 JUnit 4,我很喜欢它。不过没看到加一的部分 :) - Pascal Thivent

7

我对Charle的回答进行了改写:

如果该方法打算以任何方式使用底层对象的状态,请将其设置为实例方法。否则,将其设置为静态方法。

这取决于对象类的设计方式。

在你的情况下,alphazero,可能int geneDistance(Gene g0, Gene g1)并不真正依赖于它被调用的Gene实例的状态。我会将此方法设置为静态方法。并将其放置在像GeneUtils这样的实用程序类中。

当然,您的问题可能还有其他方面,我不知道,但这是我使用的一般规则。

P.S. -> 我不会将该方法放在Gene类本身中的原因是,Gene不应该负责计算其与另一个Gene之间的距离。 ;-)


@adwiv 嗯,getter方法永远不能是静态的,因为必须始终存在一个实例来“获取”属性。但是,是的,我明白你的意思。我已相应地编辑了我的答案。 - divesh premdeep
+1并删除我的评论,因为现在它看起来很不合适。 :) - Adwiv

3

public static int geneDistance(Gene g0, Gene g1)是一个类似于Java中的CollectionsArrays的单独实用程序类的一部分,而public int geneDistance(Gene other)将成为Gene类的一部分。考虑到您还有其他操作,如“两个基因的修剪版本、查找基因之间的匹配、查找动物之间的匹配(这些动物包含基因集合)等”,我会为它们创建一个单独的静态实用程序类,因为这些操作对于Gene并不具有语义意义。

如果“基因距离”的语义可以包装在您的equals(Object o)方法中,则可以在那里使用它,否则请将其包含在您的静态实用程序中。


实际上,我最初有一个静态实用程序,但后来将所有这些函数移动到了“Gene”和“Species”中。为什么这些与基因在语义上相关呢? - Nick Heiner
我认为Collections实用类的存在是为了使第三方更容易实现各种Collection接口,而无需实现自己的min、rotate、sort等方法。对于Gene类,这不是一个问题。考虑到“java.awt.Point”类,没有Points实用类来计算点之间的距离。 - Sam Barnum
@Sam,我不仅关注单一距离检查,还包括修剪、匹配、集合匹配以及其他他决定加入的内容,这些内容与Collections类所代表的内容保持一致。@Rosarch,当我说不是语义相关时,我的意思是找到多个基因的修剪版本之类的东西可能并不是与基因有关的问题(我肯定可能错了,因为我不是生物学家哈哈 :)),但希望你能理解我的意思。 - non sequitor

2
我会尝试总结一下已经给出的观点,并且我同意这些观点。
就个人而言,我不认为有一个“感觉更好”的答案。确实存在一些有效的理由,说明您不应该使用填充了静态方法的实用程序类。
简短的答案是,在面向对象的世界中,您应该使用对象及其所带来的所有好处(封装、多态)。
如果计算基因之间距离的方法不同,则应该(更可能是使用策略模式)为每种变化使用一种Gene类。封装变量。否则,您将最终得到多个if语句。
这意味着如果未来出现计算基因之间距离的新方法,则不应修改现有代码,而是应添加新代码。否则,您可能会破坏已经存在的内容。
您应该告诉您的对象要做什么,而不是询问它们的状态并为它们做出决定。突然之间,您打破了单一职责原则,因为这是多态性。
静态方法可能很容易单独测试,但在后续过程中,您将在其他类中使用此静态方法。当对这些类进行隔离测试时,您将很难做到。或者说根本不行。
我会让Misko说出他的话,这可能比我能想到的更好。
import junit.framework.Assert;

import org.junit.Test;

public class GeneTest
{
    public static abstract class Gene
    {
        public abstract int geneDistance(Gene other);
    }

    public static class GeneUtils
    {
        public static int geneDistance(Gene g0, Gene g1)
        {
            if( g0.equals(polymorphicGene) )
                return g0.geneDistance(g1);
            else if( g0.equals(oneDistanceGene) )
                return 1;
            else if( g0.equals(dummyGene) )
                return -1;
            else
                return 0;            
        }
    }


    private static Gene polymorphicGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return other.geneDistance(other);
                                        }
                                    };

    private static Gene zeroDistanceGene = new Gene() 
                                    {                                        
                                        @Override
                                        public int geneDistance(Gene other) {
                                        return 0;
                                        }
                                    };

    private static Gene oneDistanceGene = new Gene() 
                                    {                                        
                                        @Override
                                        public int geneDistance(Gene other) {
                                        return 1;
                                        }
                                    };

    private static Gene hardToTestOnIsolationGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return GeneUtils.geneDistance(this, other);
                                        }
                                    };

    private static Gene dummyGene = new Gene()
                                    {

                                        @Override
                                        public int geneDistance(Gene other) {
                                        return -1;
                                        }
                                    };                                    
    @Test
    public void testPolymorphism()
    {
        Assert.assertEquals(0, polymorphicGene.geneDistance(zeroDistanceGene));
        Assert.assertEquals(1, polymorphicGene.geneDistance(oneDistanceGene));
        Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
    }

    @Test
    public void testTestability()
    {

        Assert.assertEquals(0, hardToTestOnIsolationGene.geneDistance(dummyGene));
        Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
    }    

    @Test
    public void testOpenForExtensionClosedForModification()
    {

        Assert.assertEquals(0, GeneUtils.geneDistance(polymorphicGene, zeroDistanceGene));
        Assert.assertEquals(1, GeneUtils.geneDistance(oneDistanceGene, null));
        Assert.assertEquals(-1, GeneUtils.geneDistance(dummyGene, null));
    }    
}

2

我想先用一个新问题回答你的问题:你的Gene类负责什么?也许你听说过“单一职责原则”:一个类应该只有一个改变的原因。所以,我相信如果你回答了这个问题,你就能决定如何设计你的应用程序。在这种情况下,我不会使用第一种方法或第二种方法。在我看来,更好的做法是定义新的职责并将其封装在一个单独的类或函数中。


1
终于在这个讨论帖中有人理解面向对象编程的真正含义了!+1 - MattDavey

1
在这种情况下,我会将其作为实例方法。但是如果您有一个逻辑答案,当g0为空时,请使用BOTH(这种情况比您想象的更常见)。
例如,aString.startsWith(),如果aString为空,您可能认为返回null是合理的(如果您认为函数可以容忍NULL)。这使我能够简化程序,因为客户端代码中不需要检查aString是否为空。

final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if (MyString.startsWith(aString, aPrefix))
        aStrings.aStringadd();
}

而不是


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if ((aString != null) && aString.startsWith(aPrefix))
        aStrings.aStringadd();
}

注意:这只是一个过于简化的例子。

只是一个想法。


1
这是一个元答案和有趣的练习:调查一堆Java SDK库类,并尝试对不同类中的静态方法之间的共性进行分类。

1
很遗憾,没有元赞。 ;) - Bombe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接