Предупреждение метода синтетического доступа

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

Вот моя проблема с примером кода

public class Test {
    private String testString;

    public void performAction() {

        new Thread( new Runnable() {
            @Override
            public void run() {
                testString = "initialize"; // **
            }
        });
    }
}

Строка с ** дает мне предупреждение в eclipse

Read access to enclosing field Test.testString is emulated by a synthetic accessor method. 
Increasing its visibility will improve your performance.

Проблема в том, что я не хочу изменять модификатор доступа testString. Кроме того, не хотите создавать геттер для него.

Какое изменение должно быть сделано?


More descriptive example 

public class Synthetic
{
    private JButton testButton;

    public Synthetic()
    {
        testButton = new JButton("Run");
        testButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae)
                {
                    /* Sample code */
                    if( testButton.getText().equals("Pause") ) {
                        resetButton(); // **    
                    } else if( testButton.getText().equals("Run") ) {
                        testButton.setText("Pause"); // **  
                    }

                }
            }
        );
    }

    public void reset() {
        //Some operations
        resetButton();
    }

    private void resetButton() {
        testButton.setText("Run");
    }
}

Строки с ** дают мне то же предупреждение.

Ответ 1

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

Для метода resetButton() вы можете добавить аргумент, чтобы передать объект, на который нужно действовать, если вы сделали это, это не такая большая проблема, которая снижает его ограничения доступа.

Ответ 2

Что такое "синтетический" метод?

Начиная с Method class (и он родительский, Member), мы узнаем, что синтетические члены "введены компилятором" и что JLS §13.1 расскажет нам Больше. Он отмечает:

Конструкция, выпущенная компилятором Java, должна быть помечена как синтетическая, если она не соответствует конструкции, явно или неявно объявленной в исходном коде

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

Член класса, который не отображается в исходном коде, должен быть отмечен с помощью атрибута Synthetic, иначе он должен иметь установленный флаг ACC_SYNTHETIC. Единственными исключениями из этого требования являются методы, генерируемые компилятором, которые не рассматриваются как артефакты реализации....

Атрибут Synthetic был введен в JDK 1.1 для поддержки вложенных классов и интерфейсов.

Другими словами, "синтетические" методы - это артефакт реализации, который компилятор Java представляет для поддержки языковых функций, которые сам JVM не поддерживает.

Какая проблема?

Вы столкнулись с одним из таких случаев; вы пытаетесь получить доступ к полю private класса из анонимного внутреннего класса. Язык Java позволяет это, но JVM не поддерживает его, поэтому компилятор Java генерирует синтетический метод, который предоставляет поле private для внутреннего класса. Это безопасно, потому что компилятор не разрешает другим классам вызывать этот метод, однако он вводит две (небольшие) проблемы:

  • Объявляются дополнительные методы. Это не должно быть проблемой для большинства случаев использования, но если вы работаете в ограниченной среде, такой как Android, и генерируете много этих синтетических методов, вы можете столкнуться с проблемами.
  • Доступ к этому полю осуществляется косвенно через синтетический метод, а не напрямую. Это тоже не должно быть проблемой, кроме случаев использования с высокой степенью производительности. Если вы не хотите использовать метод getter здесь по соображениям производительности, вам также не нужен синтетический метод getter. Это редко бывает проблемой на практике.

Короче говоря, они действительно неплохие. Если у вас нет конкретной причины избегать синтетических методов (т.е. Вы окончательно определили, что они являются узким местом в вашем приложении), вы должны просто позволить компилятору генерировать их по своему усмотрению. Попробуйте отключить предупреждение Eclipse, если оно вас беспокоит.

Что мне делать с ними?

Если вы действительно хотите, чтобы компилятор не генерировал синтетические методы, у вас есть несколько вариантов:

Вариант 1: изменение разрешений

Поля private-private или protected доступны для внутренних классов напрямую. Специально для чего-то вроде приложения Swing это должно быть хорошо. Но вы говорите, что хотите этого избежать, поэтому идем.

Вариант 2: Создать геттер

Оставьте свои поля в покое, но явно создайте глагол protected или public и используйте его вместо этого. Это, по сути, то, что автоматически выполняет компилятор для вас, но теперь у вас есть прямой контроль над поведением метода.

Вариант 3: используйте локальную переменную и разделите ссылку на оба класса

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

public Synthetic() {
  // Create final local instance - will be reachable by the inner class
  final JButton testButton = new JButton("Run");
  testButton.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
          /* Sample code */
          if( testButton.getText().equals("Pause") ) {
            resetButton();
          } else if( testButton.getText().equals("Run") ) {
            testButton.setText("Pause");
          }
        }
      });
  // associate same instance with outer class - this.testButton can be final too
  this.testButton = testButton;
}

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


Помимо безопасности потока

Ваш пример Test class не является потокобезопасным - testString устанавливается в отдельном Thread, но вы не синхронизируете это назначение. Маркировка testString как volatile будет достаточной, чтобы все потоки увидели обновление. Пример Synthetic не имеет этой проблемы, так как testButton задается только в конструкторе, но в этом случае было бы целесообразно отметить testButton как final.

Ответ 3

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

Ответ 4

Я думаю, проблема в том, что вы устанавливаете String в родительском классе. Это будет страдать от производительности, потому что Thread должен посмотреть, где эта переменная снова. Я думаю, что более чистый подход использует Callable, который возвращает String, а затем делает .get() или что-то, что возвращает результат. После получения результата вы можете вернуть данные родительскому классу.

Идея заключается в том, что вы хотите удостовериться, что Thread только делает одну вещь и только одну вещь вместо установки переменных в других классах. Это более чистый подход и, вероятно, быстрее, потому что внутренняя нить не получает ничего вне себя. Это означает, что меньше блокировки.:)

Ответ 5

Это один из редких случаев, когда видимость по умолчанию Java (также называемая "private" ) используется.

public class Test {
    /* no private */ String testString;

    public void performAction() {

        new Thread( new Runnable() {
            @Override
            public void run() {
                testString = "initialize"; // **
            }
        });
    }
}

Это сделает следующее:

  • testString теперь доступен для всех классов в том же пакете, что и внешний класс (Test).
  • Поскольку внутренние классы фактически генерируются как OuterClassPackage.OuterClassName$InnerClassName, они также находятся в одном пакете. Поэтому они могут напрямую обращаться к этому полю.
  • В отличие от этого поля protected, видимость по умолчанию не сделает это поле доступным для подклассов (за исключением случаев, когда они находятся в одном пакете, конечно). Поэтому вы не загрязняете свой API для внешних пользователей.

При использовании private javac вместо этого генерирует синтетический аксессуар, который сам по себе является только методом getter с видимостью по умолчанию Java. Таким образом, это в основном делает то же самое, за исключением минимальных затрат дополнительного метода.