如何在Java的静态方法中调用getClass()方法?

422

我有一个必须包含一些静态方法的类。在这些静态方法内,我需要调用方法getClass()以进行以下调用:

public static void startMusic() {
  URL songPath = getClass().getClassLoader().getResource("background.midi");
}

然而Eclipse告诉我:

Cannot make a static reference to the non-static method getClass() 
from the type Object

如何适当地解决这个编译时错误?


在实例化用户定义的(例如非J2SE的)类之前使用getResource()有时会失败。问题在于此时JRE将使用引导类加载器,该加载器不会在引导加载器的类路径上具有应用程序资源。 - Andrew Thompson
9个回答

714

答案

只需使用TheClassName.class而不是getClass()

声明日志记录器

由于这个问题在特定用例中如此受到关注——提供一种插入日志声明的简单方法——我想加上我的看法。日志框架通常期望将日志限制在特定的上下文中,比如一个完全限定的类名。因此,它们不能直接复制粘贴而不经过修改。其他答案提供了一些安全复制粘贴日志声明的建议,但它们有缺点,比如膨胀字节码或添加运行时内省。我不建议这样做。复制和粘贴是编辑器的问题,因此最适合的解决方案是一个编辑器解决方案。

在IntelliJ中,我建议添加一个Live Template:

  • 使用"log"作为缩写
  • 使用private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger($CLASS$.class);作为模板文本。
  • 点击编辑变量并添加CLASS,使用表达式className()
  • 勾选重新格式化和缩短FQ名称的框。
  • 将上下文更改为Java: declaration。

现在,如果您键入log<tab>,它将自动展开为

private static final Logger logger = LoggerFactory.getLogger(ClassName.class);

并自动为您重新格式化和优化导入。


11
当错误的代码在不同的类中运行时,会导致复制/粘贴和悲惨的结果。 - William Entriken
1
@WilliamEntriken:使用匿名内部类并不是解决这个问题的好方法,它只会让你的字节码膨胀,为了节省几秒钟的开发人员工作而产生额外的类文件。我不确定人们在做什么,需要到处复制/粘贴,但应该用编辑器解决方案来处理,而不是语言黑客。例如,如果使用IntelliJ,则可以使用Live Template插入类名。 - Mark Peters
“我不确定人们在做什么,以至于他们必须到处复制/粘贴”——例如在Android中使用Log,需要提供一个标签,通常是类名。还有可能有其他的例子。当然,您可以为此声明一个字段(这是常见的做法),但这仍然是复制和粘贴。 - user149408
@user149408:是的,这个问题已经被讨论了很多次。虽然这个用例与原始问题实际上没有什么关系,但这是人们访问这个问题的常见原因,所以我在答案中添加了一些想法。 - Mark Peters
1
你在 Live Template 解决方案中忘记了一件事:点击“编辑变量”,在 CLASS 的表达式中输入 className()。这将确保 $CLASS$ 实际上被替换为类名,否则它将为空。 - HerbCSO
如果类是抽象的,ClassName.class不能解决静态引用问题。当扩展抽象类时,ClassName.class将引用抽象类而不是子类,而非静态方法中的this.getClass()将引用子类。 - dzimney

135

至于问题中的代码示例,标准解决方案是通过类名显式引用该类,甚至可以在不调用getClassLoader()的情况下完成:

class MyClass {
  public static void startMusic() {
    URL songPath = MyClass.class.getResource("background.midi");
  }
}

这种方法仍有一个不足之处,即在需要将此代码复制到许多相似的类时,它对于复制/粘贴错误不是非常安全。

至于标题中的确切问题,在相邻的线程中发表了一个技巧:

Class currentClass = new Object() { }.getClass().getEnclosingClass();

它使用嵌套的匿名Object子类来获取执行上下文。这个技巧有一个好处,就是可以进行复制/粘贴操作而不会出错...

在基类中使用时需注意:

值得注意的是,如果此代码片段被形成为某个基类的静态方法,则currentClass值将始终是对该基类的引用,而不是任何可能使用该方法的子类的引用。


27
迄今为止,这是我最喜欢的答案。NameOfClass.class显而易见,但这要求你知道类的名称。getEnclosingClass技巧不仅适用于复制粘贴,还适用于利用内省的静态基类方法。 - Domingo Ignacio
2
顺便提一下,Class.getResource() 的行为可能与 ClassLoader.getResource() 稍有不同;请参见 https://dev59.com/73RB5IYBdhLWcg3wQVSV#676273。 - augurar

89

在Java7+中,您可以在静态方法/字段中执行此操作:

MethodHandles.lookup().lookupClass()

如果你只需要在_main_方法中调用_getSimpleName()_来打印帮助信息,那么这是一个不错的解决方案。 - Dmitrii Bulashevich
11
我在寻找一种“复制粘贴”的方法,可以在代码的任何位置添加记录器,而无需每次更改类名。你给了我这个解决方案: private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - Julien Feniou
最佳解决方案是在支持的情况下使用,缺点是Android直到API 26即Android 8才支持此功能。 - user149408

27

我自己也曾经遇到这个问题。一个不错的技巧是在静态上下文中使用当前线程来获取ClassLoader。这个方法同样适用于Hadoop MapReduce。其他方法在本地运行时有效,但在MapReduce中使用时返回null InputStream。

public static InputStream getResource(String resource) throws Exception {
   ClassLoader cl = Thread.currentThread().getContextClassLoader();
   InputStream is = cl.getResourceAsStream(resource);
   return is;
}

16

只需使用类字面值,即NameOfClass.class


13

试一试

Thread.currentThread().getStackTrace()[1].getClassName()

或者

Thread.currentThread().getStackTrace()[2].getClassName()

这种方法的优点是它也适用于Android 8(API 26)之前的版本。但请注意,在Android 4.4.4上,索引[1]会导致所有日志消息报告Thread作为它们的源。[2]似乎在Android和OpenJRE上都给出了正确的结果。 - user149408

11

getClass()方法是在Object类中定义的,其签名如下:

public final Class getClass()

由于它没有被定义为静态方法,因此您无法在静态代码块内调用它。有关更多信息,请参见以下答案:Q1, Q2, Q3

如果您处于静态上下文中,则必须使用类字面表达式来获取Class类型,因此您基本上必须像这样做:

Foo.class

这种类型的表达式称为类文字,它们在Java语言规范书中解释如下:

类文字是由类、接口、数组或原始类型的名称组成的表达式,后面跟着一个点和标记“class”。类文字的类型是Class。它将计算为当前实例的定义类加载器定义的命名类型(或void)的Class对象。

您也可以在Class的API文档中找到有关此主题的信息。


3

我也遇到了相同的问题!但只需按以下方式修改代码即可解决。

public static void startMusic() {
URL songPath = YouClassName.class.getClassLoader().getResource("background.midi");
}

这对我很好用,希望对你也同样有用。


“YourClassName”是什么?应该改成“startMusic”吗?有没有避免指定类名的方法,我们能不能只使用“this.class.getClassLoader()”呢? - holms

0
假设有一个实用类,那么示例代码如下:
    URL url = Utility.class.getClassLoader().getResource("customLocation/".concat("abc.txt"));

CustomLocation - 如果资源中没有任何文件夹结构,则删除此字符串文字。


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