使所有方法都保持静态

2022-09-04 05:59:49

在我的应用程序中,有许多类包含用于处理数据的方法,这些方法可以进行计算和数据扩充。

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

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

有什么后果吗?是否有任何性能优势,因为我不必实例化类?

示例类:

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

我打算将上述内容更改为静态。


答案 1

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

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

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

虽然有一些有效的理由使用静态方法:

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

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

  • 添加方法:你真的希望类 String 有一个 removeSpecialChars() 实例方法,但它不存在(也不应该存在,因为你项目的特殊字符可能与其他项目的特殊字符不同),并且你不能添加它(因为 Java 是最低限度的),所以你创建了一个实用程序类,并调用 removeSpecialChars 而不是 s.removeSpecialChars(s)。甜。

  • 纯度:采取一些预防措施,你的静态方法将是一个纯函数,也就是说,它唯一依赖的是它的参数。数据输入,数据输出。这更易于阅读和调试,因为您不必担心继承怪癖。您也可以使用实例方法执行此操作,但编译器将使用静态方法(通过不允许引用实例属性,重写方法等)为您提供更多帮助。

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

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

因此,仅在以下方案中定义静态方法:

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

让我们也更详细地讨论它:

优势:

静态成员/方法的使用方式与在帮助器类中一样,如 Math 或常量类。这有助于其他对象利用字符串或有用的函数,您不需要为其创建对象,而是使用类名调用。示例 – 使用静态函数调用单例对象。

弊:

静态成员是类的一部分,因此保留在内存中,直到应用程序终止并且无法进行垃圾回收。使用过多的静态成员有时会预测您无法设计产品并试图应对静态/过程编程。它表示面向对象设计遭到破坏。这可能会导致内存溢出。此外,如果您在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


答案 2

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


推荐