Как вызвать getClass () из статического метода в Java?


350

У меня есть класс, который должен иметь некоторые статические методы. Внутри этих статических методов мне нужно вызвать метод getClass (), чтобы сделать следующий вызов:

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

Однако Затмение говорит мне:

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

Как правильно исправить ошибку во время компиляции?


Использование getResource()до того, как будет создан экземпляр пользовательского (например, не J2SE) класса, иногда будет неудачным. Проблема в том, что JRE будет использовать загрузчик классов начальной загрузки на этом этапе, у которого не будет ресурсов приложения на пути к классам (загрузчика начальной загрузки).
Эндрю Томпсон

Ответы:


617

Ответ

Просто используйте TheClassName.classвместо getClass().

Объявление лесорубов

Поскольку этому уделяется так много внимания для конкретного варианта использования - чтобы обеспечить простой способ вставки объявлений журнала - я решил добавить свои мысли по этому поводу. Каркасы журналов часто ожидают, что журнал будет ограничен определенным контекстом, скажем, полным именем класса. Таким образом, они не могут быть скопированы без изменений. Предложения для вставки безопасных объявлений журнала представлены в других ответах, но у них есть недостатки, такие как раздувание байт-кода или добавление самоанализа во время выполнения. Я не рекомендую это. Копирование-вставка - это проблема редактора , поэтому решение для редактора является наиболее подходящим.

В IntelliJ я рекомендую добавить живой шаблон:

  • Используйте «журнал» в качестве сокращения
  • Используйте private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger($CLASS$.class);в качестве текста шаблона.
  • Нажмите Edit Variables и добавьте CLASS, используя выражение className()
  • Установите флажки для переформатирования и сокращения имен FQ.
  • Измените контекст на Java: объявление.

Теперь, если вы напечатаете, log<tab>он автоматически расширится до

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

И автоматически переформатировать и оптимизировать импорт для вас.


6
Это приведет к копированию / вставке и печальным результатам, когда неправильный код выполняется в другом классе.
Уильям Энтрикен,

1
@WilliamEntriken: использование анонимных внутренних классов не является хорошим решением, оно просто увеличивает ваш байт-код дополнительными файлами классов, чтобы сэкономить пару секунд усилий разработчика. Я не уверен, что люди делают, где им приходится копировать / вставлять повсюду, но это следует рассматривать с помощью решения для редактора, а не языкового взлома. Например, если вы используете IntelliJ, используйте Live Template, который может вставить имя класса.
Марк Питерс

1
Мне действительно интересно, почему вы получили 4 отрицательных голоса
Аль-Мотафар

«Я не уверен, что люди делают, где им приходится копировать / вставлять повсюду» - что-то вроде использования Logв Android, для которого требуется предоставить тег, обычно имя класса. И, возможно, есть и другие примеры. Конечно, вы можете объявить поле для этого (что является обычной практикой), но это все еще копирование и вставка.
user149408

1
Вы забыли одну вещь в своем решении Live Template: нажмите «Изменить переменные» и в выражении для CLASSввода className(). Это обеспечит замену $ CLASS $ на имя класса, иначе он просто будет пустым.
HerbCSO

122

Что касается примера кода в вопросе, стандартное решение состоит в том, чтобы явно ссылаться на класс по его имени, и даже можно обойтись без getClassLoader()вызова:

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

У этого подхода есть обратная сторона: он не очень безопасен от ошибок копирования / вставки в случае, если вам нужно скопировать этот код на ряд похожих классов.

А что касается точного вопроса в заголовке, в соседней теме есть трюк :

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

Он использует вложенный анонимный Objectподкласс для получения контекста выполнения. Преимущество этого трюка в том, что он защищен от копирования / вставки ...

Будьте осторожны при использовании этого в Базовом классе, который наследуют другие классы:

Также стоит отметить, что если этот фрагмент сформирован как статический метод некоторого базового класса, тогда currentClassvalue всегда будет ссылкой на этот базовый класс, а не на любой подкласс, который может использовать этот метод.


23
Безусловно, мой любимый ответ. NameOfClass.class очевиден, но для этого необходимо знать имя вашего класса. Трюк getEnclosingClass полезен не только для вставки копий, но и для статических методов базового класса, которые используют самоанализ
Доминго Игнасио

1
Кстати Class.getResource()может вести себя немного иначе ClassLoader.getResource(); см. stackoverflow.com/a/676273
augurar

68

В Java7 + вы можете сделать это в статических методах / полях:

MethodHandles.lookup().lookupClass()

Хорошее решение, если вам нужен только вызов getSimpleName () для вывода справки из метода main .
Дмитрий Булашевич

6
Я искал решение «копировать-вставить» для добавления регистратора везде, не меняя имя класса каждый раз. Вы дали это мне: private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Жюльен Фениу

Лучшее решение там, где оно поддерживается, недостатком является то, что Android не поддерживает его до API 26, то есть Android 8.
user149408

24

Я боролся с этим сам. Хорошая хитрость заключается в использовании текущего потока для получения ClassLoader в статическом контексте. Это будет работать и в Hadoop MapReduce. Другие методы работают при локальном запуске, но возвращают нулевой InputStream при использовании в MapReduce.

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


11

getClass() Метод определен в классе Object со следующей сигнатурой:

открытый финальный класс getClass ()

Поскольку он не определен как static, вы не можете вызывать его в статическом кодовом блоке. Посмотрите эти ответы для получения дополнительной информации: Q1 , Q2 , Q3 .

Если вы находитесь в статическом контексте, то вы должны использовать буквальное выражение класса, чтобы получить класс, поэтому вы должны сделать следующее:

Foo.class

Этот тип выражения называется литералами класса, и они объясняются в книге спецификаций языка Java следующим образом:

Литерал класса - это выражение, состоящее из имени класса, интерфейса, массива или примитивного типа, за которым следует `. ' и класс токенов. Тип литерала класса - Класс. Он оценивает объект Class для именованного типа (или для void), как это определено загрузчиком класса, определяющего класс текущего экземпляра.

Вы также можете найти информацию по этому вопросу в документации API для Class.


9

Попробуй это

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

Или

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

Прелесть этого подхода в том, что он также будет работать на версиях Android до 8 (API 26). Остерегайтесь того, что этот индекс [1]приведет к тому, что все сообщения журнала будут сообщаться Threadкак их источник на Android 4.4.4. [2]похоже, дает правильный результат на Android и OpenJRE.
user149408

0

Предположим, что есть класс Utility, тогда пример кода будет:

    URL url = Utility.class.getClassLoader().getResource("customLocation/".concat("abc.txt"));

CustomLocation - если в ресурсах есть какая-либо структура папок, удалите этот строковый литерал.


-2

Попробуйте что-то вроде этого. Меня устраивает. Logg (Имя класса)

    String level= "";

    Properties prop = new Properties();

    InputStream in =
            Logg.class.getResourceAsStream("resources\\config");

    if (in != null) {
        prop.load(in);
    } else {
        throw new FileNotFoundException("property file '" + in + "' not found in the classpath");
    }

    level = prop.getProperty("Level");

1
Не уверен, почему вы даете один и тот же ответ, который уже есть в этой теме три раза: дважды с 2011 года, один раз с 2013 года.
Antares42

Это решение работает, если класс Logg загружается загрузчиком классов с доступом к папке «resources \\ config». На некоторых серверах, которые имеют несколько загрузчиков классов (например, один загрузчик классов для загрузки папки lib и другой для загрузки библиотек приложений и ресурсов), это не так. По этой причине решение poupose для этого ответа работает только иногда по совпадению!
Мануэль Ромейро
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.