Як знайти абонента методу, використовуючи стек-тракцію або відображення?


391

Мені потрібно знайти абонента методу. Чи можливо за допомогою стек-трактування або відображення?


5
Просто цікаво, але навіщо вам це потрібно робити?
Джульєтта

2
У мене є клас Батьків (модель MVC) з подією сповіщувача, і лише метод, який встановлює мої підкласи, називає цей метод. я не хочу засмічувати свій код зайвим аргументом. Я б краще дозволити методу в батьківському класі розібратися у сеттера, який його викликав.
Сатіш

30
@Sathish Здається, що вам слід подумати про те, що дизайн
krosenvold

7
@Juliet У рамках рефакторингу великого патрона коду нещодавно я змінив метод, який використовується багатьма речами. Існує певний спосіб виявити, чи код використовував новий метод належним чином, тому я друкував номер класу та рядка, які називали його в тих випадках. Поза веденням журналу я не бачу реальної мети для подібного. Хоча я начебто хочу написати API зараз, що кидає a, DontNameYourMethodFooExceptionякщо метод виклику називається foo.
Cruncher

5
Мені здається, що я можу отримати абонента свого методу неоціненним інструментом налагодження: саме таким чином веб-пошук привів мене сюди. Якщо мій метод викликається з декількох місць, чи викликається він з потрібного місця в потрібний час? Поза налагодження чи ведення журналу корисність, мабуть, в кращому випадку обмежена, як зазначає @Cruncher.
Огрійський псалом33

Відповіді:


412
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()

Відповідно до Javadocs:

Останній елемент масиву являє собою нижню частину стека, що є найменшим викликом методу в послідовності.

StackTraceElementМає getClassName(), getFileName(), getLineNumber()і getMethodName().

Вам доведеться поекспериментувати, щоб визначити, який індекс ви хочете (можливо, stackTraceElements[1]чи [2]).


7
Слід зазначити, що getStackTrace () все ще створює Виняток, тому це насправді не швидше - просто зручніше.
Майкл Майерс

41
Зауважте, що цей метод не дасть вам абонента, а лише тип виклику . Ви не матимете посилання на об'єкт, що викликає ваш метод.
Йоахім Зауер

3
Просто бічна примітка, але на 1.5 JVM Thread.currentThread (). GetStackTrace () здається набагато повільніше, ніж створення нового винятку () (приблизно в 3 рази повільніше). Але, як уже зазначалося, у жодному разі не слід використовувати такий код у критичній продуктивності. ;) 1.6 JVM здається приблизно на 10% повільнішим, і, як сказала Software Monkey, він виражає наміри краще, ніж спосіб "новий виняток".
GaZ

21
@Eelco Thread.currentThread () - дешево. Thread.getStackTrace () коштує дорого, тому що, на відміну від Throwable.fillInStackTrace (), немає гарантії, що метод викликається тією ж ниткою, яку він вивчає, тому JVM повинен створити "безпечну точку" - блокування купи та стека. Дивіться цей звіт про помилку: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6375302
Девід

7
@JoachimSauer Ви знаєте спосіб отримати посилання на об'єкт, що викликає метод?
Джофд

216

Альтернативне рішення можна знайти в коментарі до цього запиту щодо вдосконалення . Він використовує getClassContext()метод custom SecurityManagerі здається, що він швидший, ніж метод стеження стека.

Наступна програма тестує швидкість різних запропонованих методів (найцікавіший біт - у внутрішньому класі SecurityManagerMethod):

/**
 * Test the speed of various methods for getting the caller class name
 */
public class TestGetCallerClassName {

  /**
   * Abstract class for testing different methods of getting the caller class name
   */
  private static abstract class GetCallerClassNameMethod {
      public abstract String getCallerClassName(int callStackDepth);
      public abstract String getMethodName();
  }

  /**
   * Uses the internal Reflection class
   */
  private static class ReflectionMethod extends GetCallerClassNameMethod {
      public String getCallerClassName(int callStackDepth) {
          return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
      }

      public String getMethodName() {
          return "Reflection";
      }
  }

  /**
   * Get a stack trace from the current thread
   */
  private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Current Thread StackTrace";
      }
  }

  /**
   * Get a stack trace from a new Throwable
   */
  private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {

      public String getCallerClassName(int callStackDepth) {
          return new Throwable().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Throwable StackTrace";
      }
  }

  /**
   * Use the SecurityManager.getClassContext()
   */
  private static class SecurityManagerMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return mySecurityManager.getCallerClassName(callStackDepth);
      }

      public String getMethodName() {
          return "SecurityManager";
      }

      /** 
       * A custom security manager that exposes the getClassContext() information
       */
      static class MySecurityManager extends SecurityManager {
          public String getCallerClassName(int callStackDepth) {
              return getClassContext()[callStackDepth].getName();
          }
      }

      private final static MySecurityManager mySecurityManager =
          new MySecurityManager();
  }

  /**
   * Test all four methods
   */
  public static void main(String[] args) {
      testMethod(new ReflectionMethod());
      testMethod(new ThreadStackTraceMethod());
      testMethod(new ThrowableStackTraceMethod());
      testMethod(new SecurityManagerMethod());
  }

  private static void testMethod(GetCallerClassNameMethod method) {
      long startTime = System.nanoTime();
      String className = null;
      for (int i = 0; i < 1000000; i++) {
          className = method.getCallerClassName(2);
      }
      printElapsedTime(method.getMethodName(), startTime);
  }

  private static void printElapsedTime(String title, long startTime) {
      System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
  }
}

Приклад виходу з мого 2,4-ГГц Intel Core 2 Duo MacBook під керуванням Java 1.6.0_17:

Reflection: 10.195 ms.
Current Thread StackTrace: 5886.964 ms.
Throwable StackTrace: 4700.073 ms.
SecurityManager: 1046.804 ms.

Метод внутрішнього відображення набагато швидший, ніж інші. Отримання сліду стека від новоствореного Throwableшвидше, ніж отримання його з поточного Thread. І серед не внутрішніх способів пошуку класу виклику звичай SecurityManagerздається найшвидшим.

Оновлення

Як lyomi вказує на цей коментарsun.reflect.Reflection.getCallerClass() метод був відключений за замовчуванням в Java 7 оновлень 40 і повністю видалені в Java 8. Більш детально про це в цьому питанні в базі даних помилок Java .

Оновлення 2

Як з'ясували zammbi , Oracle змушений був відмовитися від змін, які усунули sun.reflect.Reflection.getCallerClass(). Він все ще доступний у Java 8 (але він застарілий).

Оновлення 3

3 роки після: Оновлення часу щодо поточного JVM.

> java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
> java TestGetCallerClassName
Reflection: 0.194s.
Current Thread StackTrace: 3.887s.
Throwable StackTrace: 3.173s.
SecurityManager: 0.565s.

5
Так, здається, що так. Але зауважте, що терміни, які я даю у прикладі, - це мільйон дзвінків - тому залежно від того, яким чином ви користуєтесь цим, це може не бути проблемою.
Йоган Кавінг

1
Для мене видалення рефлексії з мого проекту призвело до збільшення в 10 разів швидкості.
Кевін Паркер

1
Так, відображення загалом повільне (див., Наприклад, stackoverflow.com/questions/435553/java-reflection-performance ), але в цьому конкретному випадку використання внутрішнього класу sun.reflect.Reflection є найшвидшим.
Йоган Кавінг

1
Це насправді не потрібно. Ви можете перевірити це, змінивши код вище, щоб надрукувати повернене className (і я пропоную зменшити кількість циклу до 1). Ви побачите, що всі методи повертають один і той же класName - TestGetCallerClassName.
Йоган Кавінг

1
getCallerClass застарілий і буде видалений у 7u40 .. сумно :(
lyomi

36

Здається, ви намагаєтеся уникати посилання thisна метод. Пройти thisнабагато краще, ніж знайти абонента через поточний слід стека. Реконструкція на більш OO дизайн ще краще. Вам не потрібно знати особу, що телефонує. Якщо потрібно, передайте об'єкт зворотного дзвінка.


6
++ Знання абонента - це занадто багато інформації. Якщо потрібно, ви можете передати інтерфейс, але є велика ймовірність, що потрібен великий рефакторинг. @satish повинен опублікувати його код і давайте повеселитися з ним :)
Білл К

15
Дійсні причини, які хочуть зробити це, існують. У мене були кілька випадків, коли я вважав це корисним, наприклад, під час тестування.
Eelco

2
@chillenious Я знаю :) Я це зробив сам, щоб створити такий метод, як, LoggerFactory.getLogger(MyClass.class)де мені не довелося проходити в класі буквально. Це все ще рідко правильно робити.
Крейг П. Мотлін

6
Це гарна порада загалом, але це не дає відповіді на питання.
Навін

1
Конкретним прикладом, коли може бути ПРАВИЛЬНЕ дизайнерське рішення отримати інформацію про абонента, є реалізація INotifyPropertyChangedінтерфейсу .NET . Хоча цей конкретний приклад відсутній у Java, та сама проблема може проявлятися при спробі моделювати поля / getters як рядки для Reflection.
Кріс Керекес

30

Java 9 - JEP 259: API-прогулянка стеком

JEP 259 забезпечує ефективний стандартний API для ходіння по стеках, що дозволяє легко фільтрувати інформацію та ледачий доступ до інформації у стеках стеків. Перед API Stack-Walking поширеними способами доступу до фреймів стека були:

Throwable::getStackTraceі Thread::getStackTraceповернути масив StackTraceElementоб'єктів, який містить ім'я класу та ім'я кожного елемента стека-сліду.

SecurityManager::getClassContextце захищений метод, який дозволяє SecurityManagerпідкласу отримати доступ до контексту класу.

Внутрішній sun.reflect.Reflection::getCallerClassметод JDK, який ви не повинні використовувати

Використання цих API зазвичай неефективне:

Ці API вимагають від VM охоче робити знімок всього стека , і вони повертають інформацію, що представляє весь стек. Неможливо уникнути витрат на вивчення всіх кадрів, якщо абонента цікавлять лише перші кілька кадрів на стеку.

Для того, щоб знайти клас безпосереднього абонента, спочатку отримайте StackWalker:

StackWalker walker = StackWalker
                           .getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);

Тоді або телефонуйте getCallerClass():

Class<?> callerClass = walker.getCallerClass();

або walkв StackFrames і отримати перший попередній StackFrame:

walker.walk(frames -> frames
      .map(StackWalker.StackFrame::getDeclaringClass)
      .skip(1)
      .findFirst());

15

Oneliner :

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

Зауважте, що вам може знадобитися замінити 2 на 1.


10

Цей метод робить те саме, але трохи простіше і, можливо, трохи ефективніше, і якщо ви використовуєте роздуми, він автоматично пропускає ці кадри. Єдине питання полягає в тому, що він може бути відсутнім у JVM, що не є Sun, хоча він включений до класів виконання JRockit 1.4 -> 1.6. (Справа в тому, що це не громадський клас).

sun.reflect.Reflection

    /** Returns the class of the method <code>realFramesToSkip</code>
        frames up the stack (zero-based), ignoring frames associated
        with java.lang.reflect.Method.invoke() and its implementation.
        The first frame is that associated with this method, so
        <code>getCallerClass(0)</code> returns the Class object for
        sun.reflect.Reflection. Frames associated with
        java.lang.reflect.Method.invoke() and its implementation are
        completely ignored and do not count toward the number of "real"
        frames skipped. */
    public static native Class getCallerClass(int realFramesToSkip);

Що стосується realFramesToSkipзначення, версії Sun 1.5 та 1.6 VM java.lang.System, існує захищений пакунком метод, який називається getCallerClass (), який викликає sun.reflect.Reflection.getCallerClass(3), але в моєму допоміжному класі я використовував 4, оскільки є доданий кадр допоміжного класу. виклик.


16
Використання класів реалізації JVM - це дуже погана ідея.
Лоуренс Дол

7
Помічено. Я вказував, що це не публічний клас, і захищений метод getCallerClass () в java.lang.System присутній у всіх 1.5+ VM, які я переглянув, включаючи IBM, JRockit і Sun, але ваше твердження консервативно звучить .
Микола

6
@Software Monkey, як і зазвичай, "все залежить". Зробити щось подібне для налагодження чи тестування журналу - особливо якщо він ніколи не закінчується виробничим кодом - або якщо ціль розгортання є суто ПК для розробника, можливо, буде добре. Той, хто все ще думає інакше, навіть у таких випадках: вам би потрібно було насправді пояснити " дійсно погану ідею", міркуючи краще, ніж просто сказати, що це погано ...

8
Крім того, за подібною логікою ви також можете стверджувати, що щоразу, коли ви використовуєте функцію, пов’язану зі сплячим режимом, яка не сумісна з JPA, це завжди " дійсно погана ідея". Або якщо ви збираєтеся використовувати функції Oracle, які відсутні в інших базах даних, це " дійсно погана ідея". Звичайно, це безпечніший спосіб мислення та, безумовно, хороша порада для певного використання, але автоматично викидати корисні інструменти лише тому, що він не працюватиме з конфігурацією програмного забезпечення, яку ви, е .., зовсім не використовуєте ? Це трохи негнучко і трохи нерозумно.

5
Неохоронне використання специфічних для постачальника класів представляє більш високу ймовірність виникнення проблем, але слід визначити шлях до витонченої деградації, якщо відповідного класу немає (або заборонено з якихось причин). На мою думку, політика відмови від використання будь-яких конкретних категорій продавців є, на мій погляд, трохи наївною. Огляньте у вихідному коді деяких бібліотек, які ви використовуєте у виробництві, і подивіться, чи робить це хтось із них. (sun.misc.Unsafe можливо?)
Микола

7
     /**
       * Get the method name for a depth in call stack. <br />
       * Utility function
       * @param depth depth in the call stack (0 means current method, 1 means call method, ...)
       * @return method name
       */
      public static String getMethodName(final int depth)
      {
        final StackTraceElement[] ste = new Throwable().getStackTrace();

        //System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
        return ste[ste.length - depth].getMethodName();
      }

Наприклад, якщо ви намагаєтеся отримати рядок методу виклику з метою налагодження, вам потрібно пройти клас класу Utility, в якому ви
кодуєте ці статичні методи: (старий код java1.4, просто для ілюстрації потенційного використання StackTraceElement)

        /**
          * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
          * From the Stack Trace.
          * @return "[class#method(line)]: " (never empty, first class past StackTraceUtils)
          */
        public static String getClassMethodLine()
        {
            return getClassMethodLine(null);
        }

        /**
          * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass. <br />
          * Allows to get past a certain class.
          * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. 
          * @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils)
          */
        public static String getClassMethodLine(final Class aclass)
        {
            final StackTraceElement st = getCallingStackTraceElement(aclass);
            final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber()
            +")] <" + Thread.currentThread().getName() + ">: ";
            return amsg;
        }

     /**
       * Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass. <br />
       * Stored in array of the callstack. <br />
       * Allows to get past a certain class.
       * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. 
       * @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils)
       * @throws AssertionFailedException if resulting statckTrace is null (RuntimeException)
       */
      public static StackTraceElement getCallingStackTraceElement(final Class aclass)
      {
        final Throwable           t         = new Throwable();
        final StackTraceElement[] ste       = t.getStackTrace();
        int index = 1;
        final int limit = ste.length;
        StackTraceElement   st        = ste[index];
        String              className = st.getClassName();
        boolean aclassfound = false;
        if(aclass == null)
        {
            aclassfound = true;
        }
        StackTraceElement   resst = null;
        while(index < limit)
        {
            if(shouldExamine(className, aclass) == true)
            {
                if(resst == null)
                {
                    resst = st;
                }
                if(aclassfound == true)
                {
                    final StackTraceElement ast = onClassfound(aclass, className, st);
                    if(ast != null)
                    {
                        resst = ast;
                        break;
                    }
                }
                else
                {
                    if(aclass != null && aclass.getName().equals(className) == true)
                    {
                        aclassfound = true;
                    }
                }
            }
            index = index + 1;
            st        = ste[index];
            className = st.getClassName();
        }
        if(resst == null) 
        {
            //Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies 
            throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$
        }
        return resst;
      }

      static private boolean shouldExamine(String className, Class aclass)
      {
          final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils"
            ) == false || (aclass !=null && aclass.getName().endsWith("LogUtils")));
          return res;
      }

      static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st)
      {
          StackTraceElement   resst = null;
          if(aclass != null && aclass.getName().equals(className) == false)
          {
              resst = st;
          }
          if(aclass == null)
          {
              resst = st;
          }
          return resst;
      }

Мені потрібно було щось, що працює з Java 1.4, і ця відповідь була дуже корисною! Дякую!
RGO

6

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

Я робив це раніше для утиліти журналу в додатку, де продуктивність не мала великого значення (продуктивність рідко має значення багато, насправді - поки ви показуєте результат такої дії, як швидке натискання кнопки).

Це було раніше, ніж ви могли отримати трасування стека, винятки просто мали .printStackTrace (), тому мені довелося перенаправити System.out на потік мого власного створення, тоді (новий виняток ()). PrintStackTrace (); Перенаправляйте System.out назад і розбирайте потік. Веселі речі.


Класно; вам не доведеться його кидати?
krosenvold

Ні, принаймні так я його пам’ятаю, я цього не робив за кілька років, але я майже впевнений, що нове виняток - це просто створення об’єкта, а викидання винятку не робить для нього нічого, крім передачі це до пункту catch ().
Білл К

Акуратний. Я був схильний кинути це, щоб імітувати фактичний виняток.
Сатіш

Ні, оскільки у Java 5 існує метод на Thread, щоб отримати поточний стек як масив StackTraceElements; це все ще не дешево, але дешевше, ніж старе рішення для розбору винятків.
Лоуренс Дол

@Software Monkey Хоча я впевнений, що це більш доречно, що змушує вас сказати, що дешевше? Я б припустив, що буде використовуватися той самий механізм, а якщо ні, то навіщо робити один повільніше, коли він робить те саме?
Білл К

1
private void parseExceptionContents(
      final Exception exception,
      final OutputStream out)
   {
      final StackTraceElement[] stackTrace = exception.getStackTrace();
      int index = 0;
      for (StackTraceElement element : stackTrace)
      {
         final String exceptionMsg =
              "Exception thrown from " + element.getMethodName()
            + " in class " + element.getClassName() + " [on line number "
            + element.getLineNumber() + " of file " + element.getFileName() + "]";
         try
         {
            out.write((headerLine + newLine).getBytes());
            out.write((headerTitlePortion + index++ + newLine).getBytes() );
            out.write((headerLine + newLine).getBytes());
            out.write((exceptionMsg + newLine + newLine).getBytes());
            out.write(
               ("Exception.toString: " + element.toString() + newLine).getBytes());
         }
         catch (IOException ioEx)
         {
            System.err.println(
                 "IOException encountered while trying to write "
               + "StackTraceElement data to provided OutputStream.\n"
               + ioEx.getMessage() );
         }
      }
   }

0

Ось частина коду, яку я склав на підставі підказок, показаних у цій темі. Сподіваюся, це допомагає.

(Не соромтеся робити будь-які пропозиції щодо вдосконалення цього коду, будь ласка, скажіть мені)

Лічильник:

public class InstanceCount{
    private static Map<Integer, CounterInstanceLog> instanceMap = new HashMap<Integer, CounterInstanceLog>();
private CounterInstanceLog counterInstanceLog;


    public void count() {
        counterInstanceLog= new counterInstanceLog();
    if(counterInstanceLog.getIdHashCode() != 0){
    try {
        if (instanceMap .containsKey(counterInstanceLog.getIdHashCode())) {
         counterInstanceLog= instanceMap .get(counterInstanceLog.getIdHashCode());
    }

    counterInstanceLog.incrementCounter();

            instanceMap .put(counterInstanceLog.getIdHashCode(), counterInstanceLog);
    }

    (...)
}

І об'єкт:

public class CounterInstanceLog{
    private int idHashCode;
    private StackTraceElement[] arrayStackTraceElements;
    private int instanceCount;
    private String callerClassName;

    private StackTraceElement getProjectClasses(int depth) {
      if(depth< 10){
        getCallerClassName(sun.reflect.Reflection.getCallerClass(depth).getName());
        if(getCallerClassName().startsWith("com.yourproject.model")){
            setStackTraceElements(Thread.currentThread().getStackTrace());
            setIdHashCode();
        return arrayStackTraceElements[depth];
        }
        //+2 because one new item are added to the stackflow
        return getProjectClasses(profundidade+2);           
      }else{
        return null;
      }
    }

    private void setIdHashCode() {
        if(getNomeClasse() != null){
            this.idHashCode = (getCallerClassName()).hashCode();
        }
    }

    public void incrementaContador() {
    this.instanceCount++;
}

    //getters and setters

    (...)



}

0
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

class DBConnection {
    String createdBy = null;

    DBConnection(Throwable whoCreatedMe) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(os);
        whoCreatedMe.printStackTrace(pw);
        try {
            createdBy = os.toString();
            pw.close();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class ThrowableTest {

    public static void main(String[] args) {

        Throwable createdBy = new Throwable(
                "Connection created from DBConnectionManager");
        DBConnection conn = new DBConnection(createdBy);
        System.out.println(conn.createdBy);
    }
}

АБО

public static interface ICallback<T> { T doOperation(); }


public class TestCallerOfMethod {

    public static <T> T callTwo(final ICallback<T> c){
        // Pass the object created at callee to the caller
        // From the passed object we can get; what is the callee name like below.
        System.out.println(c.getClass().getEnclosingMethod().getName());
        return c.doOperation();
    }

    public static boolean callOne(){
        ICallback callBackInstance = new ICallback(Boolean){
            @Override
            public Boolean doOperation() 
            {
                return true;
            }
        };
        return callTwo(callBackInstance);
    }

    public static void main(String[] args) {
         callOne();
    }
}

0

використовувати цей метод: -

 StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
 stackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
 System.out.println(e.getMethodName());

Викликаючи приклад методу Код тут: -

public class TestString {

    public static void main(String[] args) {
        TestString testString = new TestString();
        testString.doit1();
        testString.doit2();
        testString.doit3();
        testString.doit4();
    }

    public void doit() {
        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
        StackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
        System.out.println(e.getMethodName());
    }

    public void doit1() {
        doit();
    }

    public void doit2() {
        doit();
    }

    public void doit3() {
        doit();
    }

    public void doit4() {
        doit();
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.