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


392

Мне нужно найти вызывающего метода. Возможно ли использовать Stacktrace или отражение?


5
Просто интересно, а зачем тебе это делать?
Джульетта

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

30
@ Sathish Похоже, вы должны переосмыслить этот дизайн
krosenvold

7
@Juliet Как часть рефакторинга большого куска кода, недавно я изменил метод, который используется многими вещами. Есть определенный способ определить, правильно ли код использовал новый метод, поэтому я печатал класс и номер строки, которые вызывали его в этих случаях. Вне регистрации я не вижу никакой реальной цели для чего-то подобного. Хотя сейчас я хочу написать API-интерфейс, в котором DontNameYourMethodFooExceptionвызывается метод, называемый foo.
Cruncher

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

Ответы:


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

Согласно Javadocs:

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

StackTraceElementИмеет getClassName(), getFileName(), getLineNumber()и getMethodName().

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


7
Я должен отметить, что getStackTrace () все еще создает исключение, так что это не очень быстро - просто более удобно.
Майкл Майерс

42
Обратите внимание, что этот метод не даст вам вызывающего, а только тип вызывающего . У вас не будет ссылки на объект, вызывающий ваш метод.
Йоахим Зауэр

3
Просто примечание, но для 1.5 JVM Thread.currentThread (). GetStackTrace () кажется намного медленнее, чем создание нового Exception () (примерно в 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 update 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). Вы увидите, что все методы возвращают одно и то же className - TestGetCallerClassName.
Йохан Кавинг

1
getCallerClass устарела и будет удалена в 7u40 .. sad :(
lyomi

36

Похоже, вы пытаетесь избежать передачи ссылки thisна метод. Передача thisнамного лучше, чем поиск вызывающего по текущей трассировке стека. Рефакторинг к более оригинальному дизайну еще лучше. Вам не нужно знать звонящего. Передайте объект обратного вызова при необходимости.


6
++ Знание вызывающего абонента - это слишком много информации. Если нужно, вы можете передать интерфейс, но есть большая вероятность, что потребуется серьезный рефакторинг. @satish должен опубликовать свой код и позволить нам повеселиться с ним :)
Билл К

15
Существуют веские причины для желания сделать это. У меня было несколько случаев, когда я находил это полезным, например, во время тестирования.
Eelco

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

6
Это хороший совет в целом, но он не отвечает на вопрос.
Навин

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

31

Java 9 - JEP 259: API стека

JEP 259 предоставляет эффективный стандартный API для обхода стека, который позволяет легко фильтровать и легко получать доступ к информации в следах стека. До использования Stack-Walking API распространенными способами доступа к фреймам стека были:

Throwable::getStackTraceи Thread::getStackTraceвозвращает массив StackTraceElementобъектов, которые содержат имя класса и имя метода каждого элемента трассировки стека.

SecurityManager::getClassContextявляется защищенным методом, который позволяет SecurityManagerподклассу обращаться к контексту класса.

JDK-внутренний sun.reflect.Reflection::getCallerClassметод, который вы не должны использовать в любом случае

Использование этих API обычно неэффективно:

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

Чтобы найти класс непосредственного абонента, сначала получите 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 и VM java.lang.System, есть метод с защитой пакетов, называемый getCallerClass (), который вызывает sun.reflect.Reflection.getCallerClass(3), но в своем вспомогательном служебном классе я использовал 4, поскольку есть добавленный фрейм вспомогательного класса. призывание.


16
Использование классов реализации JVM - очень плохая идея.
Лоуренс Дол

7
Отметил. Я указал, что это не публичный класс, и защищенный метод getCallerClass () в java.lang.System присутствует на всех более чем 1,5 виртуальных машинах, на которые я смотрел, включая IBM, JRockit и Sun, но ваше утверждение консервативно обосновано. ,
Николас

6
@ Обезьяна программного обеспечения, как обычно, "все зависит". Выполнение чего-либо подобного для помощи в отладке или ведении журнала тестирования, особенно если оно никогда не заканчивается в рабочем коде, или если целью развертывания является исключительно компьютер разработчика, вероятно, будет хорошо. Любой, кто все еще думает иначе даже в таких случаях: вам нужно было бы на самом деле объяснить

8
Кроме того, по аналогичной логике вы также можете утверждать, что всякий раз, когда вы используете специфичную для Hibernate функцию, которая не совместима с JPA, это всегда « действительно плохая идея». Или, если вы собираетесь использовать специфичные для Oracle функции, которых нет в других базах данных, это « действительно плохая идея». Конечно, это безопасное мышление и, безусловно, полезный совет для определенных целей, но он автоматически выбрасывает полезные инструменты только потому, что он не будет работать с конфигурацией программного обеспечения, которую вы не используете вообще ? Это слишком негибко и немного глупо.

5
Неохраняемое использование классов, специфичных для поставщика, будет представлять более высокую вероятность возникновения проблем, но следует определить путь постепенного ухудшения качества, если рассматриваемый класс отсутствует (или по какой-либо причине запрещен). На мой взгляд, политика категорического отказа от использования классов, специфичных для конкретного поставщика, немного наивна. Изучите исходный код некоторых библиотек, которые вы используете в производстве, и посмотрите, сделает ли это какая-либо из них. (sun.misc. Возможно, небезопасно?)
Николас

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 в поток моего собственного создания, затем (new Exception ()). PrintStackTrace (); Перенаправьте System.out обратно и проанализируйте поток. Прикольные вещи.


Круто; ты не должен бросать это?
Кросенволд

Нет, по крайней мере, так я это помню, я не делал этого в течение нескольких лет, но я почти уверен, что создание исключения - это просто создание объекта, а создание исключения ничего с ним не делает, кроме pass это к предложению catch ().
Билл К

Ухоженная. Я был склонен бросить это, чтобы симулировать фактическое исключение.
Сатиш

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

@ Обезьяна программного обеспечения Хотя я уверен, что это более уместно, что заставляет вас говорить, что это дешевле? Я бы предположил, что будет использоваться тот же механизм, и если нет, то зачем замедлять его, когда он делает то же самое?
Билл К

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.