一个经验法则:问问自己“调用这个方法是否有意义,即使还没有构建 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,如果您不小心,最终会遇到很多 并发错误和/或瓶颈。
所以只在以下场景中定义静态方法:
- 如果您正在编写实用程序类并且它们不应该被更改。
- 如果方法没有使用任何实例变量。
- 如果任何操作不依赖于实例创建。
- 如果有一些代码可以很容易地被所有实例方法共享,请将该代码提取到静态方法中。
- 如果您确定方法的定义永远不会被更改或覆盖。因为静态方法不能被覆盖。
让我们更详细地讨论它:
优点:
静态成员/方法在帮助类中使用,比如数学或常量类。这有助于其他对象利用字符串或有用的函数,您不需要为其创建对象但使用类名调用它们。示例 – 使用静态函数调用单例对象。
缺点:
静态成员是类的一部分,因此在应用程序终止之前一直保留在内存中,并且永远不会被垃圾回收。使用过多的静态成员有时会预测您无法设计产品并试图应对静态/过程编程。它表示面向对象的设计受到了损害。这会导致内存溢出。如果您在 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