【问题标题】:Making all methods static使所有方法静态
【发布时间】:2016-06-09 05:54:36
【问题描述】:

在我的应用程序中,有许多类包含处理数据的方法,这些方法可以是计算和数据丰富。

我的问题是 - 如果类没有任何类级别的变量,我可以将类中的所有方法设为静态吗?

我想线程不会有任何问题。

有什么后果吗?由于我不必实例化类,有什么性能优势吗?

一个示例类:

Class A{   
    public Object findTheCar(String id){    
        Car car= new Car();    
        //do something    
        return car;    
    }
}

我打算把上面的改成静态的。

【问题讨论】:

  • 如果你发现你想要编写包含许多/所有静态方法的类,你不是在考虑面向对象;你不是在做OO设计。您应该查阅诸如amazon.com/dp/0672330164/?tag=stackoverfl08-20 之类的教科书以获取更多信息。
  • 哦,我不知道,也许是你的雇主、教授、同事、未来的自己和佛陀?
  • @BadZen 其中一些似乎有效 ;)
  • @GhostCat 如何接受回答?

标签: java static


【解决方案1】:

一个经验法则:问问自己“调用这个方法是否有意义,即使还没有构建 Obj?”如果是这样,它肯定应该是静态的。

因此,在 Car 类中,您可能有一个静态方法 double convertMpgToKpl(double mpg),因为即使没有人制造过汽车,人们也可能想知道 35mpg 转换成什么。但是void setMileage(double mpg)(设置特定 Car 的效率)不能是静态的,因为在任何 Car 构建之前调用该方法是不可想象的。

(顺便说一句,反过来并不总是正确的:你有时可能有一个涉及两个 Car 对象的方法,但仍然希望它是静态的。例如 Car theMoreEfficientOf( Car c1, Car c2 )。虽然这可以转换为非静态版本, 有些人会争辩说,由于没有“特权”选择哪个 Car 更重要,因此您不应该强制调用者选择一个 Car 作为您将调用该方法的对象。这种情况占了相当大的比例不过,所有静态方法的一小部分。)

虽然使用静态方法有一些正当理由:

  • 性能:如果您希望运行某些代码,并且不想实例化额外的对象来执行此操作,请将其放入静态方法中。 JVM 还可以优化静态方法很多(我想我曾经读过 James Gosling 声明你不需要 JVM 中的自定义指令,因为静态方法会一样快,但找不到源 - 因此这可能是完全错误的)。是的,它是微优化,可能不需要。而且我们程序员永远不会仅仅因为它们很酷而做不必要的事情,对吧?

  • 实用性:不要调用 new Util().method(arg),而是调用 Util.method(arg),或者使用静态导入的 method(arg)。更简单,更短。

  • 添加方法:您确实希望 String 类具有 removeSpecialChars() 实例方法,但它不存在(也不应该存在,因为您的项目的特殊字符可能不同来自其他项目的),并且您无法添加它(因为 Java 至少是理智的),因此您创建了一个实用程序类,并调用 removeSpecialChars(s) 而不是 s.removeSpecialChars()。甜蜜。

  • Purity:采取一些预防措施,你的静态方法将是一个纯函数,也就是说,它唯一依赖的就是它的参数。数据输入,数据输出。这更易于阅读和调试,因为您无需担心继承问题。您也可以使用实例方法来做到这一点,但编译器会在静态方法方面为您提供更多帮助(通过不允许引用实例属性、覆盖方法等)。

如果你想创建一个单例,你还必须创建一个静态方法,但是......不要。我的意思是,三思而后行。

现在,更重要的是,为什么您不想创建静态方法?基本上,多态性消失了。您将无法覆盖该方法,也无法在接口中声明它。您的设计需要很大的灵活性。此外,如果您需要 state,如果您不小心,最终会遇到很多 并发错误和/或瓶颈。

所以只在以下场景中定义静态方法:

  1. 如果您正在编写实用程序类并且它们不应该被更改。
  2. 如果方法没有使用任何实例变量。
  3. 如果任何操作不依赖于实例创建。
  4. 如果有一些代码可以很容易地被所有实例方法共享,请将该代码提取到静态方法中。
  5. 如果您确定方法的定义永远不会被更改或覆盖。因为静态方法不能被覆盖。

让我们更详细地讨论它:

优点:

静态成员/方法在帮助类中使用,比如数学或常量类。这有助于其他对象利用字符串或有用的函数,您不需要为其创建对象但使用类名调用它们。示例 – 使用静态函数调用单例对象。

缺点:

静态成员是类的一部分,因此在应用程序终止之前一直保留在内存中,并且永远不会被垃圾回收。使用过多的静态成员有时会预测您无法设计产品并试图应对静态/过程编程。它表示面向对象的设计受到了损害。这会导致内存溢出。如果您在 Java 中使任何方法成为静态方法,也存在某些缺点,例如,您不能覆盖 Java 中的任何静态方法,因此这会使测试变得更加困难,您不能用 mock 替换该方法。由于静态方法保持全局状态,它们可以在并发环境中创建难以检测和修复的细微错误。

要记住的事情:

静态变量将是类定义的一部分,而不是堆上。但是,当您知道将从多个位置访问对象时,静态变量很有用。访问静态资源不是线程安全的。在线程环境中,您可能会得到奇怪/不可预测的结果。但是,如果您只读取静态值,那么使用线程就可以了。

静态如何破坏封装:

它们的技术实现是允许在类的所有实例中维护状态。问题是这本质上不是 OOP,因为它忽略了封装。如果一个变量可以被一个类的任何实例改变,那么封装/信息隐藏背后的基本原则就完全丧失了:一个对象不再完全控制它的状态。它的状态现在依赖于本质上是全局的变量。我们知道这是不好的。甚至私有静态变量在全局级别维护状态,但只是限制其访问。对象的任何实例都可以更改导致歧义的静态变量,因为对象的各个实例不再控制它们自己的状态。状态更改可以在不知道依赖于该状态的对象的情况下任意发生,这是有问题的,因为发生这种情况时对象可能无法正常工作。正如人们常说的那样,“继承破坏封装”静态方法以更严厉的方式做到这一点:不仅公开内部实现,还公开内部状态。

在您的示例问题中:

正如您所说,该方法将用于多线程环境,请考虑以下问题(修改您的代码):

public Object findTheCar(String id) {
        Car car = null; //Line 2
        if (id != null) {
            car = new Car();
        }
        //Line 6
        // do something 

        //
        //Line 10
        return car;
    }

在上面:如果这从两个线程执行,并且第一个线程在 第 6 行,第二个线程在第 2 行,仍然是线程 安全。

因为:

局部变量存储在每个线程自己的堆栈中。这意味着局部变量永远不会在线程之间共享。这也意味着所有局部原始变量都是线程安全的。

对对象的本地引用有点不同。引用本身不共享。然而,引用的对象并不存储在每个线程的本地堆栈中。所有对象都存储在共享堆中。如果本地创建的对象从不逃避创建它的方法,则它是线程安全的。实际上,您也可以将其传递给其他方法和对象,只要这些方法或对象都不使传递的对象可用于其他线程。

对象成员与对象一起存储在堆中。因此,如果两个线程在同一个对象实例上调用一个方法并且该方法更新对象成员,则该方法不是线程安全的。

线程安全检查:如果一个资源在同一个线程的控制下被创建、使用和处置,并且从未脱离该线程的控制,那么该资源的使用就是线程安全的。

发件人:http://tutorials.jenkov.com/java-concurrency/thread-safety.html

【讨论】:

  • 感谢您宝贵的时间和详尽的回答。但是我的观点是,由于该类不包含任何类级别的变量,因此该类应该如何改变状态?与垃圾收集相同。为什么归类为实用类?我这里的班级将处理并发请求。
  • @rahulk 更新了答案,让您更加清楚。
  • @shridutt kothari 我对更新的答案有疑问,两个线程都将在自己的上下文中处理该方法,从不重叠 - 不是吗?否则,所有实用程序类都会出现此问题。
  • @rahulk 是的,它会在所有静态实用程序类中引发问题,静态代码块在多线程环境中更容易出错。
  • 在findTheCar方法的情况下,局部变量car在线程之间不共享。每个线程都有自己的。
【解决方案2】:

如果您没有类级变量,那么可以,您可以将类上的所有方法设为静态。由于避免了对象实例化,它甚至可能更高效。但是在面向对象的编程中,对象有“状态”和“行为”。您的对象没有状态,只有行为。那么你真的在这里做面向对象编程吗?我认为你不是(这不一定是坏事)。但也许您可能更喜欢非 OO 编程语言。

【讨论】:

  • 数学课docs.oracle.com/javase/7/docs/api/java/lang/Math.html怎么样?你认为这是 java 核心中非 OOP 的一个例子吗?
  • @Pavlo。是的。 Math 类肯定不是面向对象的。它是一个函数库。它有一个私有构造函数,因此对象甚至不打算从 Math 类中实例化。
  • @Asaph 如果没有类级变量,是否需要担心状态?
  • @rahulk 正确。没有状态。我的总体观点是,如果您的大多数类没有状态,那么您就没有真正的 OO 设计。考虑重构。
【解决方案3】:

没有属性的类是线程安全的。因此,您可以使用类实例或制作静态方法。两者都可以正常工作。这是您的应用程序架构的问题。如果是这个简单的 Java 应用程序,我会在这里使用静态方法。

【讨论】:

  • 可能这不是一个简单的 java 应用程序,但我想把 OOPS 放在一边,为什么不采用像“如果没有类级别变量 - 使用静态”这样的规则:)
  • 如果您关心线程安全,那么要走的路就是不可变类。不是静态方法。
【解决方案4】:

到目前为止,我认为大多数其他答案完全忽略了两个问题。

首先,静态是面向对象编程中的异常。如果你看它,它只是变相的“过程编程”。这可能意味着:如果您的代码主要由 static 方法组成,这些方法会以某种方式处理某些数据……机会是:您在正确的 OO 建模/设计方面做得不好。因为,当您创建 OO 设计时,您会考虑创建抽象;并使用合理的封装将“数据和行为”结合在一起;更重要的是提供巧妙的信息隐藏。

然后(在我看来几乎一样糟糕):使用 static 会给代码的可测试性带来巨大的负担。所以,机会是:你要么不做 TDD/单元测试;要么或者您已经使用了诸如 PowerMock 之类的字节码操作框架来测试使用静态方法的代码。这两种选择都相当……不太好。

所以,长话短说:shr​​idutt 的回答为您提供了一个很好的起点来评估什么可能是静态。但我全心全意地建议您先退后一步,例如:向经验丰富的设计师/编码人员展示您的一些代码,并获得他们关于“这东西如何 OO 和可测试性”的反馈。

您的问题听起来像您可以从思考更多地了解您的代码而不是在这里或那里添加一些“静态”。

【讨论】:

  • 使用静态如何使测试代码变得更加困难?你有一个函数,它接受参数并返回一个值。假设它没有副作用,似乎很容易测试。
  • 无论如何,我绝对建议少考虑您的代码,多编写代码,然后看看会带您去哪里。更多地考虑你的代码会导致分析瘫痪。我发现在编码、测试、编码、测试、编码、测试、重构方面取得了更大的成功。
  • @dimadima 如果你在进行合理的单元测试,你经常必须控制你的被测类正在调用的方法。您无法控制静态方法;仅通过使用 PowerMock。如前所述:使用 PowerMock 是一个非常糟糕的主意。
  • 如果您谨慎使用静态方法,即,使它们纯粹,IMO 这是一个很好的做法,而不是异常。纯函数易于测试和推理。
【解决方案5】:

这取决于需要,如果我们想让方法静态或不。一个好的经验法则是,如果要在应用程序中调用(使用)该方法以执行一些标准操作,通常可以考虑将它们设为静态。 过度使用 static 的缺点与处理和释放对象有关,因为 static 方法会一直保留在内存中直到程序停止(重新启动),过度使用 static 会对内存消耗产生一些影响。

【讨论】:

  • 静态方法将保留在内存中直到程序停止 - 该方法的蓝图保留在内存中,可以忽略 - 对吗?
【解决方案6】:

您绝对可以将所有方法设为静态。为什么不?如果语言允许,并且对你有意义,那就去做吧。我很少因为做一些对我没有意义的事情而受益,只是因为我以前没有见过这样做,或者因为它没有遵循某种设计模式(比如“OO”——这对很多人来说意味着很多事情人,而 Java(尤其是 Java 8)实际上支持除 OO 之外的许多范例)。我绝对不会添加不必要的代码行(Obj a = new Obj(); a.do() vs Obj.do())。

就现有技术而言,此类类通常称为“utility classes”。但是对于这样的类,没有什么必须是实用的。就个人而言,当我认为不需要在类实例中捕获值或引用时,我会使用静态方法。这对我来说有很多次,我在其他人的编程中也看到过这种情况。

【讨论】:

  • 嗯,因为 staticOO 就像“Ike”对“Tina Turner”?或者可能是因为 static 几乎默认情况下会使您的代码不可单元测试?
  • (1) 每个静态方法,假设它没有副作用,是一个你可以测试的单元。 (2) OO 不是什么东西都是对象的死板概念。
  • 当然可以对静态方法进行单元测试。但是如何对调用静态方法的方法进行单元测试呢?
  • @dimadima 这里也有同样的想法
  • 我认为静态方法是一种绕过 java 限制的方法,即所有东西都需要在类定义中。正如我们已经从大量函数式编程语言中看到的那样,纯函数很好。
【解决方案7】:

如果方法不修改特定的实例,也就是说,如果方法不改变对象的实例变量,那么您可以将方法设为静态。如果您将所有方法设为静态,那么所有变量外部方法也应该是静态的。

关于线程,据我所知,不同之处在于您想要同步代码的时间。例如,您同步或锁定对象的实例方法,但是对于静态方法,您需要在类变量上同步。所以在特定的在你的课堂上,只有一个同步块将被执行。 (例如实例方法,对于 n 个实例,可以执行 n 个同步块)。

为了表现 在静态方法的情况下,绑定是在编译时的,而实例方法的绑定是动态的,即在运行时。编译器也会隐式地插入一个 this 实例方法。所以静态方法比实例方法快一点。

在继承中,静态方法不能被覆盖,但是你总是可以直接用类调用方法,所以没问题。

因此,如果您不实例化该类,则将方法设为静态并将其用作帮助类是一个好主意。

【讨论】:

    猜你喜欢
    • 2012-07-14
    • 2013-05-08
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-01
    • 2013-11-28
    • 1970-01-01
    相关资源
    最近更新 更多