Java: Как получить объект класса текущего класса из статического контекста? - PullRequest
11 голосов
/ 20 августа 2009

У меня есть функция регистрации, которая принимает вызывающий объект в качестве параметра. Затем я вызываю getClass (). GetSimpleName () для него, чтобы я мог легко получить имя класса для добавления в мою запись в журнале для удобства использования. Проблема в том, что когда я вызываю свою функцию журнала из статического метода, я не могу передать «this». Моя функция журнала выглядит примерно так:

public static void log(Object o, String msg){
  do_log(o.getClass().getSimpleName()+" "+msg);
}
public void do_something(){
  log(this, "Some message");
}

Но допустим, я хочу войти из статической функции:

public static void do_something_static(){
  log(this, "Some message from static");
}

Очевидно, что do_something_static () не будет работать, потому что он статический и «this» не находится в статическом контексте. Как я могу обойти это? И могу ли я сделать это без использования рефлексии (поскольку я понимаю, что это связано с большими накладными расходами, и это может повлиять на производительность, поскольку я регистрирую МНОГО данных)

Я знаю, что, возможно, могу каким-то образом жестко закодировать текущий класс в вызов, но я уверен, что когда я переместу функцию в другой класс, я забуду обновить жестко заданную ссылку, и она больше не будет правильный.

Спасибо!

Ответы [ 8 ]

14 голосов
/ 20 августа 2009

Вы можете добавить «Класс» в качестве первого параметра и перегрузить метод журнала:

public class SomeClass {
    // Usage test:
    public static void main( String [] args ) {
        log( SomeClass.class, "Hola" );
        log( new java.util.Date(), "Hola" );
    }

    // Object version would call Class method... 
    public static void log( Object o , String msg ) {
        log( o.getClass(), msg );
    }
    public static void log( Class c ,  String message ) {
        System.out.println( c.getSimpleName() + " " + message );
    }
}

Выход:

$ java SomeClass
SomeClass Hola
Date Hola

Но плохо, когда вызывающий класс передается в качестве первого аргумента. Вот где объектно-ориентированная модель вступает в игру как противоположность «процедурному» стилю.

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

Но если вы создаете переменную класса, тогда для класса будет только один экземпляр, если у вас будет 1000 классов, использующих эту утилиту, у вас будет не более 1000 вызовов.

Что-то вроде этого было бы лучше (тонкий вариант этого другого ответа):

public class LogUtility {

    private final String loggingFrom;

    public static LogUtility getLogger() {
        StackTraceElement [] s = new RuntimeException().getStackTrace();
        return new LogUtility( s[1].getClassName() );
    }

    private LogUtility( String loggingClassName ) {
        this.loggingFrom = "("+loggingClassName+") ";
    }

    public void log( String message ) {
        System.out.println( loggingFrom + message );
    }
}

Тест использования:

class UsageClass {
    private static final LogUtility logger = LogUtility.getLogger();

    public static void main( String [] args ) {

        UsageClass usageClass = new UsageClass();

        usageClass.methodOne();
        usageClass.methodTwo();
        usageClass.methodThree();

    }
    private void methodOne() {
        logger.log("One");
    }
    private void methodTwo() {
        logger.log("Two");
    }
    private void methodThree() {
        logger.log("Three");
    }
}

Мощно

$ java UsageClass
(UsageClass) One
(UsageClass) Two
(UsageClass) Three

Обратите внимание на объявление:

....
class UsageClass {
    // This is invoked only once. When the class is first loaded.
    private static final LogUtility logger = LogUtility.getLogger();
....

Таким образом, не имеет значения, используете ли вы «регистратор» из objectA, objectB, objectC или из метода класса (например, main), у всех них будет один экземпляр регистратора.

7 голосов
/ 20 августа 2009

запросы на трассировку стека могут быть бесполезными для вашей проблемы. У вас есть 3 возможных подхода.

Исключение # getStackTrace ()

Просто создайте экземпляр исключения и получите первый кадр:

static String className() {
    return new RuntimeException().getStackTrace()[0].getClassName();
}

Тема

Использование Thread стало еще проще:

static String className2() {
    return Thread.currentThread().getStackTrace()[1].getClassName();
}

у этих двух подходов есть недостаток: их нельзя использовать повторно. Поэтому вы можете определить класс Helper для этого:

class Helper {

    public static String getClassName() {
        return Thread.currentThread().getStackTrace()[2].getClassName();
    }
} 

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

public static void log(Object o, String msg){
    do_log(Helper.getCClassName() + "  " + msg);
}
2 голосов
/ 20 августа 2009

Изменить метод log до:

public static void log(Class c, String msg){
  do_log(c.getSimpleName()+" "+msg);
}

и если do_something_static находится в классе MyClassWithStatics, то do_something_static станет:

public static void do_something_static(){
  log(MyClassWithStatics.class, "Some message from static");
}
1 голос
/ 20 августа 2009

Вместо «this» используйте «MyClass.class», и пусть ваш метод журнала обрабатывает объекты класса без getClass ().

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

0 голосов
/ 26 февраля 2014

Не реализует ли какой-нибудь Singleton то, что вам нужно?

public class LogUtility {

    private final String loggingFrom;
    private static LogUtility instance;

    public static LogUtility getLogger() {
        if(instance == null)
            this.instance = new LogUtility();
        StackTraceElement [] s = new RuntimeException().getStackTrace();
        this.instance.setLoggingFrom(s[1].getClassName())
        return this.instance;
    }

    private LogUtility() {}

    private void setLoggingFrom(String loggingClassName){
        this.loggingFrom = loggingClassName;
    }

    public void log( String message ) {
        System.out.println( loggingFrom + message );
    }
}

Использование (в любом месте вашего проекта):

LogUtility.getLogger().log("Message");
0 голосов
/ 20 августа 2009

Замените ваши статические функции на нестатические. Вместо

Utils.do_something(...)

сделать

new Utils().do_something(...)
0 голосов
/ 20 августа 2009

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

0 голосов
/ 20 августа 2009

Вы должны жестко закодировать класс в вызове в краткосрочной перспективе. Если вы думаете, что вам это понадобится в разных местах вашего проекта, например, static, вы должны инкапсулировать его и принять класс в качестве параметра.

public static void do_something_static(Class callingClass){
  log(callingClass, "Some message from static");
}
...