为什么我在Java中收到有关实用程序类的警告?

49

我正在学习Java和OOPS,在eclipse中编写一个基本的Hello World程序时,我看到一个黄色三角形,告诉我“实用类不应该有公共或默认构造函数”。我没有理解为什么会发生这种情况,它是什么意思?我做错了什么吗?

class HelloWorld {


public static void main(String[] args)
{
    // TODO Auto-generated method stub
            System.out.println("Hola Mundo!");

}


  }

编辑1:根据建议更改了代码。

final class HelloWorld {


private HelloWorld()
{
    throw new AssertionError("Instantiating utility class...");

}
public static void main(String[] args)
{
    // TODO Auto-generated method stub
            System.out.println("Hola Mundo!");

}


}

仍然在Class HelloWorld行收到警报。

编辑2:

创建了一个新类,现在它可以工作了。谢谢Jon。为什么旧类仍然会有警告?Bohemian,我仍然不清楚您在帖子中提到的概念。一旦我有更好的理解,我会回来看看的。感谢您的解释。


为什么用户实例化一个实用类很重要呢?他们很快就会发现它没有任何作用并删除实例化代码。另一方面,私有构造函数将成为死代码。它要么会降低您的测试覆盖率,要么会迫使您编写一个测试(使用丑陋的反射技巧),测试死代码。 - Ville Oikarinen
6个回答

70

这意味着有人可以写:

HelloWorld helloWorld = new HelloWorld();

当你可能不希望它们这样做时 - 因为你没有提供任何实例成员,所以为什么要允许它们创建实例?请将您的代码改写为:

final class HelloWorld {

    private HelloWorld() {
        // Prevent instantiation
        // Optional: throw an exception e.g. AssertionError
        // if this ever *is* called
    }

    public static void main(String[] args) {
        System.out.println("Hola Mundo!");
    }
}

1
@Bohemian:这是一个异常,但不是Exception。有区别。例如,来自JLS的说明:“未经检查的异常类是RuntimeException及其子类,以及Error类及其子类。” - Jon Skeet
2
@Jon - 好的,你用JLS规范抓住了我 :) - Bohemian
3
我讨厌这个Checkstyle警告,它没有任何用处。如果一个实用类的实例化被创建,那么实例化者应该受到指责--标记它,而不是标记一个完全合理的实用类并强迫我添加一个丑陋的虚构构造函数。 - john16384
6
@john16384: 我强烈反对。你应该尽可能让你的 API 易于正确使用,而尽可能难以错误使用。如果一个操作没有意义,就禁止它。当然,如果有语言支持这样做会更好,例如 C# 中的静态类。 - Jon Skeet
这是另一个荒谬的PMD规则。此外,解决方案将产生更糟糕的覆盖率,因为私有构造函数从未被调用。 - bebbo
显示剩余9条评论

24

Jon的回答是正确的,但你也应该知道每个类都有一个构造函数,无论你是否声明它。如果你没有声明构造函数,则“默认构造函数”会隐式地为你定义。

换句话说,这两个类的行为是相同的:

public class ClassA {
}


public class ClassB {
    public ClassB() {
    }
}

小心:拥有私有构造函数并不能防止实例化!

而且,仅仅拥有私有构造函数并不能防止其他人对你的类进行实例化,只是让它变得更难了:

至少有两种方法可以规避这个问题:

声明一个工厂方法(显而易见,但如果是你公司的代码,有人也可以这么做):

public class ClassA {
    private ClassA() {}

    public static ClassA create() {
        return new ClassA(); // oops! this one got away
    }
}
使用反射(有些偷懒,而且似乎不太对,但它能正常工作!):
public class ClassA {
    private ClassA() {}
}

// Elsewhere, in another class across town:
Constructor<?> constructor = ClassA.class.getDeclaredConstructor();
// constructor private? no problem... just make it not private!
constructor.setAccessible(true); // muhahahaha
Object obj = constructor.newInstance();
System.out.println(obj.getClass().getSimpleName());  // Prints ClassA!

确保没有人创建实例的唯一方法是在构造函数中抛出异常(最好将其设置为私有):

public class ClassA {
    private ClassA() {
        throw new UnsupportedOperationException();
    }
}

9
为了完整性,迄今为止,没有回复者评论您原始类的另一个问题。
class HelloWorld {

你将其转化成了建议的内容,如下所示:

你根据建议将其转化为

final class HelloWorld {

Java类上的final关键字禁止其他类扩展(继承)它。如果你不将一个类设置为final,那么一个继承这个类的类可以使用你可能没有想到的各种东西,因为继承类可以访问所继承类的protected成员。
通常最好将你的类设为abstract或者final,表示它们只能用于继承或不可被继承。当你设计一个既可实例化(非抽象)又可扩展(非final)的类时,也许有很多这样的情况,但是养成有意识地做出这种决定的好习惯是非常重要的。

8

编写工具类的最简单方法是使用没有实例的枚举。您可以在Java 5.0及以上版本中完成此操作。

public enum Utility {;
    public static void main(String... args) {
        System.out.println("Hola Mundo!");
    }
}

你不必定义私有构造函数,也不能使用内部类或反射等技巧创建实例。(如果忽略Unsafe类 ;)

顺便说一下:有些人认为使用枚举是一种hack,但我认为它明确表示“我有一个没有实例的类”,而不是间接防止实例被创建。


1
最简单且可能是最好的。 - user454322
4
注意在这个枚举的左括号后面隐藏着一个分号?这是为了表示一个空枚举而需要的。我认为这有点棘手。 - Adriaan Koster

2

2
一个新手使用CheckStyle的情况是非常不可能的,但你提供的链接已经足够了。 - anergy
@anergy,我现在正在使用Checkstyle,当我将鼠标悬停在上面时,我也看到一个黄色三角形显示相同的消息。我不认为我曾经注意到这个警告只出现在Eclipse或RSA中。无论如何,geff_chang的链接很有帮助。 - hotshot309

0
针对Spring应用程序,请尝试以下操作:
创建一个配置文件,以抑制文件检查,checkstyle-suppressions.xml
 <?xml version="1.0"?>

<!DOCTYPE suppressions PUBLIC
        "-//Puppy Crawl//DTD Suppressions 1.0//EN"
        "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">

<suppressions>
    <suppress files="SpringBootApplication.java" checks="HideUtilityClassConstructor" />
</suppressions>

maven-checkstyle-plugin 插件配置中,添加 checkstyle-suppressions.xml 文件的位置。
    <configuration>
        <configLocation>quality/checkstyle.xml</configLocation>
        <suppressionsLocation>quality/checkstyle-suppressions.xml</suppressionsLocation>

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